/*******************************************************************************
* Copyright (c) 2006-2013 The RCP Company and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* The RCP Company - initial API and implementation
*******************************************************************************/
/**
*
*/
package com.rcpcompany.uibindings.internal.decorators;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.ParsePosition;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import com.rcpcompany.uibindings.Constants;
import com.rcpcompany.uibindings.IBindingMessage;
import com.rcpcompany.uibindings.IFormatter;
import com.rcpcompany.uibindings.IManager;
import com.rcpcompany.uibindings.INumberDecoratorProvider;
import com.rcpcompany.uibindings.IUIBindingDecorator;
import com.rcpcompany.uibindings.IValueBinding;
import com.rcpcompany.uibindings.UIBindingsUtils;
import com.rcpcompany.uibindings.decorators.SimpleUIBindingDecorator;
import com.rcpcompany.uibindings.units.IUnitBindingSupport;
import com.rcpcompany.uibindings.units.IUnitBindingSupportContext;
import com.rcpcompany.uibindings.units.IUnitBindingSupportListener;
import com.rcpcompany.uibindings.utils.CoreRuntimeException;
import com.rcpcompany.uibindings.validators.ConstraintValidatorAdapter;
import com.rcpcompany.utils.logging.LogUtils;
/**
* {@link IUIBindingDecorator Binding decorator} for number formats.
* <p>
* This number decorator can be used even after it has been disposed!!! This is used in
* {@link ConstraintValidatorAdapter}.
* <p>
* Currently with a hack to solve SIMA-457: Editors in the MassData form does not recognise
* scientific notation on input http://jira.marintek.sintef.no/jira/browse/SIMA-457
*
* @author Tonny Madsen, The RCP Company
*/
public class NumberBindingDecorator extends SimpleUIBindingDecorator implements IUIBindingDecorator,
IUnitBindingSupportContext, IUnitBindingSupportListener {
/**
* A single interval as described in {@link Constants#ARG_RANGE}.
*/
public class Interval {
/**
* Whether min has been set for this interval.
*/
public boolean myMinSet = false;
/**
* Whether max has been set for this interval.
*/
public boolean myMaxSet = false;
/**
* Whether min value is inclusive.
*/
public boolean myMinInclusive = true;
/**
* Whether max value is inclusive.
*/
public boolean myMaxInclusive = true;
/**
* The minimum value for this interval.
*/
public BigDecimal myMin = myAdapter.getMinimum();
/**
* The maximum value for this interval.
*/
public BigDecimal myMax = myAdapter.getMaximum();
/**
* Returns whther any of the limits has been set.
*
* @return <code>true</code> if set
*/
public boolean isMinOrMaxSet() {
return myMinSet || myMaxSet;
}
/**
* Parses and applies the limits from the range string. See {@link Constants#ARG_RANGE} for
* details on the syntax.
* <p>
* A manual parser to ensure the correct conversion of numbers is used
*
* @param range the range specification
* @throws CoreRuntimeException if the range is malformed
*/
public void parseRanges(String range) {
try {
final StreamTokenizer st = createTokenizer(range);
st.nextToken();
switch (st.ttype) {
case ']':
myMinInclusive = false;
//$FALL-THROUGH$ fallthrough
case '[':
st.nextToken();
break;
default:
LogUtils.throwException(NumberBindingDecorator.this, "In Range '" + range
+ "': Expected '[' or ']', got '" + st.toString() + "'", null);
}
if (st.ttype == StreamTokenizer.TT_NUMBER) {
myMin = new BigDecimal(st.nval);
myMinSet = true;
setLimitsSet();
st.nextToken();
}
if (st.ttype != ';') {
LogUtils.throwException(NumberBindingDecorator.this, "In Range '" + range
+ "': Expected ';', got '" + st.toString() + "'", null);
}
st.nextToken();
if (st.ttype == StreamTokenizer.TT_NUMBER) {
myMax = new BigDecimal(st.nval);
myMaxSet = true;
setLimitsSet();
st.nextToken();
}
switch (st.ttype) {
case '[':
myMaxInclusive = false;
//$FALL-THROUGH$ fallthrough
case ']':
st.nextToken();
break;
default:
LogUtils.throwException(NumberBindingDecorator.this, "In Range '" + range
+ "': Expected '[' or ']', got '" + st.toString() + "'", null);
}
if (st.ttype != StreamTokenizer.TT_EOF) {
LogUtils.throwException(NumberBindingDecorator.this, "In Range '" + range
+ "': Expected <EOS>, got '" + st.toString() + "'", null);
}
} catch (final IOException ex) {
LogUtils.throwException(NumberBindingDecorator.this, "In Range '" + range + "'", ex);
}
}
/**
* Be backward compatible - apply limits from min and max arguments.
*/
public void retrieveMinMaxLimits() {
BigDecimal argument;
argument = getBinding().getArgument(Constants.ARG_MIN, BigDecimal.class, null);
if (argument != null) {
if (myMin == null || myMin.compareTo(argument) < 0) {
myMin = argument;
myMinSet = true;
setLimitsSet();
}
}
argument = getBinding().getArgument(Constants.ARG_MAX, BigDecimal.class, null);
if (argument != null) {
if (myMax == null || myMax.compareTo(argument) > 0) {
myMax = argument;
myMaxSet = true;
setLimitsSet();
}
}
}
/**
* Checks the specified number against the range of this decorator.
*
* @param fromObject the original object
* @param number the number to test
* @param typeOfCheck the type of check performed: "declared" or "native"
* @return message if range is violated, otherwise <code>null</code>
*
* TODO: possibly change format of min and max in the returned messages
*/
public String checkRange(Object fromObject, final BigDecimal number, String typeOfCheck) {
final boolean minViolated = number.compareTo(myMin) < (myMinInclusive ? 0 : 1);
final boolean maxViolated = number.compareTo(myMax) > (myMaxInclusive ? 0 : -1);
if (!minViolated && !maxViolated) return null;
if (minViolated && !myMaxSet) {
if (BigDecimal.ZERO.equals(myMin)) {
if (myMinInclusive)
return MessageFormat.format("{0}: ''{1}'' must be positive or zero", getLabel(), fromObject,
typeOfCheck, myMin, myMax);
else
return MessageFormat.format("{0}: ''{1}'' must be positive", getLabel(), fromObject,
typeOfCheck, myMin, myMax);
} else {
if (myMinInclusive)
return MessageFormat.format("{0}: ''{1}'' outside {2} range (min {3})", getLabel(), fromObject,
typeOfCheck, myMin, myMax);
else
return MessageFormat.format("{0}: ''{1}'' outside {2} range (greater than {3})", getLabel(),
fromObject, typeOfCheck, myMin, myMax);
}
}
if (maxViolated && !myMinSet) {
if (BigDecimal.ZERO.equals(myMax)) {
if (myMaxInclusive)
return MessageFormat.format("{0}: ''{1}'' must be negative or zero", getLabel(), fromObject,
typeOfCheck, myMin, myMax);
else
return MessageFormat.format("{0}: ''{1}'' must be negative", getLabel(), fromObject,
typeOfCheck, myMin, myMax);
} else {
if (myMaxInclusive)
return MessageFormat.format("{0}: ''{1}'' outside {2} range (max {4})", getLabel(), fromObject,
typeOfCheck, myMin, myMax);
else
return MessageFormat.format("{0}: ''{1}'' outside {2} range (less than {4})", getLabel(),
fromObject, typeOfCheck, myMin, myMax);
}
}
return MessageFormat.format("{0}: ''{1}'' outside {2} range {3}{4}; {5}{6}", getLabel(), fromObject,
typeOfCheck, myMinInclusive ? "[" : "]", myMin, myMax, myMaxInclusive ? "]" : "[");
}
}
/**
* The error code used in {@link IBindingMessage messages} for number errors.
*/
public static final int NUMBER_ERROR_CODE = 1000;
/**
* Unit support for this binding or <code>null</code>.
*/
private IUnitBindingSupport myUnitSupport;
/**
* The provider.
*/
private final INumberDecoratorProvider myProvider;
/**
* @param provider the number decorator provider
*/
public NumberBindingDecorator(INumberDecoratorProvider provider) {
myProvider = provider;
}
/**
* The formatter instance.
*/
private IFormatter myFormatter;
/**
* The buffer used for the output.
*/
private StringBuilder myBuffer;
/**
* The used adapter for the specific model type.
*/
protected NumberAdapter myAdapter;
/**
* Whether any limits has been set for this decorator.
*/
private boolean myLimitsSet = false;
/**
* The last object converted in {@link #convertUIToModel(Object)}.
*/
protected Object myLastFromObject;
/**
* The last {@link BigDecimal} result in {@link #convertUIToModel(Object)}.
*/
protected BigDecimal myLastConvertedValue;
/**
* The precision and rounding mode used.
*/
protected static final MathContext MATH_CONTEXT = MathContext.DECIMAL64;
/**
* Sets whether limits have been set.
*/
public void setLimitsSet() {
myLimitsSet = true;
}
/**
* Whether any limits has been set for this decorator.
*
* @return true if set
*/
public boolean isLimitsSet() {
return myLimitsSet;
}
/**
* Returns whether the number of this decorator is an integral number - e.g. one of {@link Byte}
* , {@link Short}, {@link Integer} or {@link Long} or their corresponding primitive types.
*
* @return <code>true</code> if it is an integral number and <code>false</code> if it is a real
* number
*/
public boolean isIntegralNumber() {
return myAdapter.isIntegralNumber();
}
@Override
public void init(IValueBinding binding) {
super.init(binding);
myBuffer = new StringBuilder(40);
myFormatter = IManager.Factory.getManager().getFormatterProvider()
.getFormatter(myBuffer, myProvider.getFormat());
initForValidation(getBinding());
myUnitSupport = getBinding().getArgument(Constants.ARG_UNIT_SUPPORT, IUnitBindingSupport.class, null);
if (myUnitSupport != null) {
myUnitSupport.addListener(this);
}
}
@Override
public void dispose() {
super.dispose();
if (myUnitSupport != null) {
myUnitSupport.removeListener(this);
}
}
@Override
public void unitsChanged() {
getBinding().updateUI();
getBinding().updateBinding();
}
/**
* Special version of init when the decorator is only used for validation.
*
* @param binding the binding
*/
public void initForValidation(IValueBinding binding) {
setBinding(binding);
calculateAdapter(binding);
final String range = getBinding().getArgument(Constants.ARG_RANGE, String.class, null);
myDeclaredInterval = new Interval();
if (range != null) {
myDeclaredInterval.parseRanges(range);
} else {
myDeclaredInterval.retrieveMinMaxLimits();
}
if (myDeclaredInterval.isMinOrMaxSet()) {
myNativeInterval = new Interval();
} else {
myNativeInterval = myDeclaredInterval;
}
}
/**
* Finds the adapter to use for the model type of this binding.
*
* @param binding the binding
*/
private void calculateAdapter(IValueBinding binding) {
final Class<?> modelType = binding.getModelType();
if (modelType == Byte.class || modelType == Byte.TYPE) {
myAdapter = BYTE_ADAPTER;
} else if (modelType == Short.class || modelType == Short.TYPE) {
myAdapter = SHORT_ADAPTER;
} else if (modelType == Integer.class || modelType == Integer.TYPE) {
myAdapter = INTEGER_ADAPTER;
} else if (modelType == Long.class || modelType == Long.TYPE) {
myAdapter = LONG_ADAPTER;
} else if (modelType == Float.class || modelType == Float.TYPE) {
myAdapter = FLOAT_ADAPTER;
} else if (modelType == Double.class || modelType == Double.TYPE) {
myAdapter = DOUBLE_ADAPTER;
} else if (modelType == BigDecimal.class) {
myAdapter = BIG_INTERGER_ADAPTER;
} else if (modelType == BigInteger.class) {
myAdapter = BIG_DECIMAL_ADAPTER;
}
if (myAdapter == null) {
binding.addErrorCondition("Cannot convert model type " + modelType.getName() + " to String");
return;
}
}
/**
* Returns the label for the binding.
*
* @return the label
*/
public String getLabel() {
return getBinding().getLabel();
}
@Override
protected Object convertModelToUI(Object fromObject) {
if (myUnitSupport != null) {
final double factor = getUnitFactor();
if (factor != 1.0) {
fromObject = myAdapter.scale(fromObject, factor, true);
}
}
final Class<?> uiType = getBinding().getUIType();
if (uiType == Integer.class || uiType == Integer.TYPE)
return fromObject;
else if (uiType == String.class) {
myBuffer.setLength(0);
myFormatter.format(fromObject);
return myBuffer.toString();
} else
return null;
}
private double getUnitFactor() {
if (myUnitSupport == null) return 1.0;
return myUnitSupport.getFactor(this);
}
@Override
protected Object convertUIToModel(Object fromObject) {
/*
* Micro Cache:
*
* Avoid converting the from object if identical to the last value.
*
* convertToBigDecimal can result in an exception so we delay setting the from object
*/
if (myLastFromObject != fromObject) {
myLastConvertedValue = convertToBigDecimal(fromObject);
if (myUnitSupport != null) {
final double factor = getUnitFactor();
if (factor != 1.0) {
try {
myLastConvertedValue = myLastConvertedValue.divide(new BigDecimal(factor), MATH_CONTEXT);
} catch (final ArithmeticException ex) {
LogUtils.error(this, myLastConvertedValue + "/" + factor + ": " + ex.getMessage(), ex);
throw ex;
}
}
}
myLastFromObject = fromObject;
}
/*
* - Check the limits
*/
final String m = myNativeInterval.checkRange(fromObject, myLastConvertedValue, "native");
if (m != null) {
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, m);
}
/*
* - Convert to the wanted model type
*/
return myAdapter.getConformantNumber(myLastConvertedValue);
}
@Override
public IValidator getUIToModelAfterConvertValidator() {
return new IValidator() {
@Override
public IStatus validate(Object value) {
/*
* - Check the limits
*/
final String m = checkRange(myLastFromObject, myLastConvertedValue);
if (m != null) return UIBindingsUtils.error(NUMBER_ERROR_CODE, m);
return Status.OK_STATUS;
}
};
}
/**
* Converts the specified object to a {@link BigDecimal} if possible.
*
* @param fromObject the object to convert
* @return the corresponding {@link BigDecimal} or <code>null</code>
*
* @throws CoreRuntimeException if the object is <code>null</code>
*/
private BigDecimal convertToBigDecimal(Object fromObject) {
if (myAdapter == null) return null;
BigDecimal d = null;
final Class<?> uiType = getBinding().getUIType();
if (uiType == Integer.class || uiType == Integer.TYPE) {
getBinding().assertTrue(fromObject instanceof Integer, "fromObject not an Integer");
d = new BigDecimal((Integer) fromObject);
} else if (uiType == String.class) {
getBinding().assertTrue(fromObject instanceof String, "fromObject not a String");
final String s = (String) fromObject;
/*
* - Parse the string
*/
if (s == null || s.length() == 0) {
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, "number missing");
}
final ParsePosition parsePosition = new ParsePosition(0);
final boolean formatUsesGroupings = myProvider.getFormat().indexOf(',') >= 0;
final NumberFormat format = formatUsesGroupings ? myAdapter.getGroupingParseFormat() : myAdapter
.getPlainParseFormat();
Number number = format.parse(s.toLowerCase(), parsePosition);
/*
* - Check the result
*
* If we have an error AND the wanted type is float, double or BigDecimal, then give it
* a second try with Double.parseDouble().
*/
if (parsePosition.getErrorIndex() != -1 || parsePosition.getIndex() != s.length()) {
boolean ok = false;
try {
if (myAdapter == FLOAT_ADAPTER || myAdapter == DOUBLE_ADAPTER || myAdapter == BIG_DECIMAL_ADAPTER) {
number = BigDecimal.valueOf(Double.parseDouble(s));
ok = true;
}
} catch (final NumberFormatException ex) { // $codepro.audit.disable
// emptyCatchClause
// Do nothing
}
if (!ok) {
final int errorPos = (parsePosition.getErrorIndex() != -1) ? parsePosition.getErrorIndex()
: parsePosition.getIndex();
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, MessageFormat.format(
"Illegal number: ''{1}'' at position {2}: ''{3}''", getLabel(), fromObject, errorPos + 1,
s.charAt(errorPos)));
}
}
/*
* - Handle the special values
*/
if (number instanceof Double) {
if (Double.isNaN(number.doubleValue())) return null;
if (number.doubleValue() == Double.NEGATIVE_INFINITY) {
d = getMin();
} else if (number.doubleValue() == Double.POSITIVE_INFINITY) {
d = getMax();
}
} else {
d = (BigDecimal) number;
}
}
return d;
}
/**
* The "declared" interval of the decorator based on the {@link Constants#ARG_RANGE}.
*/
private Interval myDeclaredInterval = null;
/**
* The "native" interval of the decorator based on the EMF field.
*/
private Interval myNativeInterval = null;
/**
* Checks the specified number against the range of this decorator.
*
* @param fromObject the original object
* @param number the number to test
* @return message if range is violated, otherwise <code>null</code>
*/
public String checkRange(Object fromObject, final BigDecimal number) {
return myDeclaredInterval.checkRange(fromObject, number, "declared");
}
/**
* Returns the minimum value of the decorator.
*
* @return the minimum value
*/
public BigDecimal getMin() {
return myDeclaredInterval.myMin;
}
/**
* Returns the maximum value of this decorator.
*
* @return the maximum value
*/
public BigDecimal getMax() {
return myDeclaredInterval.myMax;
}
/**
* Creates and returns a new tokenizer for the specified string.
*
* @param spec the string to parse
* @return the new tokenizer
*/
protected StreamTokenizer createTokenizer(String spec) {
final StreamTokenizer st = new StreamTokenizer(new StringReader(spec));
// st.commentChar('#');
st.lowerCaseMode(false);
st.parseNumbers();
st.quoteChar('"');
st.quoteChar('\'');
st.slashSlashComments(false);
st.slashStarComments(false);
st.ordinaryChar('.');
return st;
}
/**
* Sets up the specified format for use in {@link #convertUIToModel(Object)}.
*
* @param format the format to modify
* @param allowGroupings <code>true</code> if groupings are allowed
* @return a modified copy
*/
protected static DecimalFormat setupDecimalFormat(NumberFormat format, boolean allowGroupings) {
final DecimalFormat f = (DecimalFormat) format.clone();
f.setParseBigDecimal(true);
final DecimalFormatSymbols symbols = f.getDecimalFormatSymbols();
symbols.setExponentSeparator(symbols.getExponentSeparator().toLowerCase());
symbols.setNaN(symbols.getNaN().toLowerCase());
symbols.setInfinity(symbols.getInfinity().toLowerCase());
if (!allowGroupings) {
symbols.setGroupingSeparator('\0');
}
f.setDecimalFormatSymbols(symbols);
return f;
}
/**
* This interface is used to hide the differences between the {@link Number} sub-classes.
* <p>
* The objects of this class are immutable - does not change.
*/
private interface NumberAdapter {
/**
* Returns the format used to parse this type.
* <p>
* The parser must return a {@link BigDecimal} or {@link Double} as described in
* {@link DecimalFormat#parse(String, ParsePosition)} with
* {@link DecimalFormat#setParseBigDecimal(boolean) setParseBigDecimal(true)}.
*
* @return the format
*/
NumberFormat getPlainParseFormat();
/**
* Returns whether the number of this decorator is an integral number - e.g. one of
* {@link Byte} , {@link Short}, {@link Integer} or {@link Long} or their corresponding
* primitive types.
*
* @return <code>true</code> if it is an integral number and <code>false</code> if it is a
* real number
*/
boolean isIntegralNumber();
/**
* Scales and returns a new number of the correct type.
*
* @param fromObject the number to scale
* @param factor the scaling factor
* @param viewToUI whether to scale from view to UI (multiply by the factor) or UI to model
* (divide by the factor)
* @return the new number
*/
Object scale(Object fromObject, double factor, boolean viewToUI);
/**
* Returns the format used to parse this type with groupings.
* <p>
* See {@link #getPlainParseFormat()}.
*
* @return the format
*/
NumberFormat getGroupingParseFormat();
/**
* Returns the minimum value for the type. The value is the same type as TODO
*
* @return the minimum
*/
BigDecimal getMinimum();
/**
* Returns the minimum value for the type. The value is the same type as TODO
*
* @return the minimum
*/
BigDecimal getMaximum();
/**
* Returns a new number with the same value, but of the correct type. When this method is
* called, it is already checked that {@code source} is within the bounds of
* {@link #getMinimum()} and {@link #getMaximum()}.
*
* @param source the source number to be converted
* @return the resulting number
*/
Number getConformantNumber(BigDecimal source);
}
/**
* The adapter for {@link Byte}{@code .class} and {@link Byte}{@code .TYPE}.
*/
private static final NumberAdapter BYTE_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
private final BigDecimal min = new BigDecimal(Byte.MIN_VALUE);
@Override
public BigDecimal getMinimum() {
return min;
}
private final BigDecimal max = new BigDecimal(Byte.MAX_VALUE);
@Override
public BigDecimal getMaximum() {
return max;
}
@Override
public Number getConformantNumber(BigDecimal source) {
try {
return Byte.valueOf(source.byteValueExact());
} catch (final ArithmeticException ex) {
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, "Fraction not allowed");
}
return null;
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
return fromObject;
}
@Override
public boolean isIntegralNumber() {
return true;
};
};
/**
* The adapter for {@link Short}{@code .class} and {@link Short}{@code .TYPE}.
*/
private static final NumberAdapter SHORT_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
private final BigDecimal min = new BigDecimal(Short.MIN_VALUE);
@Override
public BigDecimal getMinimum() {
return min;
}
private final BigDecimal max = new BigDecimal(Short.MAX_VALUE);
@Override
public BigDecimal getMaximum() {
return max;
}
@Override
public Number getConformantNumber(BigDecimal source) {
try {
return Short.valueOf(source.shortValueExact());
} catch (final ArithmeticException ex) {
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, "Fraction not allowed");
}
return null;
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
return fromObject;
};
@Override
public boolean isIntegralNumber() {
return true;
};
};
/**
* The adapter for {@link Integer}{@code .class} and {@link Integer}{@code .TYPE}.
*/
private static final NumberAdapter INTEGER_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
private final BigDecimal min = new BigDecimal(Integer.MIN_VALUE);
@Override
public BigDecimal getMinimum() {
return min;
}
private final BigDecimal max = new BigDecimal(Integer.MAX_VALUE);
@Override
public BigDecimal getMaximum() {
return max;
}
@Override
public Number getConformantNumber(BigDecimal source) {
try {
return Integer.valueOf(source.intValueExact());
} catch (final ArithmeticException ex) {
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, "Fraction not allowed");
}
return null;
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
return fromObject;
};
@Override
public boolean isIntegralNumber() {
return true;
};
};
/**
* The adapter for {@link Long}{@code .class} and {@link Long}{@code .TYPE}.
*/
private static final NumberAdapter LONG_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getIntegerInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
private final BigDecimal min = new BigDecimal(Long.MIN_VALUE);
@Override
public BigDecimal getMinimum() {
return min;
}
private final BigDecimal max = new BigDecimal(Long.MAX_VALUE);
@Override
public BigDecimal getMaximum() {
return max;
}
@Override
public Number getConformantNumber(BigDecimal source) {
try {
return Long.valueOf(source.longValueExact());
} catch (final ArithmeticException ex) {
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, "Fraction not allowed");
}
return null;
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
return fromObject;
};
@Override
public boolean isIntegralNumber() {
return true;
};
};
/**
* The adapter for {@link Float}{@code .class} and {@link Float}{@code .TYPE}.
*/
private static final NumberAdapter FLOAT_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
private final BigDecimal min = new BigDecimal(-Float.MAX_VALUE);
@Override
public BigDecimal getMinimum() {
return min;
}
private final BigDecimal max = new BigDecimal(Float.MAX_VALUE);
@Override
public BigDecimal getMaximum() {
return max;
}
@Override
public Number getConformantNumber(BigDecimal source) {
return new Float(source.floatValue());
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
final Float i = (Float) fromObject;
if (viewToUI)
return i * factor;
else
return i / factor;
};
@Override
public boolean isIntegralNumber() {
return false;
};
};
/**
* The adapter for {@link Double}{@code .class} and {@link Double}{@code .TYPE}.
*/
private static final NumberAdapter DOUBLE_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
private final BigDecimal min = new BigDecimal(-Double.MAX_VALUE);
@Override
public BigDecimal getMinimum() {
return min;
}
private final BigDecimal max = new BigDecimal(Double.MAX_VALUE);
@Override
public BigDecimal getMaximum() {
return max;
}
@Override
public Number getConformantNumber(BigDecimal source) {
return new Double(source.doubleValue());
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
final Double i = (Double) fromObject;
if (viewToUI)
return i * factor;
else
return i / factor;
};
@Override
public boolean isIntegralNumber() {
return false;
};
};
/**
* The adapter for {@link BigInteger}{@code .class} and {@link BigInteger}{@code .TYPE}.
*/
private static final NumberAdapter BIG_INTERGER_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
@Override
public BigDecimal getMinimum() {
return null;
}
@Override
public BigDecimal getMaximum() {
return null;
}
@Override
public Number getConformantNumber(BigDecimal source) {
try {
return source.toBigIntegerExact();
} catch (final ArithmeticException ex) {
UIBindingsUtils.throwError(true, NUMBER_ERROR_CODE, "Fraction not allowed");
}
return null;
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
return fromObject;
};
@Override
public boolean isIntegralNumber() {
return true;
};
};
/**
* The adapter for {@link BigDecimal}{@code .class} and {@link BigDecimal}{@code .TYPE}.
*/
private static final NumberAdapter BIG_DECIMAL_ADAPTER = new NumberAdapter() {
private final DecimalFormat myPlainFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), false);
private final DecimalFormat myGroupingFormat = setupDecimalFormat(NumberFormat.getNumberInstance(), true);
@Override
public NumberFormat getPlainParseFormat() {
return myPlainFormat;
}
@Override
public NumberFormat getGroupingParseFormat() {
return myGroupingFormat;
};
@Override
public BigDecimal getMinimum() {
return null;
}
@Override
public BigDecimal getMaximum() {
return null;
}
@Override
public Number getConformantNumber(BigDecimal source) {
return source;
}
@Override
public Object scale(Object fromObject, double factor, boolean viewToUI) {
final BigDecimal i = (BigDecimal) fromObject;
if (viewToUI)
return i.multiply(new BigDecimal(factor, MATH_CONTEXT), MATH_CONTEXT);
else
return i.divide(new BigDecimal(factor, MATH_CONTEXT), MATH_CONTEXT);
};
@Override
public boolean isIntegralNumber() {
return false;
};
};
}