unitsofmeasurement / unit-api

Units of Measurement API
http://unitsofmeasurement.github.io/unit-api/
Other
180 stars 42 forks source link

How to resolve a Quantity instance for a Number and Unit #201

Open msqr opened 4 years ago

msqr commented 4 years ago

Hello, I am not finding how to resolve a Quantity<?> instance if I am starting with a Number and a Unit<?> instance, using only the javax.measure API. The trouble here is that I don't know the Quantity class at runtime, so I can't call javax.measure.spi.ServiceProvider.getQuantityFactory(Class<Q> quantity) to get the appropriate QuantityFactory on which I could call javax.measure.spi.QuantityFactory.create(Number value, Unit<Q> unit).

I don't know there is a very easy way using Indriya: tech.units.indriya.quantity.Quantities.getQuantity(Number value, Unit<Q> unit) but I am trying to understand how to achieve the same thing with only the javax.measure API. Could you point me in the right direction?

keilw commented 4 years ago

Matt, Thanks a lot for your question. You are scratching an older itch I'm afraid and the Java language itself has no real solution here at runtime, see a recent analysis: https://stackify.com/jvm-generics-type-erasure/ None of the solutions work really well without either passing the type in every case (instead of a class this is also what ICU4J does) or being restricted to a special condition like inline code or static methods where the generic type may sometimes be available via Reflection.

All in all, that is a question for future Java releases, Project Valhalla (I did some very early experiments with it here: https://github.com/unitsofmeasurement/uom-demos/tree/master/console/valhalla) may help, but it is not yet clear, which aspects of it make it into a future JDK and which version. So I put a deferred label on it, we only finalized JSR 385, so it might have a bit of time anyway. Without changing the API we could try to help with that in the RI, e.g. AbstractSystemOfUnits, so please feel free to create a similar ticket in https://github.com/unitsofmeasurement/indriya/issues. That class has a getUnit(Class) method already, but it could be an option to add a getUnits(Class) method returning a Set or Collection like getUnits(Dimension) does there already. The existing method only returns the system or base unit for the given quantity type. We would have to store the meta-information like the type class for every unit (otherwise it would not be in such collection) but it could offer a way to retrieve all units for a given type until Java allows to get that information more explicitly from the Unit or Quantity itself.

keilw commented 4 years ago

Actually I think https://github.com/unitsofmeasurement/indriya/issues/224 might be a ticket for that already. It's phrased very "thin" but I would interpret "list of all valid Dimension for a Quantity LENGTH..." as list of valid units for a Quantity Length, because a Unit only has one Dimension, and while LENGTH is also a Dimension instance, a method that returns all units for a given dimension like that already exists. It doesn't for a given quantity class because that meta-information is not stored so far.

msqr commented 4 years ago

Hi Werner, thanks for your response. I've dealt with erasure problems like this, I understand what you mean. I guess I was wishing the Unit API could provide the Quantity class directly, something like

Class<Q> getQuantityClass();

Which would of course require all Unit implementations to capture that class at construction time, as you note, for example in AbstractUnit

    private final Class<Q> quantityClass;

    /**
     * Constructor.
     * 
     * @param quantityClass the quantity class.
     */
    protected AbstractUnit(Class<Q> quantityClass) {
        this.quantityClass = quantityClass;
    }

    /**
     * Constructor setting a symbol.
     * 
     * @param quantityClass the quantity class.
     * @param symbol the unit symbol.
     */
    protected AbstractUnit(Class<Q> quantityClass, String symbol) {
        this.quantityClass = quantityClass;
        this.symbol = symbol;
    }

    public Class<Q> getQuantityClass() {
        return quantityClass;
    }

I understand that impacts all implementations of Unit but does not impact consumers of the API (other that the added getQuantityClass() method). This would then continue to support Java 8.

This issue comes up for me on a project where I need to deal with arbitrary, user-configured units defined as strings so that I can later capture amount values associated with those units into Quantity instances and translate them into system or base units.

Thanks again for the info; it looks like for now I will need to use Indriya directly. Perhaps a tweak to the Unit API like discussed here could happen in the future.

keilw commented 4 years ago

I'm not sure if Class<Q> would work because the problem is, Q at runtime usually gets deleted even if you try to store the class, Class would but then one had to store e.g. Length etc. everywhere. For Unit because that comes from a registry like Units or another system, it could work, but it would be a redundancy.