blob: 151fbb33f6b2579d9c6adcb3ab35268002c7a3b9 [file] [log] [blame]
/**
* Copyright (c) 2005, 2015, Werner Keil 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:
* Werner Keil - initial API and implementation
*/
package org.eclipse.uomo.business.money;
import static org.eclipse.uomo.business.money.MonetaryUnits.ISO_NAMESPACE;
import java.math.BigInteger;
import java.util.Map;
import org.eclipse.uomo.business.internal.CurrencyUnit;
import org.eclipse.uomo.business.internal.Localizable;
import org.eclipse.uomo.business.types.IMoney;
import org.eclipse.uomo.core.IName;
import javax.measure.Quantity;
import javax.measure.UnconvertibleException;
import javax.measure.Dimension;
import javax.measure.IncommensurableException;
import javax.measure.Unit;
import javax.measure.UnitConverter;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ULocale;
import tech.units.indriya.AbstractConverter;
import tech.units.indriya.AbstractUnit;
import tech.units.indriya.function.AddConverter;
import tech.units.indriya.function.MultiplyConverter;
import tech.units.indriya.function.RationalConverter;
import tech.units.indriya.unit.AlternateUnit;
import tech.units.indriya.unit.ProductUnit;
import tech.units.indriya.unit.TransformedUnit;
/**
* @author <a href="mailto:uomo@catmedia.us">Werner Keil</a>
* @version 0.3, $Date: 2020-03-16
* @param <Q> the monetary quantity
*
*/
public class MoneyUnit<Q extends IMoney> extends Currency implements
Unit<IMoney>, IName, CurrencyUnit, Localizable {
// TODO use JSR 354
/**
*
*/
private static final long serialVersionUID = 8524573975644908457L;
/** namespace for this currency. */
private final String namespace;
/** valid from, or {@code null}. */
private final Long validFrom;
/** valid until, or {@code null}. */
private final Long validUntil;
/** true, if legal tender. */
private final boolean legalTender;
/** true, if it is a virtual currency. */
private final boolean virtual;
protected MoneyUnit(String theCode, String namespace,
Long validFrom, Long validUntil,
boolean legalTender, boolean virtual) {
super(theCode);
this.namespace = namespace;
this.validFrom = validFrom;
this.validUntil = validUntil;
this.legalTender = legalTender;
this.virtual = virtual;
}
protected MoneyUnit(String theISOCode) {
this(theISOCode, ISO_NAMESPACE, null, null, true, false);
}
/**
* The Canadian Dollar currency unit.
*/
public static final MoneyUnit<IMoney> CAD = new MoneyUnit<IMoney>("CAD"); //$NON-NLS-1$
/**
* The China Yan currency.
*/
public static final MoneyUnit<IMoney> CNY = new MoneyUnit<IMoney>("CNY"); //$NON-NLS-1$
/**
* The Euro currency.
*/
@SuppressWarnings("rawtypes")
public static final MoneyUnit EUR = new MoneyUnit<IMoney>("EUR"); //$NON-NLS-1$
/**
* The British Pound currency.
*/
public static final MoneyUnit<IMoney> GBP = new MoneyUnit<IMoney>("GBP"); //$NON-NLS-1$
/**
* The Japanese Yen currency.
*/
public static final MoneyUnit<IMoney> JPY = new MoneyUnit<IMoney>("JPY"); //$NON-NLS-1$
/**
* The Korean Republic Won currency.
*/
public static final MoneyUnit<IMoney> KRW = new MoneyUnit<IMoney>("KRW"); //$NON-NLS-1$
/**
* The Taiwanese dollar currency.
*/
@SuppressWarnings("rawtypes")
public static final MoneyUnit TWD = new MoneyUnit<IMoney>("TWD"); //$NON-NLS-1$
/**
* Holds the dimensionless unit <code>ONE</code>.
*/
public static final ProductUnit<IMoney> ONE = new ProductUnit<IMoney>();
/**
* The United State dollar currency.
*/
@SuppressWarnings("rawtypes")
public static final MoneyUnit USD = new MoneyUnit<IMoney>("USD"); //$NON-NLS-1$
/**
* Returns the result of adding an offset to this unit. The returned unit is
* convertible with all units that are convertible with this unit.
*
* @param offset
* the offset added (expressed in this unit, e.g.
* <code>CELSIUS = KELVIN.add(273.15)</code>).
* @return <code>this.transform(new AddConverter(offset))</code>
*/
public final Unit<IMoney> add(double offset) {
if (offset == 0)
return this;
return transform(new AddConverter(offset));
}
/**
* Returns a metric unit equivalent to this unscaled metric unit but used in
* expressions to distinguish between quantities of a different nature but
* of the same dimensions.
*
* <p>
* Examples of alternate units:[code] Unit<Angle> RADIAN =
* ONE.alternate("rad"); Unit<Force> NEWTON =
* METRE.times(KILOGRAM).divide(SECOND.pow(2)).alternate("N");
* Unit<Pressure> PASCAL = NEWTON.divide(METRE.pow(2)).alternate("Pa");
* [/code]
* </p>
*
* @param <Q>
* the type of the quantity measured by the new alternate unit.
*
* @param symbol
* the new symbol for the alternate unit.
* @return the alternate unit.
* @throws UnsupportedOperationException
* if this unit is not an unscaled metric unit.
* @throws IllegalArgumentException
* if the specified symbol is already associated to a different
* unit.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public final Unit<IMoney> alternate(String symbol) {
return new AlternateUnit(this, symbol);
}
public <T extends Quantity<T>> Unit<T> asType(Class<T> type) {
// TODO Auto-generated method stub
return null;
}
/**
* Returns the result of dividing this unit by an approximate divisor.
*
* @param divisor
* the approximate divisor.
* @return <code>this.transform(new MultiplyConverter(1.0 / divisor))</code>
*/
public final Unit<IMoney> divide(double divisor) {
if (divisor == 1)
return this;
return transform(new MultiplyConverter(1.0 / divisor));
}
/**
* Returns the result of dividing this unit by an exact divisor.
*
* @param divisor
* the exact divisor. (e.g.
* <code>QUART = GALLON_LIQUID_US.divide(4)</code>).
* @return <code>this.transform(new RationalConverter(1 , divisor))</code>
*/
public final Unit<?> divide(long divisor) {
if (divisor == 1)
return this;
return (Unit<?>) transform(new RationalConverter(BigInteger.ONE,
BigInteger.valueOf(divisor)));
}
public UnitConverter getConverterTo(Unit<IMoney> that)
throws UnconvertibleException {
return new MoneyConverter(this, that, 1);
}
public UnitConverter getConverterToAny(Unit<?> that)
throws IncommensurableException, UnconvertibleException {
// TODO Auto-generated method stub
return null;
}
public Dimension getDimension() {
return IMoney.DIMENSION;
}
public Map<Unit<?>, Integer> getProductUnits() {
// TODO Auto-generated method stub
return null;
}
private boolean isRationalFactor() {
// if (!(this instanceof TransformedUnit<?>))
// return false;
// TransformedUnit<Q> tu = (TransformedUnit<Q>) this;
// return tu.getParentUnit().equals(ONE) &&
// (tu.getConverterTo(tu.toMetric()) instanceof RationalConverter);
return true;
}
/**
* Returns the inverse of this unit.
*
* @return <code>1 / this</code>
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public final Unit<?> inverse() {
if (this.equals(ONE))
return this;
if (this.isRationalFactor())
return this.transform(this.getConverterTo((Unit) ONE).inverse());
return ProductUnit.ofQuotient(ONE, this);
}
public boolean isCompatible(Unit<?> that) {
return (this == that)
|| this.toMetric().equals(that.getSystemUnit())
|| (!"".equals(this.getDimension().toString()) && this.getDimension().equals(that.getDimension())); //$NON-NLS-1$
}
/**
* Returns the result of multiplying this unit by an exact factor.
*
* @param factor
* the exact scale factor (e.g.
* <code>KILOMETRE = METRE.multiply(1000)</code>).
* @return <code>this.transform(new RationalConverter(factor, 1))</code>
*/
final Unit<IMoney> multiply(long factor) {
if (factor == 1)
return this;
return transform(new RationalConverter(BigInteger.valueOf(factor),
BigInteger.ONE));
}
/**
* Returns the result of multiplying this unit by a an approximate factor.
*
* @param factor
* the approximate factor (e.g.
* <code>ELECTRON_MASS = KILOGRAM.multiply(9.10938188e-31)</code>
* ).
* @return <code>this.transform(new MultiplyConverter(factor))</code>
*/
public final Unit<IMoney> multiply(double factor) {
if (factor == 1)
return this;
return transform(new MultiplyConverter(factor));
}
/**
* Returns the product of this unit with the one specified.
*
* @param that
* the unit multiplicand.
* @return <code>this * that</code>
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public final Unit<?> multiply(Unit<?> that) {
if (this.equals(ONE))
return that;
if (that.equals(ONE))
return this;
if (this.isRationalFactor())
return that.transform(this.getConverterTo(ONE));
if (((MoneyUnit<?>) that).isRationalFactor())
return this.transform(that.getConverterTo((Unit) ONE));
return ProductUnit.ofProduct(this, (AbstractUnit<?>) that);
}
/**
* Returns a unit equals to this unit raised to an exponent.
*
* @param n
* the exponent.
* @return the result of raising this unit to the exponent.
*/
public final Unit<?> pow(int n) {
if (n > 0)
return this.multiply(this.pow(n - 1));
else if (n == 0)
return ONE;
else
// n < 0
return ONE.divide(this.pow(-n));
}
/**
* Returns a unit equals to the given root of this unit.
*
* @param n
* the root's order.
* @return the result of taking the given root of this unit.
* @throws ArithmeticException
* if <code>n == 0</code> or if this operation would result in
* an unit with a fractional exponent.
*/
public final Unit<?> root(int n) {
if (n > 0)
return ProductUnit.ofRoot(this, n);
else if (n == 0)
throw new ArithmeticException("Root's order of zero"); //$NON-NLS-1$
else
// n < 0
return ONE.divide(this.root(-n));
}
protected Unit<IMoney> toMetric() {
return this;
}
/**
* Returns the unit derived from this unit using the specified converter.
* The converter does not need to be linear. For example:[code]
* Unit<Dimensionless> DECIBEL = Unit.ONE.transform( new
* LogConverter(10).inverse().concatenate( new RationalConverter(1, 10)));
* [/code]
*
* @param operation
* the converter from the transformed unit to this unit.
* @return the unit after the specified transformation.
*/
@SuppressWarnings("unchecked")
public final Unit<IMoney> transform(UnitConverter operation) {
if (this instanceof Unit<?>) {
Unit<IMoney> tf = this;
Unit<?> parent = (Unit<?>) ((TransformedUnit<?>) tf)
.getParentUnit();
UnitConverter toParent = tf.getConverterTo((Unit<IMoney>) parent);
if (toParent == null)
return (Unit<IMoney>) parent;
UnitConverter toParentConcat = toParent.concatenate(operation);
if (toParentConcat == AbstractConverter.IDENTITY)
return (Unit<IMoney>) parent;
return new TransformedUnit<IMoney>((Unit<IMoney>) parent,
(AbstractConverter) toParentConcat);
}
if (operation == AbstractConverter.IDENTITY)
return this;
return new TransformedUnit<IMoney>(this, (AbstractConverter) operation);
}
/**
* Returns the quotient of this unit with the one specified.
*
* @param that
* the unit divisor.
* @return <code>this / that</code>
*/
public final Unit<?> divide(Unit<?> that) {
return (Unit<?>) this.multiply(that.inverse());
}
public String getName() {
return getName(ULocale.getDefault(), LONG_NAME, new boolean[] { false });
}
public Unit<IMoney> getSystemUnit() {
return toMetric();
}
/**
* Get the namespace of this {@link CurrencyUnit}, default 'ISO-4217'.
*/
public String getNamespace() {
return namespace;
}
/*
* (non-Javadoc)
*
* @see CurrencyUnit#isLegalTender()
*/
public boolean isLegalTender() {
return legalTender;
}
/*
* (non-Javadoc)
*
* @see CurrencyUnit#isVirtual()
*/
public boolean isVirtual() {
return virtual;
}
/*
* (non-Javadoc)
*
* @see CurrencyUnit#getValidFrom()
*/
public Long getValidFrom() {
return validFrom;
}
/*
* (non-Javadoc)
*
* @see CurrencyUnit#getValidUntil()
*/
public Long getValidUntil() {
return validUntil;
}
public int compareTo(CurrencyUnit currency) {
int compare = getNamespace().compareTo(currency.getNamespace());
if (compare == 0) {
compare = getCurrencyCode().compareTo(currency.getCurrencyCode());
}
if (compare == 0) {
if (validFrom == null && currency.getValidFrom() != null) {
compare = -1;
} else if (validFrom != null && currency.getValidFrom() == null) {
compare = 1;
} else if (validFrom != null) {
compare = validFrom.compareTo(currency.getValidFrom());
}
}
if (compare == 0) {
if (validUntil == null && currency.getValidUntil() != null) {
compare = -1;
} else if (validUntil != null && currency.getValidUntil() == null) {
compare = 1;
} else if (validUntil != null) {
compare = validUntil.compareTo(currency.getValidUntil());
}
}
return compare;
}
/**
* Access a new instance based on the ISO currency code. The code must
* return a {@link Currency} when passed to
* {@link Currency#getInstance(String)}.
*
* @param currencyCode
* the ISO currency code, not null.
* @return the corresponding {@link MonetaryCurrency} instance.
*/
public static MoneyUnit of(String currencyCode) {
return new MoneyUnit(currencyCode);
}
@Override
public Map<? extends Unit<?>, Integer> getBaseUnits() {
// TODO Auto-generated method stub
return null;
}
@Override
public Unit<IMoney> shift(double offset) {
// TODO Auto-generated method stub
return null;
}
// public String getDisplayName(Locale locale) {
// return getName(ULocale.forLocale(locale), LONG_NAME, new boolean[1]);
// }
//
// public int getNumericCode() {
// return -1;
// }
}