001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2018, Jean-Marie Dautelle, Werner Keil, Otavio Santana. 004 * 005 * All rights reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without modification, 008 * are permitted provided that the following conditions are met: 009 * 010 * 1. Redistributions of source code must retain the above copyright notice, 011 * this list of conditions and the following disclaimer. 012 * 013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 014 * and the following disclaimer in the documentation and/or other materials provided with the distribution. 015 * 016 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products 017 * derived from this software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package tech.units.indriya.quantity; 031 032import static tech.units.indriya.unit.Units.*; 033 034import java.lang.reflect.InvocationHandler; 035import java.lang.reflect.Method; 036import java.lang.reflect.Proxy; 037import java.util.HashMap; 038import java.util.Map; 039import java.util.concurrent.ConcurrentHashMap; 040import java.util.logging.Level; 041import java.util.logging.Logger; 042 043import javax.measure.Quantity; 044import javax.measure.Unit; 045import javax.measure.quantity.*; 046import javax.measure.spi.QuantityFactory; 047 048import tech.units.indriya.AbstractQuantity; 049import tech.units.indriya.AbstractUnit; 050 051/** 052 * A factory producing simple quantities instances (tuples {@link Number}/{@link Unit}). This implementation of {@link QuantityFactory} uses the 053 * DynamicProxy features of Java reflection API.<br> 054 * <br> 055 * 056 * For example:<br> 057 * <code> 058 * Quantity<Mass> m = ProxyQuantityFactory.getInstance(Mass.class).create(23.0, KILOGRAM); // 23.0 kg<br/> 059 * Quantity<Time> t = ProxyQuantityFactory.getInstance(Time.class).create(124, MILLI(SECOND)); // 124 ms 060 * </code> 061 * 062 * @param <Q> 063 * The type of the quantity. 064 * 065 * @author <a href="mailto:martin.desruisseaux@geomatys.com">Martin Desruisseaux</a> 066 * @author <a href="mailto:units@catmedia.us">Werner Keil</a> 067 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 068 * @version 1.1, $Date: 2018-03-04 $ 069 * @since 1.0 070 */ 071public abstract class ProxyQuantityFactory<Q extends Quantity<Q>> implements QuantityFactory<Q> { 072 073 /** 074 * Holds the current instances. 075 */ 076 @SuppressWarnings("rawtypes") 077 private static final Map<Class, ProxyQuantityFactory> INSTANCES = new ConcurrentHashMap<>(); 078 079 private static final Logger logger = Logger.getLogger(ProxyQuantityFactory.class.getName()); 080 081 private static final Level LOG_LEVEL = Level.FINE; 082 083 /** 084 * Returns the default instance for the specified quantity type. 085 * 086 * @param <Q> 087 * The type of the quantity 088 * @param type 089 * the quantity type 090 * @return the quantity factory for the specified type 091 */ 092 @SuppressWarnings("unchecked") 093 public static <Q extends Quantity<Q>> ProxyQuantityFactory<Q> getInstance(final Class<Q> type) { 094 095 logger.log(LOG_LEVEL, "Type: " + type + ": " + type.isInterface()); 096 ProxyQuantityFactory<Q> factory; 097 if (!type.isInterface()) { 098 if (type != null && type.getInterfaces() != null & type.getInterfaces().length > 0) { 099 logger.log(LOG_LEVEL, "Type0: " + type.getInterfaces()[0]); 100 Class<?> type2 = type.getInterfaces()[0]; 101 102 factory = INSTANCES.get(type2); 103 if (factory != null) 104 return factory; 105 if (!AbstractQuantity.class.isAssignableFrom(type2)) 106 // This exception is not documented because it should never happen if the 107 // user don't try to trick the Java generic types system with unsafe cast. 108 throw new ClassCastException(); 109 factory = new Default<>((Class<Q>) type2); 110 INSTANCES.put(type2, factory); 111 } else { 112 factory = INSTANCES.get(type); 113 if (factory != null) 114 return factory; 115 if (!AbstractQuantity.class.isAssignableFrom(type)) 116 // This exception is not documented because it should never happen if the 117 // user don't try to trick the Java generic types system with unsafe cast. 118 throw new ClassCastException(); 119 factory = new Default<>(type); 120 INSTANCES.put(type, factory); 121 } 122 } else { 123 factory = INSTANCES.get(type); 124 if (factory != null) 125 return factory; 126 if (!Quantity.class.isAssignableFrom(type)) 127 // This exception is not documented because it should never happen if the 128 // user don't try to trick the Java generic types system with unsafe cast. 129 throw new ClassCastException(); 130 factory = new Default<>(type); 131 INSTANCES.put(type, factory); 132 } 133 return factory; 134 } 135 136 /** 137 * Overrides the default implementation of the factory for the specified quantity type. 138 * 139 * @param <Q> 140 * The type of the quantity 141 * @param type 142 * the quantity type 143 * @param factory 144 * the quantity factory 145 */ 146 protected static <Q extends Quantity<Q>> void setInstance(final Class<Q> type, ProxyQuantityFactory<Q> factory) { 147 if (!AbstractQuantity.class.isAssignableFrom(type)) 148 // This exception is not documented because it should never happen if the 149 // user don't try to trick the Java generic types system with unsafe cast. 150 throw new ClassCastException(); 151 INSTANCES.put(type, factory); 152 } 153 154 /** 155 * Returns the metric unit for quantities produced by this factory or <code>null</code> if unknown. 156 * 157 * @return the metric units for this factory quantities. 158 */ 159 public abstract Unit<Q> getSystemUnit(); 160 161 /** 162 * The default factory implementation. This factory uses reflection for providing a default implementation for every {@link AbstractMeasurement} 163 * sub-types. 164 * 165 * @param <Q> 166 * The type of the quantity 167 */ 168 private static final class Default<Q extends Quantity<Q>> extends ProxyQuantityFactory<Q> { 169 170 /** 171 * The type of the quantities created by this factory. 172 */ 173 private final Class<Q> type; 174 175 /** 176 * The metric unit for quantities created by this factory. 177 */ 178 private final Unit<Q> metricUnit; 179 180 /** 181 * Creates a new factory for quantities of the given type. 182 * 183 * @param type 184 * The type of the quantities created by this factory. 185 */ 186 @SuppressWarnings("unchecked") 187 Default(final Class<Q> type) { 188 this.type = type; 189 metricUnit = CLASS_TO_METRIC_UNIT.get(type); 190 } 191 192 @SuppressWarnings("rawtypes") 193 static final HashMap<Class, Unit> CLASS_TO_METRIC_UNIT = new HashMap<>(); 194 static { 195 CLASS_TO_METRIC_UNIT.put(Dimensionless.class, AbstractUnit.ONE); 196 CLASS_TO_METRIC_UNIT.put(ElectricCurrent.class, AMPERE); 197 CLASS_TO_METRIC_UNIT.put(LuminousIntensity.class, CANDELA); 198 CLASS_TO_METRIC_UNIT.put(Temperature.class, KELVIN); 199 CLASS_TO_METRIC_UNIT.put(Mass.class, KILOGRAM); 200 CLASS_TO_METRIC_UNIT.put(Length.class, METRE); 201 CLASS_TO_METRIC_UNIT.put(AmountOfSubstance.class, MOLE); 202 CLASS_TO_METRIC_UNIT.put(Time.class, SECOND); 203 // CLASS_TO_METRIC_UNIT.put(MagnetomotiveForce.class, AMPERE_TURN); 204 CLASS_TO_METRIC_UNIT.put(Angle.class, RADIAN); 205 CLASS_TO_METRIC_UNIT.put(SolidAngle.class, STERADIAN); 206 // CLASS_TO_METRIC_UNIT.put(Information.class, BIT); 207 CLASS_TO_METRIC_UNIT.put(Frequency.class, HERTZ); 208 CLASS_TO_METRIC_UNIT.put(Force.class, NEWTON); 209 CLASS_TO_METRIC_UNIT.put(Pressure.class, PASCAL); 210 CLASS_TO_METRIC_UNIT.put(Energy.class, JOULE); 211 CLASS_TO_METRIC_UNIT.put(Power.class, WATT); 212 CLASS_TO_METRIC_UNIT.put(ElectricCharge.class, COULOMB); 213 CLASS_TO_METRIC_UNIT.put(ElectricPotential.class, VOLT); 214 CLASS_TO_METRIC_UNIT.put(ElectricCapacitance.class, FARAD); 215 CLASS_TO_METRIC_UNIT.put(ElectricResistance.class, OHM); 216 CLASS_TO_METRIC_UNIT.put(ElectricConductance.class, SIEMENS); 217 CLASS_TO_METRIC_UNIT.put(MagneticFlux.class, WEBER); 218 CLASS_TO_METRIC_UNIT.put(MagneticFluxDensity.class, TESLA); 219 CLASS_TO_METRIC_UNIT.put(ElectricInductance.class, HENRY); 220 CLASS_TO_METRIC_UNIT.put(LuminousFlux.class, LUMEN); 221 CLASS_TO_METRIC_UNIT.put(Illuminance.class, LUX); 222 CLASS_TO_METRIC_UNIT.put(Radioactivity.class, BECQUEREL); 223 CLASS_TO_METRIC_UNIT.put(RadiationDoseAbsorbed.class, GRAY); 224 CLASS_TO_METRIC_UNIT.put(RadiationDoseEffective.class, SIEVERT); 225 CLASS_TO_METRIC_UNIT.put(CatalyticActivity.class, KATAL); 226 CLASS_TO_METRIC_UNIT.put(Speed.class, METRE_PER_SECOND); 227 CLASS_TO_METRIC_UNIT.put(Acceleration.class, METRE_PER_SQUARE_SECOND); 228 CLASS_TO_METRIC_UNIT.put(Area.class, SQUARE_METRE); 229 CLASS_TO_METRIC_UNIT.put(Volume.class, CUBIC_METRE); 230 } 231 232 @Override 233 public Unit<Q> getSystemUnit() { 234 return metricUnit; 235 } 236 237 @SuppressWarnings("unchecked") 238 @Override 239 public Quantity<Q> create(Number value, Unit<Q> unit) { 240 // System.out.println("Type: " + type); 241 return (Q) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, new GenericHandler<>(value, unit)); 242 } 243 } 244 245 /** 246 * The method invocation handler for implementation backed by any kind of {@link Number}. This is a fall back used when no specialized handler is 247 * available for the number type. 248 */ 249 private static final class GenericHandler<Q extends Quantity<Q>> implements InvocationHandler { 250 final Unit<Q> unit; 251 final Number value; 252 253 GenericHandler(final Number value, final Unit<Q> unit) { 254 this.unit = unit; 255 this.value = value; 256 } 257 258 @SuppressWarnings("unchecked") 259 @Override 260 public Object invoke(final Object proxy, final Method method, final Object[] args) { 261 final String name = method.getName(); 262 switch (name) { 263 case "doubleValue": { // Most frequent. 264 final Unit<Q> toUnit = (Unit<Q>) args[0]; 265 if ((toUnit == unit) || (toUnit.equals(unit))) 266 return value.doubleValue(); // Returns value directly. 267 return unit.getConverterTo(toUnit).convert(value.doubleValue()); 268 } 269 case "longValue": { 270 final Unit<Q> toUnit = (Unit<Q>) args[0]; 271 if ((toUnit == unit) || (toUnit.equals(unit))) 272 return value.longValue(); // Returns value directly. 273 double doubleValue = unit.getConverterTo(toUnit).convert(value.doubleValue()); 274 if ((doubleValue < Long.MIN_VALUE) || (doubleValue > Long.MAX_VALUE)) 275 throw new ArithmeticException("Overflow: " + doubleValue + " cannot be represented as a long"); 276 return (long) doubleValue; 277 } 278 case "getValue": 279 return value; 280 case "getUnit": 281 return unit; 282 case "toString": 283 return String.valueOf(value) + ' ' + unit; 284 case "hashCode": 285 return value.hashCode() * 31 + unit.hashCode(); 286 case "equals": { 287 final Object obj = args[0]; 288 if (!(obj instanceof AbstractQuantity)) 289 return false; 290 final AbstractQuantity<Q> that = (AbstractQuantity<Q>) obj; 291 return unit.isCompatible((AbstractUnit<?>) that.getUnit()) && value.doubleValue() == (that).doubleValue(unit); 292 } 293 case "compareTo": { 294 final AbstractQuantity<Q> that = (AbstractQuantity<Q>) args[0]; 295 return Double.compare(value.doubleValue(), that.doubleValue(unit)); 296 } 297 default: 298 throw new UnsupportedOperationException(name); 299 } 300 } 301 } 302}