blob: 3df66c29421f85ea7f6886321c75d69f054f84e0 [file] [log] [blame]
/*
* Copyright (c) 2005, 2017, 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.units.impl.ext;
import static org.eclipse.uomo.units.impl.system.Units.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.measure.Quantity;
import javax.measure.Unit;
import javax.measure.quantity.*;
import javax.measure.spi.QuantityFactory;
import org.eclipse.uomo.units.AbstractQuantity;
import org.eclipse.uomo.units.AbstractUnit;
/**
* A factory producing simple quantities instances (tuples {@link Number}/{@link Unit}).
*
* For example:<br/>
* <code>
* Quantity<Mass> m = ProxyQuantityFactory.getInstance(Mass.class).create(23.0, KILOGRAM); // 23.0 kg<br/>
* Quantity<Time> t = ProxyQuantityFactory.getInstance(Time.class).create(124, MILLI(SECOND)); // 124 ms
* </code>
*
* @param <Q>
* The type of the quantity.
*
* @author <a href="mailto:martin.desruisseaux@geomatys.com">Martin Desruisseaux</a>
* @author <a href="mailto:units@catmedia.us">Werner Keil</a>
* @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
* @version 1.0.1, $Date: 2017-02-12 $
*/
public abstract class ProxyQuantityFactory<Q extends Quantity<Q>> implements QuantityFactory<Q> {
/**
* Holds the current instances.
*/
@SuppressWarnings("rawtypes")
private static final Map<Class, ProxyQuantityFactory> INSTANCES = new ConcurrentHashMap<>();
private static final Logger logger = Logger.getLogger(ProxyQuantityFactory.class.getName());
private static final Level LOG_LEVEL = Level.FINE;
/**
* Returns the default instance for the specified quantity type.
*
* @param <Q>
* The type of the quantity
* @param type
* the quantity type
* @return the quantity factory for the specified type
*/
@SuppressWarnings("unchecked")
public static <Q extends Quantity<Q>> ProxyQuantityFactory<Q> getInstance(final Class<Q> type) {
logger.log(LOG_LEVEL, "Type: " + type + ": " + type.isInterface());
ProxyQuantityFactory<Q> factory;
if (!type.isInterface()) {
if (type != null && type.getInterfaces() != null & type.getInterfaces().length > 0) {
logger.log(LOG_LEVEL, "Type0: " + type.getInterfaces()[0]);
Class<?> type2 = type.getInterfaces()[0];
factory = INSTANCES.get(type2);
if (factory != null)
return factory;
if (!AbstractQuantity.class.isAssignableFrom(type2))
// This exception is not documented because it should never happen if the
// user don't try to trick the Java generic types system with unsafe cast.
throw new ClassCastException();
factory = new Default<>((Class<Q>) type2);
INSTANCES.put(type2, factory);
} else {
factory = INSTANCES.get(type);
if (factory != null)
return factory;
if (!AbstractQuantity.class.isAssignableFrom(type))
// This exception is not documented because it should never happen if the
// user don't try to trick the Java generic types system with unsafe cast.
throw new ClassCastException();
factory = new Default<>(type);
INSTANCES.put(type, factory);
}
} else {
factory = INSTANCES.get(type);
if (factory != null)
return factory;
if (!Quantity.class.isAssignableFrom(type))
// This exception is not documented because it should never happen if the
// user don't try to trick the Java generic types system with unsafe cast.
throw new ClassCastException();
factory = new Default<>(type);
INSTANCES.put(type, factory);
}
return factory;
}
/**
* Overrides the default implementation of the factory for the specified quantity type.
*
* @param <Q>
* The type of the quantity
* @param type
* the quantity type
* @param factory
* the quantity factory
*/
protected static <Q extends Quantity<Q>> void setInstance(final Class<Q> type, ProxyQuantityFactory<Q> factory) {
if (!AbstractQuantity.class.isAssignableFrom(type))
// This exception is not documented because it should never happen if the
// user don't try to trick the Java generic types system with unsafe cast.
throw new ClassCastException();
INSTANCES.put(type, factory);
}
/**
* Returns the metric unit for quantities produced by this factory or <code>null</code> if unknown.
*
* @return the metric units for this factory quantities.
*/
public abstract Unit<Q> getSystemUnit();
/**
* The default factory implementation. This factory uses reflection for providing a default implementation for every {@link AbstractMeasurement}
* sub-types.
*
* @param <Q>
* The type of the quantity
*/
private static final class Default<Q extends Quantity<Q>> extends ProxyQuantityFactory<Q> {
/**
* The type of the quantities created by this factory.
*/
private final Class<Q> type;
/**
* The metric unit for quantities created by this factory.
*/
private final Unit<Q> metricUnit;
/**
* Creates a new factory for quantities of the given type.
*
* @param type
* The type of the quantities created by this factory.
*/
@SuppressWarnings("unchecked")
Default(final Class<Q> type) {
this.type = type;
metricUnit = CLASS_TO_METRIC_UNIT.get(type);
}
@SuppressWarnings("rawtypes")
static final HashMap<Class, Unit> CLASS_TO_METRIC_UNIT = new HashMap<>();
static {
CLASS_TO_METRIC_UNIT.put(Dimensionless.class, AbstractUnit.ONE);
CLASS_TO_METRIC_UNIT.put(ElectricCurrent.class, AMPERE);
CLASS_TO_METRIC_UNIT.put(LuminousIntensity.class, CANDELA);
CLASS_TO_METRIC_UNIT.put(Temperature.class, KELVIN);
CLASS_TO_METRIC_UNIT.put(Mass.class, KILOGRAM);
CLASS_TO_METRIC_UNIT.put(Length.class, METRE);
CLASS_TO_METRIC_UNIT.put(AmountOfSubstance.class, MOLE);
CLASS_TO_METRIC_UNIT.put(Time.class, SECOND);
// CLASS_TO_METRIC_UNIT.put(MagnetomotiveForce.class, AMPERE_TURN);
CLASS_TO_METRIC_UNIT.put(Angle.class, RADIAN);
CLASS_TO_METRIC_UNIT.put(SolidAngle.class, STERADIAN);
// CLASS_TO_METRIC_UNIT.put(Information.class, BIT);
CLASS_TO_METRIC_UNIT.put(Frequency.class, HERTZ);
CLASS_TO_METRIC_UNIT.put(Force.class, NEWTON);
CLASS_TO_METRIC_UNIT.put(Pressure.class, PASCAL);
CLASS_TO_METRIC_UNIT.put(Energy.class, JOULE);
CLASS_TO_METRIC_UNIT.put(Power.class, WATT);
CLASS_TO_METRIC_UNIT.put(ElectricCharge.class, COULOMB);
CLASS_TO_METRIC_UNIT.put(ElectricPotential.class, VOLT);
CLASS_TO_METRIC_UNIT.put(ElectricCapacitance.class, FARAD);
CLASS_TO_METRIC_UNIT.put(ElectricResistance.class, OHM);
CLASS_TO_METRIC_UNIT.put(ElectricConductance.class, SIEMENS);
CLASS_TO_METRIC_UNIT.put(MagneticFlux.class, WEBER);
CLASS_TO_METRIC_UNIT.put(MagneticFluxDensity.class, TESLA);
CLASS_TO_METRIC_UNIT.put(ElectricInductance.class, HENRY);
CLASS_TO_METRIC_UNIT.put(LuminousFlux.class, LUMEN);
CLASS_TO_METRIC_UNIT.put(Illuminance.class, LUX);
CLASS_TO_METRIC_UNIT.put(Radioactivity.class, BECQUEREL);
CLASS_TO_METRIC_UNIT.put(RadiationDoseAbsorbed.class, GRAY);
CLASS_TO_METRIC_UNIT.put(RadiationDoseEffective.class, SIEVERT);
CLASS_TO_METRIC_UNIT.put(CatalyticActivity.class, KATAL);
CLASS_TO_METRIC_UNIT.put(Speed.class, METRE_PER_SECOND);
CLASS_TO_METRIC_UNIT.put(Acceleration.class, METRE_PER_SQUARE_SECOND);
CLASS_TO_METRIC_UNIT.put(Area.class, SQUARE_METRE);
CLASS_TO_METRIC_UNIT.put(Volume.class, CUBIC_METRE);
}
@Override
public Unit<Q> getSystemUnit() {
return metricUnit;
}
@SuppressWarnings("unchecked")
@Override
public Quantity<Q> create(Number value, Unit<Q> unit) {
// System.out.println("Type: " + type);
return (Q) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, new GenericHandler<>(value, unit));
}
}
/**
* The method invocation handler for implementation backed by any kind of {@link Number}. This is a fall back used when no specialized handler is
* available for the number type.
*/
private static final class GenericHandler<Q extends Quantity<Q>> implements InvocationHandler {
final Unit<Q> unit;
final Number value;
GenericHandler(final Number value, final Unit<Q> unit) {
this.unit = unit;
this.value = value;
}
@SuppressWarnings("unchecked")
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) {
final String name = method.getName();
switch (name) {
case "doubleValue": { // Most frequent.
final Unit<Q> toUnit = (Unit<Q>) args[0];
if ((toUnit == unit) || (toUnit.equals(unit)))
return value.doubleValue(); // Returns value directly.
return unit.getConverterTo(toUnit).convert(value.doubleValue());
}
case "longValue": {
final Unit<Q> toUnit = (Unit<Q>) args[0];
if ((toUnit == unit) || (toUnit.equals(unit)))
return value.longValue(); // Returns value directly.
double doubleValue = unit.getConverterTo(toUnit).convert(value.doubleValue());
if ((doubleValue < Long.MIN_VALUE) || (doubleValue > Long.MAX_VALUE))
throw new ArithmeticException("Overflow: " + doubleValue + " cannot be represented as a long");
return (long) doubleValue;
}
case "getValue":
return value;
case "getUnit":
return unit;
case "toString":
return String.valueOf(value) + ' ' + unit;
case "hashCode":
return value.hashCode() * 31 + unit.hashCode();
case "equals": {
final Object obj = args[0];
if (!(obj instanceof AbstractQuantity))
return false;
final AbstractQuantity<Q> that = (AbstractQuantity<Q>) obj;
return unit.isCompatible((AbstractUnit<?>) that.getUnit()) && value.doubleValue() == (that).doubleValue(unit);
}
case "compareTo": {
final AbstractQuantity<Q> that = (AbstractQuantity<Q>) args[0];
return Double.compare(value.doubleValue(), that.doubleValue(unit));
}
default:
throw new UnsupportedOperationException(name);
}
}
}
}