package de.invesdwin.util.math.decimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import de.invesdwin.util.collections.Lists;
import de.invesdwin.util.collections.iterable.ICloseableIterable;
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.scaled.IDecimalScale;
@SuppressWarnings({ "rawtypes", "serial" })
@ThreadSafe
public abstract class AScaledDecimal<T extends AScaledDecimal<T, S>, S extends IDecimalScale<T, S>> extends ADecimal<T>
implements Cloneable {
protected final S scale;
@GuardedBy("none for performance")
private Decimal scaledValue;
@GuardedBy("none for performance")
private ScaledDecimalDelegateImpl impl;
@GuardedBy("none for performance")
private Decimal defaultValue;
private final S defaultScale;
protected AScaledDecimal(final Decimal value, final S scale, final S defaultScale) {
this.defaultScale = defaultScale;
if (defaultScale == null) {
throw new NullPointerException("defaultScale should not be null");
}
validateScale(defaultScale);
this.scaledValue = Decimal.nullToZero(value);
validateScale(scale);
this.scale = scale;
}
protected void validateScale(final S scale) {}
protected abstract T newValueCopy(Decimal value, S scale);
@Override
public ScaledDecimalDelegateImpl getImpl() {
if (impl == null) {
impl = new ScaledDecimalDelegateImpl(this, getScaledValue().getImpl());
}
return impl;
}
@Override
protected final T newValueCopy(final ADecimalImpl value) {
return newValueCopy(new Decimal(value), scale);
}
@SuppressWarnings("unchecked")
@Override
public final T fromDefaultValue(final Decimal value) {
try {
final AScaledDecimal<T, S> clone = (AScaledDecimal<T, S>) clone();
clone.scaledValue = null;
clone.impl = null;
clone.defaultValue = value;
clone.isPositive = null;
clone.isZero = null;
return (T) clone;
} catch (final CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public final Decimal getDefaultValue() {
if (defaultValue == null) {
defaultValue = innerGetValue(defaultScale);
}
return defaultValue;
}
public final Decimal getValue(final S scale) {
if (defaultScale.equals(scale)) {
return getDefaultValue();
}
return innerGetValue(scale);
}
private Decimal innerGetValue(final S scale) {
if (scale == this.scale) {
return getScaledValue();
} else {
validateScale(scale);
if (scaledValue != null) {
return scale.convertValue(getGenericThis(), new Decimal(getImpl().getDelegate()), this.scale);
} else {
return scale.convertValue(getGenericThis(), defaultValue, defaultScale);
}
}
}
private Decimal getScaledValue() {
if (scaledValue == null) {
scaledValue = scale.convertValue(getGenericThis(), defaultValue, this.defaultScale);
}
return scaledValue;
}
@Override
public int hashCode() {
return getClass().hashCode() + getScale().hashCode() + getDefaultValue().hashCode();
}
@Override
public boolean equals(final Object obj) {
if (obj != null && getGenericThis().getClass().isAssignableFrom(obj.getClass())) {
final AScaledDecimal castedObj = (AScaledDecimal) obj;
return castedObj.getDefaultValue().equals(this.getDefaultValue());
} else {
return false;
}
}
public final S getScale() {
return scale;
}
public final S getDefaultScale() {
return defaultScale;
}
@Override
public final String toString() {
return toString(scale);
}
public final String toString(final boolean withSymbol) {
return toString(scale, withSymbol);
}
public final String toString(final S scale) {
return toString(scale, true);
}
public final String toString(final S scale, final boolean withSymbol) {
return toStringBuilder().withScale(scale).withSymbol(withSymbol).toString();
}
public ScaledDecimalToStringBuilder<T, S> toStringBuilder() {
return new ScaledDecimalToStringBuilder<T, S>(getGenericThis());
}
@Override
public String toFormattedString() {
return toFormattedString(Decimal.DEFAULT_DECIMAL_FORMAT);
}
@Override
public String toFormattedString(final String format) {
return toStringBuilder().toString(format);
}
public T asScale(final S scale) {
validateScale(scale);
return newValueCopy(getValue(scale), scale);
}
@SuppressWarnings("unchecked")
@Override
public T subtract(final ADecimal<T> subtrahend) {
if (subtrahend == null) {
return getGenericThis();
}
final ADecimal<?> defaultScaledSubtrahend = maybeGetDefaultScaledNumber(subtrahend);
final ADecimalImpl newDefault = getDefaultValue().getImpl().subtract(defaultScaledSubtrahend);
return fromDefaultValue(new Decimal(newDefault));
}
@SuppressWarnings("unchecked")
@Override
public T add(final ADecimal<T> augend) {
if (augend == null) {
return getGenericThis();
}
final ADecimal<?> defaultScaledAugend = maybeGetDefaultScaledNumber(augend);
final ADecimalImpl newDefault = getDefaultValue().getImpl().add(defaultScaledAugend);
return fromDefaultValue(new Decimal(newDefault));
}
@SuppressWarnings("unchecked")
@Override
public T multiply(final ADecimal<T> multiplicant) {
if (isZero()) {
return getGenericThis();
} else if (multiplicant == null) {
return multiply(0);
} else {
final ADecimal<?> defaultScaledMultiplicant = maybeGetDefaultScaledNumber(multiplicant);
final ADecimalImpl newDefault = getDefaultValue().getImpl().multiply(defaultScaledMultiplicant);
return fromDefaultValue(new Decimal(newDefault));
}
}
@SuppressWarnings("unchecked")
@Override
public T divide(final ADecimal<T> divisor) {
if (isZero()) {
//prevent NaN
return getGenericThis();
} else if (divisor == null || divisor.isZero()) {
return divide(0);
} else {
final ADecimal<?> defaultScaledDivisor = maybeGetDefaultScaledNumber(divisor);
final ADecimalImpl newDefault = getDefaultValue().getImpl().divide(defaultScaledDivisor);
return fromDefaultValue(new Decimal(newDefault));
}
}
@SuppressWarnings("unchecked")
@Override
public T remainder(final ADecimal<T> divisor) {
if (isZero()) {
return getGenericThis();
} else if (divisor == null || divisor.isZero()) {
return remainder(0);
} else {
final ADecimal<?> defaultScaledDivisor = maybeGetDefaultScaledNumber(divisor);
final ADecimalImpl newDefault = getDefaultValue().getImpl().remainder(defaultScaledDivisor);
return fromDefaultValue(new Decimal(newDefault));
}
}
private ADecimal<?> maybeGetDefaultScaledNumber(final ADecimal<?> number) {
if (number instanceof AScaledDecimal) {
final AScaledDecimal<?, ?> scaledNumber = (AScaledDecimal<?, ?>) number;
return scaledNumber.getDefaultValue();
} else {
return number;
}
}
public static <D extends ADecimal<D>> IDecimalAggregate<D> valueOf(final D... values) {
return valueOf(Arrays.asList(values));
}
public static <D extends ADecimal<D>> IDecimalAggregate<D> valueOf(final ICloseableIterable<? extends D> values) {
return valueOf(Lists.toList(values));
}
public static <D extends ADecimal<D>> IDecimalAggregate<D> valueOf(final Iterable<? extends D> values) {
return valueOf(Lists.toList(values));
}
public static <D extends ADecimal<D>> IDecimalAggregate<D> valueOf(final List<? extends D> values) {
if (values == null || values.size() == 0) {
return DummyDecimalAggregate.getInstance();
} else {
return new DecimalAggregate<D>(values, null);
}
}
public static <T, D extends ADecimal<D>> List<D> extractValues(final Function<T, D> getter, final List<T> objects) {
final List<D> decimals = new ArrayList<D>();
for (final T obj : objects) {
final D decimal = getter.apply(obj);
decimals.add(decimal);
}
return decimals;
}
public static <T, D extends ADecimal<D>> List<D> extractValues(final Function<T, D> getter, final T... objects) {
return extractValues(getter, Arrays.asList(objects));
}
}