/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.sis.measure; import javax.measure.Unit; import javax.measure.Quantity; import javax.measure.UnitConverter; import java.lang.reflect.Proxy; import java.lang.reflect.Method; import java.lang.reflect.InvocationHandler; import org.apache.sis.util.ArgumentChecks; /** * A quantity related to a scalar by an arbitrary (not necessarily linear) conversion. * For example a temperature in Celsius degrees is related to a temperature in Kelvin * by applying an offset. * * <p>The {@link Scalar} parent class is restricted to cases where the relationship with system unit * is a scale factor. This {@code DerivedScalar} subclass allow the relationship to be more generic. * It is a design similar to {@link org.opengis.referencing.crs.DerivedCRS}</p> * * @author Martin Desruisseaux (Geomatys) * @version 1.0 * * @param <Q> the concrete subtype. * * @since 1.0 * @module */ abstract class DerivedScalar<Q extends Quantity<Q>> extends Scalar<Q> { /** * For cross-version compatibility. */ private static final long serialVersionUID = 3729159568163676568L; /** * The value specified by the user, in unit of {@link #derivedUnit}. * Could be computed form super-class value, but nevertheless stored * for avoiding rounding errors. */ private final double derivedValue; /** * The unit of measurement specified by the user. The relationship between this unit * and its system unit (stored in super-class) is something more complex than a scale * factor, otherwise we would not need this {@code DerivedScalar}. */ private final Unit<Q> derivedUnit; /** * Converter from the system unit to the unit of this quantity. */ private final UnitConverter fromSystem; /** * Creates a new scalar for the given value. * * @param toSystem converter from {@code unit} to the system unit. */ DerivedScalar(final double value, final Unit<Q> unit, final Unit<Q> systemUnit, final UnitConverter toSystem) { super(toSystem.convert(value), systemUnit); derivedValue = value; derivedUnit = unit; fromSystem = toSystem.inverse(); } /** * Creates a new scalar resulting from an arithmetic operation performed on the given scalar. * The arithmetic operation result is in the same unit than the original scalar. * * @param value the arithmetic result in system unit. */ DerivedScalar(final DerivedScalar<Q> origin, final double value) { super(value, origin.getSystemUnit()); derivedUnit = origin.derivedUnit; fromSystem = origin.fromSystem; derivedValue = fromSystem.convert(value); } /** * Creates a new quantity of same type than this quantity but with a different value. * The unit of measurement shall be the same than the system unit of this quantity. * Implementation in subclasses should be like below: * * {@preformat java * assert newUnit == getSystemUnit() : newUnit; * return new MyDerivedScalar(this, newValue); * } */ @Override abstract Quantity<Q> create(double newValue, Unit<Q> newUnit); /** * Returns the system unit of measurement. */ final Unit<Q> getSystemUnit() { return super.getUnit(); } /** * Returns the unit of measurement specified at construction time. */ @Override public final Unit<Q> getUnit() { return derivedUnit; } /** * Returns the value specified at construction time. */ @Override public final double doubleValue() { return derivedValue; } /** * Returns the value casted to a single-precision floating point number. */ @Override public final float floatValue() { return (float) derivedValue; } /** * Returns the value rounded to nearest integer. {@link Double#NaN} are casted to 0 and values out of * {@code long} range are clamped to minimal or maximal representable numbers of {@code long} type. */ @Override public final long longValue() { return Math.round(derivedValue); } /** * Converts this quantity to another unit of measurement. */ @Override public final Quantity<Q> to(final Unit<Q> newUnit) { if (newUnit == derivedUnit) { return this; } ArgumentChecks.ensureNonNull("unit", newUnit); // "unit" is the parameter name used in public API. /* * Do not invoke 'this.create(double, Unit)' because the contract in this subclass * restricts the above method to cases where the given unit is the system unit. * Furthermore we need to let 'Quantities.create(…)' re-evaluate whether we need * a 'DerivedScalar' instance or whether 'Scalar' would be sufficient. */ return Quantities.create(derivedUnit.getConverterTo(newUnit).convert(derivedValue), newUnit); } /** * A temperature in Celsius degrees or any other units having an offset compared to Kelvin. */ static final class TemperatureMeasurement extends DerivedScalar<javax.measure.quantity.Temperature> implements javax.measure.quantity.Temperature { private static final long serialVersionUID = -3901877967613695897L; /** Constructor for {@link Quantities} factory only. */ TemperatureMeasurement(double value, Unit<javax.measure.quantity.Temperature> unit, Unit<javax.measure.quantity.Temperature> systemUnit, UnitConverter toSystem) { super(value, unit, systemUnit, toSystem); } /** Constructor for {@code create(…)} implementation only. */ private TemperatureMeasurement(TemperatureMeasurement origin, double value) { super(origin, value); } @Override Quantity<javax.measure.quantity.Temperature> create(double newValue, Unit<javax.measure.quantity.Temperature> newUnit) { assert newUnit == getSystemUnit() : newUnit; return new TemperatureMeasurement(this, newValue); } } /** * Fallback used when no {@link DerivedScalar} implementation is available for a given quantity type. * This is basically a copy of {@link ScalarFallback} implementation adapted to {@code DerivedScalar}. */ @SuppressWarnings("serial") static final class Fallback<Q extends Quantity<Q>> extends DerivedScalar<Q> implements InvocationHandler { /** * The type implemented by proxy instances. * * @see ScalarFallback#type */ private final Class<Q> type; /** * Constructor for {@link Quantities} factory only. */ private Fallback(final double value, final Unit<Q> unit, final Unit<Q> systemUnit, final UnitConverter toSystem, final Class<Q> type) { super(value, unit, systemUnit, toSystem); this.type = type; } /** * Constructor for {@code create(…)} implementation only. */ private Fallback(final Fallback<Q> origin, final double value) { super(origin, value); type = origin.type; } /** * Creates a new quantity of the same type than this quantity but a different value and/or unit. * * @see ScalarFallback#create(double, Unit) */ @Override @SuppressWarnings("unchecked") Quantity<Q> create(final double newValue, final Unit<Q> newUnit) { assert newUnit == getSystemUnit() : newUnit; final Fallback<Q> quantity = new Fallback<>(this, newValue); return (Q) Proxy.newProxyInstance(Scalar.class.getClassLoader(), new Class<?>[] {type}, quantity); } /** * Creates a new {@link Fallback} instance implementing the given quantity type. * * @see ScalarFallback#factory(double, Unit, Class) */ @SuppressWarnings("unchecked") static <Q extends Quantity<Q>> Q factory(final double value, final Unit<Q> unit, final Unit<Q> systemUnit, final UnitConverter toSystem, final Class<Q> type) { final Fallback<Q> quantity = new Fallback<>(value, unit, systemUnit, toSystem, type); return (Q) Proxy.newProxyInstance(Scalar.class.getClassLoader(), new Class<?>[] {type}, quantity); } /** * Invoked when a method of the {@link Quantity} interface is invoked. * * @see ScalarFallback#invoke(Object, Method, Object[]) */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws ReflectiveOperationException { return method.invoke(this, args); } } }