JSR 363 Unit of Measurement API - a First Glance
During the last Hackergarten in Zurich I had the chance to look at the current GitHub Repository of JAR 363. Hereby I focused on the API only. This JSR is quite young, nevertheless there is already a code base available, which was originally forked from https://code.google.com/p/unitsofmeasure/ . My objective hereby was relatively simple: look at the main abstractions and get a gut feeling on it, so here it is:
Main Concepts
One main artifact is the Quantity interface, which is defined as follows:public interface Quantity<Q extends Quantity<Q>>
extends Measurement<Q, Number> {
Quantity<?> divide(Quantity<?> that);
}
Quantity<?> divide(Quantity<?> that);
}
The according JavaDoc describes this interface withRepresents a quantitative properties or attributes of thing. Mass, time, distance, heat, and angular separation are among the familiar examples of quantitative properties.
As we have seen this interface hereby extends Measurement:
public interface Measurement<Q extends Quantity<Q>, V>
extends UnitSupplier<Q>,
ValueSupplier<V>,
ConversionOperator<Measurement<Q, V>, Unit<Q>> {
Measurement<Q, V> add(Measurement<Q, V> that);
Measurement<Q, V> substract(Measurement<Q, V> that);
ConversionOperator<Measurement<Q, V>, Unit<Q>> {
Measurement<Q, V> add(Measurement<Q, V> that);
Measurement<Q, V> substract(Measurement<Q, V> that);
Measurement<?, V> multiply(Measurement<?, V> that);
Measurement<Q, V> multiply(V that);
Measurement<Q, V> divide(V that);
Measurement<Q, V> inverse();
Measurement<Q, V> inverse();
}
Whereas Supplier are common concepts also known from other APIs, Additionally ConversionOperator, Measurement and Unit:must be explained:// equivalent to @FunctionalInterface public interface ConversionOperator <T, U> { T to(U unit); } public interface Unit<Q extends Quantity<Q>> extends UnitTransformer<Q>, Nameable { String getSymbol(); Dimension getDimension(); Unit<Q> getSystemUnit(); Map<? extends Unit<?>, Integer> getProductUnits(); boolean isCompatible(Unit<?> that); <T extends Quantity<T>> Unit<T> asType(Class<T> type) throws ClassCastException; UnitConverter getConverterTo(Unit<Q> that) throws UnconvertibleException; UnitConverter getConverterToAny(Unit<?> that) throws IncommensurableException, UnconvertibleException; Unit<Q> alternate(String symbol); Unit<Q> shift(double offset); Unit<Q> multiply(double factor); Unit<?> multiply(Unit<?> that); Unit<Q> divide(double divisor); Unit<?> divide(Unit<?> that); Unit<?> inverse(); Unit<?> root(int n); Unit<?> pow(int n); }
But we still have not everything covered, there is also a Dimension, modelling the algorithmic relations between units:
public interface Dimension {
Dimension multiply(Dimension that);
Dimension divide(Dimension that);
Dimension pow(int n);
Dimension root(int n);
Map<? extends Dimension, Integer> getProductDimensions();
}
Summarizing we have the following main artifacts involved:
- A Quantity, which describes the flavor or type of measurement, e.g. Length, Weight or Volume.
- A Unit, which defines a concrete unit of a specific measurement, e.g. Nanometer, Tons, Gallons..
- A concrete Measurement, which can be seen as a concrete value measured of a Unit, e.g. 10 Meters, 3 tons or 4 Gallons. Measurements also provide simple arithmetics for adding, subtracting, division and multiplication with other measures of compatible units.
- Dimension defines the numeric relationships between a set of related units.
My Feedback
Basically defining an API for all measures and units available is a tough thing. Also there was already a lot of work done, and I am looking forward how this JSR will evolve. Nevertheless my first impression is that the API mixes up a few concerns that I think should be separated:
- I would expect to have such general and defining concepts like quantities and units being separated from more concrete aspects like measurements and dimensions.
- Another element that was present on the former measurement API was the SystemOfUnits concept. or some kind of accessor, which acts as an entry point. I think the API requires some entry point, e.g. in form of a singleton to make it complete. Basically a programmer should be able to program against the API without having to know any details on the implementations.
- I would like the quantity to be modeled as the more general but basically independent concept and let Unit refer to its corresponding Quantity. This would resolve the circular dependency between Quantity and Unit.
- Furthermore I would really ensure that the Quantity and Unit types are always evaluable at runtime (be aware of type erasure).
- For me it is also arguable if we really have enough benefits of Nameable. If the idea was to provide functional support for getName(), maybe it makes sense, but I would try to reduce the number of artifacts needed to the minimum. Just start small and if you really have to add something add it later.
- I do not like the idea of adding arithmetic operations on units, since I concern units as well as quantities as the metadata level, similar to currencies in the money world. I would expect them to be on measurements, which for me would be the same concept as monetary amount.
- FInally I think the naming of system unit and product units is confusing. I would prefer more something like a main or leading unit, and derived units.(or somebody can tell me, that the semantics is different here, I am not sure, if I understood these concepts fully here...).
public interface Quantity { String getName(); boolean isCompatible(Unit<?> that); // needed here ? }
public Unit<Q extends Quantity>{
Class<Q> getQuantity();
Unit<Q> getLeadingUnit();
Collection<Unit<Q>> getDerivedUnits();
boolean isCompatible(Unit<Q> that);
}
The management of quantities and units, including the mappings of units, quantities I would handle as a separate concern, e.g. something similar like
public final class Quantities { public static Collection<Quantity> getQuantities(); public static Collection<String> getQuantityNames(); public static Quantitiy getQuantity(String name); public static <Q extends Quantity> Q getQuantity( Class<Q> type); public static boolean isCompatible(Quantity quantity, Unit<?> that); }
Compared to what we have above this is much more simpler and comprehensive. Additionally there are also more advantages:public final class Units{ public static <Q extends Quantity> Collection<Unit<Q>> getUnits(Quantity); public static <Q extends Quantity> Collection<Unit<Q>> getLeadingUnits(Q quantity); public static <Q extends Quantity> Collection<Unit<Q>> getDerivedUnits(Unit<Q> unit); public static boolean isCompatible(Quantity quantity, Unit<?> that); public static <Q extends Quantity> boolean isDerived(Unit<Q> mainUnit, Unit<Q> derivedUnit); public static <Q extends Quantity> boolean isLeadingUnit(Unit<Q> unit); public static <Q extends Quantity> boolean isLeadingUnit(Unit<Q> unit); public static SystemOfUnits getSystemOfUnits(String name); public static SystemOfUnits getSystemOfUnits(Locale country); public static Collection<SystemOfUnits> getSystemOfUnits( Unit<?> unit); public static Collection<SystemOfUnits> getSystemOfUnits( Unit<?> unit); public static Collection<String> getSystemOfUnitNames(); public static SystemOfUnits getDefaultSystemOfUnits(); }public final class Dimensions{ ... }
- All these singleton can be backed up by spis. With this it should be easily possible to support partial implementations and overrides. E.g. when an additional unit type is defined, e.g. for measuring black materia in super-light speed mode (e.g. "WARPS") it would be possible to implement the parts required to support this additional unit.
- Basically such a structure could allow also additional functionality such as contextual behaviour in a multi-tenancy/ee context.
public interface SystemOfUnits{ <Q extends Quantity> Collection<Unit<Q>> getLeadingUnits(Quantity quantity); <Q extends Quantity> Unit<Q> getUnit(String symbol); ... }Now let us focus again on the next artifact: Measurement. Here
- I would reduce the arithmetic methods to the absolute minimum. Instead of having operations on it like shift, alternate I would prefer an extension mechanism, similar to MonetaryQuery or MonetaryOperator in JSR 354.
- I would heavily recommend to separate the concern of conversion. Basically this might also be doable based on the extension mechanism above, or it can be modeled as a separate API, but also provided by a separate accessor singleton.
- Finally I would like to see, if we really need alternate values than decimal numbers to represent the values of measurements. Even if there are rare cases, where measures are not modeled as decimal numbers, I would see, if it is possible to map them nevertheless somehow to decimal numbers in a feasible way. If, I hope so, most of the measurements can be modeled in a unified way, I would tend to take this as the leading use case. For speciayou can still have an additional complex measurement model. This would IMO make the API simpler for the majority of use cases, which I think is better than trying to include also edge cases that pollute the API at the end (making more difficult to use).
public interface Measurement<Q extends Quantity, U extends Unit<Q>> extends UnitSupplier<U>, ValueSupplier<Number>{ Measurement<Q, U> add(Measurement<Q, ?> that); Measurement<Q, U> subtract(Measurement<Q, ?> that); Measurement<Q, U> multiply(Measurement<Q, ?> that); Measurement<Q, U> multiply(double that); Measurement<Q, U> multiply(long that); Measurement<Q, U> multiply(Number that); Measurement<Q, U> divide(double that); Measurement<Q, U> divide(long that); Measurement<Q, U> divide(Number that); Measurement<Q, U> inverse(); Measurement<Q, U> pow(int n); Measurement<Q, U> with(MeasurementOperator op); <T> T query(MeasurementQuery<T, Q> query); }, whereas
public interface MeasurementOperator<Q extends Quantity, U extends Unit<Q>>{ <T extends Measurement<Q, U>> T apply(T unit); } public interface MeasurementQuery<Q extends Quantity, U extends Unit<Q>, R>{ R query(U unit); }
Also I would definitively recommend handling formatting and conversion as separate concerns, but I will stop here for now. I think the JSR's Expert Group has enough input for discussion and I hope, they will catch up at least some of my concerns, so we get a simple, comprehensive, easy to use, but nevertheless powerful measurement API.
Of course, as always, any comments are welcome!