/*
* Copyright (C) 2011 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.errai.databinding.client.api;
import java.awt.Checkbox;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.jboss.errai.common.client.api.Assert;
import org.jboss.errai.common.client.api.WrappedPortable;
import org.jboss.errai.common.client.ui.ElementWrapperWidget;
import org.jboss.errai.databinding.client.AbstractOneWayConverter;
import org.jboss.errai.databinding.client.ConverterRegistrationKey;
import org.jboss.errai.databinding.client.OneWayConverter;
import org.jboss.errai.databinding.client.TwoWayConverter;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DoubleBox;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.IntegerBox;
import com.google.gwt.user.client.ui.LongBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextBoxBase;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.DateBox;
import com.google.gwt.user.datepicker.client.DatePicker;
/**
* Type conversion utility used by the generated {@link Bindable} proxies.
*
* @author Christian Sadilek <csadilek@redhat.com>
* @author Max Barkley <mbarkley@redhat.com>
*/
public class Convert {
private static class AnyToStringConverter<D> extends AbstractOneWayConverter<D, String> {
public AnyToStringConverter(final Class<D> domainType) {
super(domainType, String.class);
}
@Override
public String convert(final D value) {
if (value == null) {
return "";
}
else {
return value.toString();
}
}
}
private static class IdentityConverter<T> extends AbstractOneWayConverter<T, T> {
public IdentityConverter(final Class<T> type) {
super(type, type);
}
@Override
public T convert(final T value) {
return value;
}
}
private static class StringToIntegerConverter extends AbstractOneWayConverter<String, Integer> {
public StringToIntegerConverter() {
super(String.class, Integer.class);
}
@Override
public Integer convert(final String value) {
if (isEmpty(value)) {
return null;
}
else {
return Integer.parseInt(value);
}
}
}
private static class StringToLongConverter extends AbstractOneWayConverter<String, Long> {
public StringToLongConverter() {
super(String.class, Long.class);
}
@Override
public Long convert(final String value) {
if (isEmpty(value)) {
return null;
}
else {
return Long.parseLong(value);
}
}
}
private static class StringToFloatConverter extends AbstractOneWayConverter<String, Float> {
public StringToFloatConverter() {
super(String.class, Float.class);
}
@Override
public Float convert(final String value) {
if (isEmpty(value)) {
return null;
}
else {
return Float.parseFloat(value);
}
}
}
private static class StringToDoubleConverter extends AbstractOneWayConverter<String, Double> {
public StringToDoubleConverter() {
super(String.class, Double.class);
}
@Override
public Double convert(final String value) {
if (isEmpty(value)) {
return null;
}
else {
return Double.parseDouble(value);
}
}
}
private static class StringToBigIntegerConverter extends AbstractOneWayConverter<String, BigInteger> {
public StringToBigIntegerConverter() {
super(String.class, BigInteger.class);
}
@Override
public BigInteger convert(final String value) {
if (isEmpty(value)) {
return null;
}
else {
return new BigInteger(value);
}
}
}
private static class StringToBigDecimalConverter extends AbstractOneWayConverter<String, BigDecimal> {
public StringToBigDecimalConverter() {
super(String.class, BigDecimal.class);
}
@Override
public BigDecimal convert(final String value) {
if (isEmpty(value)) {
return null;
}
else {
return new BigDecimal(value);
}
}
}
private static class StringToBooleanConverter extends AbstractOneWayConverter<String, Boolean> {
public StringToBooleanConverter() {
super(String.class, Boolean.class);
}
@Override
public Boolean convert(final String value) {
if (isEmpty(value)) {
return null;
}
else {
return Boolean.parseBoolean(value);
}
}
}
private static final OneWayConverter<String, Integer> STRING_TO_INT = new StringToIntegerConverter();
private static final OneWayConverter<String, Long> STRING_TO_LONG = new StringToLongConverter();
private static final OneWayConverter<String, Float> STRING_TO_FLOAT = new StringToFloatConverter();
private static final OneWayConverter<String, Double> STRING_TO_DOUBLE = new StringToDoubleConverter();
private static final OneWayConverter<String, BigInteger> STRING_TO_BIG_INTEGER = new StringToBigIntegerConverter();
private static final OneWayConverter<String, BigDecimal> STRING_TO_BIG_DECIMAL = new StringToBigDecimalConverter();
private static final OneWayConverter<String, Boolean> STRING_TO_BOOLEAN = new StringToBooleanConverter();
private static final Map<Class<?>, Class<?>> boxedTypesByPrimitive = new HashMap<>();
static {
boxedTypesByPrimitive.put(boolean.class, Boolean.class);
boxedTypesByPrimitive.put(short.class, Short.class);
boxedTypesByPrimitive.put(int.class, Integer.class);
boxedTypesByPrimitive.put(long.class, Long.class);
boxedTypesByPrimitive.put(float.class, Float.class);
boxedTypesByPrimitive.put(double.class, Double.class);
boxedTypesByPrimitive.put(byte.class, Byte.class);
boxedTypesByPrimitive.put(char.class, Character.class);
}
@SuppressWarnings("rawtypes")
private static final Map<ConverterRegistrationKey, Converter> defaultConverters =
new HashMap<ConverterRegistrationKey, Converter>();
/**
* Lookup a default converter.
*
* @return A {@link Converter} between the given types, or else {@code null} if no such default converter exists.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static <M, W> Converter<M, W> getConverter(final Class<M> modelValueType, final Class<W> componentValueType) {
Converter converter = defaultConverters.get(new ConverterRegistrationKey(modelValueType, componentValueType));
if (converter == null) {
converter = maybeCreateBuiltinConverter(maybeBoxPrimitive(modelValueType), maybeBoxPrimitive(componentValueType));
}
return converter;
}
private static boolean isEmpty(final String value) {
return value == null || value.equals("");
}
private static <M, C> Converter<M, C> maybeCreateBuiltinConverter(final Class<M> modelValueType, final Class<C> widgetValueType) {
Assert.notNull(modelValueType);
Assert.notNull(widgetValueType);
final OneWayConverter<M, C> modelToWidget = getOneWayConverter(modelValueType, widgetValueType);
final OneWayConverter<C, M> widgetToModel = getOneWayConverter(widgetValueType, modelValueType);
if (modelToWidget == null || widgetToModel == null) {
return null;
}
else {
return TwoWayConverter.createConverter(modelToWidget, widgetToModel);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static <D, T> OneWayConverter<D, T> getOneWayConverter(final Class<D> domainType, final Class<T> targetType) {
if (domainType.equals(targetType)) {
return new IdentityConverter(domainType);
}
else if (targetType.equals(String.class)) {
return new AnyToStringConverter(domainType);
}
else if (domainType.equals(String.class)) {
if (targetType.equals(Integer.class)) {
return (OneWayConverter<D, T>) STRING_TO_INT;
}
else if (targetType.equals(Long.class)) {
return (OneWayConverter<D, T>) STRING_TO_LONG;
}
else if (targetType.equals(Float.class)) {
return (OneWayConverter<D, T>) STRING_TO_FLOAT;
}
else if (targetType.equals(Double.class)) {
return (OneWayConverter<D, T>) STRING_TO_DOUBLE;
}
else if (targetType.equals(BigInteger.class)) {
return (OneWayConverter<D, T>) STRING_TO_BIG_INTEGER;
}
else if (targetType.equals(BigDecimal.class)) {
return (OneWayConverter<D, T>) STRING_TO_BIG_DECIMAL;
}
else if (targetType.equals(Boolean.class)) {
return (OneWayConverter<D, T>) STRING_TO_BOOLEAN;
}
else {
return null;
}
}
return new IdentityConverter(targetType);
}
private static Class<?> maybeBoxPrimitive(final Class<?> type) {
if (type.isPrimitive()) {
return Assert.notNull("Unrecognized primitive " + type.getName(), boxedTypesByPrimitive.get(type));
}
else {
return type;
}
}
/**
* Return an converter that does not modify model or component values.
*/
public static <T> Converter<T, T> identityConverter(final Class<T> type) {
final IdentityConverter<T> idOneWay = new IdentityConverter<>(type);
return TwoWayConverter.createConverter(idOneWay, idOneWay);
}
/**
* Registers a {@link Converter} as a default for the provided model and widget types. The default converter will be
* used in case no custom converter is provided when binding a model to a widget.
*
* @param <M>
* The type of the model value (field type of the model)
* @param <W>
* The type of the widget value (e.g. String for a {@link TextBox} (=HasValue<String>) or Boolean for a
* {@link Checkbox} (=HasValue<Boolean>)))
* @param modelValueType
* The model type the provided converter converts to, must not be null.
* @param widgetValueType
* The widget type the provided converter converts to, must not be null.
* @param converter
* The converter to register as a default for the provided model and widget types.
*/
public static <M, W> void registerDefaultConverter(final Class<M> modelValueType, final Class<W> widgetValueType,
final Converter<M, W> converter) {
Assert.notNull(modelValueType);
Assert.notNull(widgetValueType);
defaultConverters.put(new ConverterRegistrationKey(modelValueType, widgetValueType), converter);
}
/**
* Deletes all registrations of default converters.
*/
public static void deregisterDefaultConverters() {
defaultConverters.clear();
}
@SuppressWarnings("rawtypes")
public static Class inferWidgetValueType(final Widget widget, final Class<?> defaultWidgetValueType) {
Class widgetValueType = null;
if (widget instanceof ElementWrapperWidget) {
widgetValueType = ((ElementWrapperWidget<?>) widget).getValueType();
}
else if (widget instanceof TakesValue) {
Object value = ((TakesValue) widget).getValue();
if (value != null) {
if (value instanceof WrappedPortable) {
value = ((WrappedPortable) value).unwrap();
}
widgetValueType = value.getClass();
}
else if (widget instanceof TextBoxBase) {
widgetValueType = String.class;
}
else if (widget instanceof DateBox || widget instanceof DatePicker) {
widgetValueType = Date.class;
}
else if (widget instanceof CheckBox || widget instanceof ToggleButton) {
widgetValueType = Boolean.class;
}
else if (widget instanceof LongBox) {
widgetValueType = Long.class;
}
else if (widget instanceof DoubleBox) {
widgetValueType = Double.class;
}
else if (widget instanceof IntegerBox) {
widgetValueType = Integer.class;
}
else {
widgetValueType = defaultWidgetValueType;
}
}
else if (widget instanceof HasText) {
widgetValueType = String.class;
}
else {
widgetValueType = String.class;
}
return widgetValueType;
}
}