package de.invesdwin.util.math.decimal; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.function.Function; import javax.annotation.concurrent.Immutable; import de.invesdwin.util.lang.Strings; import de.invesdwin.util.math.decimal.internal.DecimalAggregate; import de.invesdwin.util.math.decimal.internal.DummyDecimalAggregate; import de.invesdwin.util.math.decimal.internal.impl.ADecimalImpl; import de.invesdwin.util.math.decimal.internal.impl.DoubleDecimalImplFactory; import de.invesdwin.util.math.decimal.internal.impl.IDecimalImplFactory; @Immutable public class Decimal extends ADecimal<Decimal> { public static final DecimalFormatSymbols DEFAULT_DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols .getInstance(Locale.ENGLISH); public static final String DEFAULT_DECIMAL_FORMAT = newDefaultDecimalFormat(2); public static final String INTEGER_DECIMAL_FORMAT = "#,##0"; public static final String MONEY_DECIMAL_FORMAT = newMoneyDecimalFormat(2); public static final Decimal MINUS_THREE; public static final Decimal MINUS_TWO; public static final Decimal MINUS_ONE; public static final Decimal ZERO; public static final Decimal ONE; public static final Decimal TWO; public static final Decimal THREE; public static final Decimal FOUR; public static final Decimal FIVE; public static final Decimal SIX; public static final Decimal TEN; public static final Decimal FIFTY; public static final Decimal SEVENTYFIVE; public static final Decimal ONE_HUNDRED; public static final Decimal PI; /** * Due to implementation simplifications of equals, hashCode and compareTo only one implementations cannot be mixed * with each other. */ private static final IDecimalImplFactory<?> DECIMAL_IMPL_FACTORY; static { /* * double is the fastest implementation, thus defaulting to that. The other ones are still there for comparison * purposes. */ DECIMAL_IMPL_FACTORY = new DoubleDecimalImplFactory(); MINUS_THREE = new Decimal("-3"); MINUS_TWO = new Decimal("-2"); MINUS_ONE = new Decimal("-1"); ZERO = new Decimal("0"); ONE = new Decimal("1"); TWO = new Decimal("2"); THREE = new Decimal("3"); FOUR = new Decimal("4"); FIVE = new Decimal("5"); SIX = new Decimal("6"); TEN = new Decimal("10"); FIFTY = new Decimal("50"); SEVENTYFIVE = new Decimal("75"); ONE_HUNDRED = new Decimal("100"); PI = new Decimal(Math.PI); } private final ADecimalImpl<?, ?> impl; public Decimal(final Number value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final Double value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final Float value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final Long value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final Integer value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final Short value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final Byte value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final String value) { this(DECIMAL_IMPL_FACTORY.valueOf(value)); } public Decimal(final ADecimalImpl<?, ?> impl) { if (impl instanceof ScaledDecimalDelegateImpl) { this.impl = ((ScaledDecimalDelegateImpl) impl).getDelegate(); } else { this.impl = impl; } } @Override public ADecimalImpl getImpl() { return impl; } @Override protected Decimal newValueCopy(final ADecimalImpl value) { return new Decimal(value); } @Override public Decimal zero() { return ZERO; } public static Decimal nullToZero(final Decimal value) { if (value == null) { return ZERO; } else { return value; } } @Override protected Decimal getGenericThis() { return this; } @Override public Decimal fromDefaultValue(final Decimal value) { return value; } @Override public Decimal getDefaultValue() { return this; } public static IDecimalAggregate<Decimal> valueOf(final Decimal... values) { return valueOf(Arrays.asList(values)); } public static IDecimalAggregate<Decimal> valueOf(final List<? extends Decimal> values) { if (values == null || values.size() == 0) { return DummyDecimalAggregate.getInstance(); } else { return new DecimalAggregate<Decimal>(values, Decimal.ZERO); } } public static <T> List<Decimal> extractValues(final Function<T, Decimal> getter, final List<T> objects) { final List<Decimal> decimals = new ArrayList<Decimal>(); for (final T obj : objects) { final Decimal decimal = getter.apply(obj); decimals.add(decimal); } return decimals; } public static <T> List<Decimal> extractValues(final Function<T, Decimal> getter, final T... objects) { return extractValues(getter, Arrays.asList(objects)); } /** * Use default values of the scaled decimal instead! */ @Deprecated public static Decimal valueOf(final AScaledDecimal<?, ?> value) { throw new UnsupportedOperationException(); } public static Decimal valueOf(final String value) { if (value == null) { return null; } else { return new Decimal(value); } } public static Decimal valueOf(final Double value) { if (value == null) { return null; } else { return new Decimal(value); } } public static Decimal valueOf(final Number value) { if (value == null) { return null; } else if (value instanceof Decimal) { return (Decimal) value; } else { if (value instanceof AScaledDecimal) { throw new IllegalArgumentException("value [" + value + "] should not be an instance of " + AScaledDecimal.class.getSimpleName() + ": " + value.getClass().getSimpleName()); } return new Decimal(value); } } public static Decimal fromDefaultValue(final AScaledDecimal<?, ?> scaledDecimal) { if (scaledDecimal != null) { return scaledDecimal.getDefaultValue(); } else { return null; } } @Override public String toFormattedString() { return toFormattedString(DEFAULT_DECIMAL_FORMAT); } @Override public String toFormattedString(final String format) { final DecimalFormat dc = newDecimalFormatInstance(format); final String str = dc.format(this); if (str.startsWith("-0") && str.matches("-0([\\.,](0)*)?")) { return Strings.removeStart(str, "-"); } else { return str; } } public static String newDefaultDecimalFormat(final int decimalDigits) { String format = "#,##0"; if (decimalDigits > 0) { format += "." + Strings.repeat("#", decimalDigits); } return format; } public static String newMoneyDecimalFormat(final int decimalDigits) { String format = "#,##0"; if (decimalDigits > 0) { format += "." + Strings.repeat("0", decimalDigits); } return format; } public static DecimalFormat newDecimalFormatInstance(final String format) { return newDecimalFormatInstance(format, Decimal.DEFAULT_DECIMAL_FORMAT_SYMBOLS); } public static DecimalFormat newDecimalFormatInstance(final String format, final Locale locale) { return newDecimalFormatInstance(format, DecimalFormatSymbols.getInstance(locale)); } public static DecimalFormat newDecimalFormatInstance(final String format, final DecimalFormatSymbols symbols) { final DecimalFormat formatter = new DecimalFormat(format, symbols); formatter.setRoundingMode(ADecimal.DEFAULT_ROUNDING_MODE); return formatter; } public static Decimal[] toObject(final double[] array) { final Decimal[] decimalArray = new Decimal[array.length]; for (int i = 0; i < array.length; i++) { decimalArray[i] = new Decimal(array[i]); } return decimalArray; } public static Decimal[] toObject(final List<Double> value) { final Decimal[] decimalArray = new Decimal[value.size()]; for (int i = 0; i < value.size(); i++) { decimalArray[i] = new Decimal(value.get(i)); } return decimalArray; } }