blob: 0df8485ad6c1e0b5efed3b2ef8cbd7cfcaf9a993 [file] [log] [blame]
/*
* 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.1
*
* @param <Q> the concrete subtype.
*
* @since 1.0
* @module
*/
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
Quantity<Q> create(double newValue, Unit<Q> newUnit) {
assert newUnit == getSystemUnit() : newUnit;
return new DerivedScalar<>(this, newValue);
}
/**
* 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);
}
}
}