001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2021, 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.internal.function;
031
032import java.util.Optional;
033import java.util.function.BinaryOperator;
034import java.util.function.UnaryOperator;
035
036import javax.measure.Quantity;
037import javax.measure.Quantity.Scale;
038import javax.measure.Unit;
039import javax.measure.UnitConverter;
040
041import static javax.measure.Quantity.Scale.ABSOLUTE;
042import static javax.measure.Quantity.Scale.RELATIVE;
043
044import tech.units.indriya.ComparableQuantity;
045import tech.units.indriya.function.AbstractConverter;
046import tech.units.indriya.quantity.Quantities;
047
048/**
049 * Encapsulates scale-honoring quantity arithmetics.
050 * 
051 * @author Andi Huber
052 */
053public final class ScaleHelper {
054
055    public static boolean isAbsolute(final Quantity<?> quantity) {
056        return ABSOLUTE == quantity.getScale();
057    }
058
059    public static boolean isRelative(final Quantity<?> quantity) {
060        return RELATIVE == quantity.getScale();
061    }
062
063    public static <Q extends Quantity<Q>> ComparableQuantity<Q> convertTo(
064            final Quantity<Q> quantity, 
065            final Unit<Q> anotherUnit) {
066
067        final UnitConverter converter = quantity.getUnit().getConverterTo(anotherUnit);
068        
069        if (isRelative(quantity)) {
070            final Number linearFactor = linearFactorOf(converter).orElse(null);
071            if(linearFactor==null) {
072                throw unsupportedRelativeScaleConversion(quantity, anotherUnit);
073            }
074            final Number valueInOtherUnit = Calculator.of(linearFactor).multiply(quantity.getValue()).peek();
075            return Quantities.getQuantity(valueInOtherUnit, anotherUnit, RELATIVE);
076        }
077        
078        final Number convertedValue = converter.convert(quantity.getValue());
079        return Quantities.getQuantity(convertedValue, anotherUnit, ABSOLUTE);
080    }
081
082    public static <Q extends Quantity<Q>> ComparableQuantity<Q> addition(
083            final Quantity<Q> q1,
084            final Quantity<Q> q2, 
085            final BinaryOperator<Number> operator) {
086
087        final boolean yieldsRelativeScale = OperandMode.get(q1, q2).isAllRelative(); 
088
089        // converting almost all, except system units and those that are shifted and relative like eg. Δ2°C == Δ2K
090        final ToSystemUnitConverter thisConverter = toSystemUnitConverterForAdd(q1, q1);
091        final ToSystemUnitConverter thatConverter = toSystemUnitConverterForAdd(q1, q2);
092
093        final Number thisValueInSystemUnit = thisConverter.apply(q1.getValue());
094        final Number thatValueInSystemUnit = thatConverter.apply(q2.getValue());
095
096        final Number resultValueInSystemUnit = operator.apply(thisValueInSystemUnit, thatValueInSystemUnit);
097
098        if (yieldsRelativeScale) {
099            return Quantities.getQuantity(thisConverter.invert(resultValueInSystemUnit), q1.getUnit(), RELATIVE);
100        }
101
102        final boolean needsInvering = !thisConverter.isNoop() || !thatConverter.isNoop();
103        final Number resultValueInThisUnit = needsInvering 
104                ? q1.getUnit().getConverterTo(q1.getUnit().getSystemUnit()).inverse().convert(resultValueInSystemUnit)
105                : resultValueInSystemUnit;
106
107        return Quantities.getQuantity(resultValueInThisUnit, q1.getUnit(), ABSOLUTE);
108    }
109
110    public static <Q extends Quantity<Q>> ComparableQuantity<Q> scalarMultiplication(
111            final Quantity<Q> quantity, 
112            final UnaryOperator<Number> operator) {
113
114        // if operand has scale RELATIVE, multiplication is trivial
115        if (isRelative(quantity)) {
116            return Quantities.getQuantity(
117                    operator.apply(quantity.getValue()), 
118                    quantity.getUnit(), 
119                    RELATIVE);
120        }
121
122        final ToSystemUnitConverter toSystemUnits = toSystemUnitConverterForMul(quantity);
123
124        final Number thisValueWithAbsoluteScale = toSystemUnits.apply(quantity.getValue());
125        final Number resultValueInAbsUnits = operator.apply(thisValueWithAbsoluteScale);
126        final boolean needsInvering = !toSystemUnits.isNoop();
127
128        final Number resultValueInThisUnit = needsInvering 
129                ? quantity.getUnit().getConverterTo(quantity.getUnit().getSystemUnit()).inverse().convert(resultValueInAbsUnits)
130                : resultValueInAbsUnits;
131
132        return Quantities.getQuantity(resultValueInThisUnit, quantity.getUnit(), quantity.getScale());
133    }
134
135    public static ComparableQuantity<?> multiplication(
136            final Quantity<?> q1,
137            final Quantity<?> q2, 
138            final BinaryOperator<Number> amountOperator, 
139            final BinaryOperator<Unit<?>> unitOperator) {
140        
141        final Quantity<?> absQ1 = toAbsoluteLinear(q1);
142        final Quantity<?> absQ2 = toAbsoluteLinear(q2);
143        return Quantities.getQuantity(
144                amountOperator.apply(absQ1.getValue(), absQ2.getValue()), 
145                unitOperator.apply(absQ1.getUnit(), absQ2.getUnit()));
146    }
147
148    // -- HELPER
149
150    private static <Q extends Quantity<Q>> Quantity<Q> toAbsoluteLinear(Quantity<Q> quantity) {
151        final Unit<Q> systemUnit = quantity.getUnit().getSystemUnit();
152        final UnitConverter toSystemUnit = quantity.getUnit().getConverterTo(systemUnit);
153        if(toSystemUnit.isLinear()) {
154            if(isAbsolute(quantity)) {
155                return quantity;
156            }
157            return Quantities.getQuantity(quantity.getValue(), quantity.getUnit());
158        }
159        // convert to system units
160        if(isAbsolute(quantity)) {
161            return Quantities.getQuantity(toSystemUnit.convert(quantity.getValue()), systemUnit, Scale.ABSOLUTE);
162        } else {
163            final Number linearFactor = linearFactorOf(toSystemUnit).orElse(null);
164            if(linearFactor==null) {
165                throw unsupportedRelativeScaleConversion(quantity, systemUnit);
166            }
167            final Number valueInSystemUnits = Calculator.of(linearFactor).multiply(quantity.getValue()).peek();
168            return Quantities.getQuantity(valueInSystemUnits, systemUnit, ABSOLUTE);
169        }
170    }
171
172    // used for addition, honors RELATIVE scale
173    private static <Q extends Quantity<Q>> ToSystemUnitConverter toSystemUnitConverterForAdd(
174            final Quantity<Q> q1,
175            final Quantity<Q> q2) {
176        final Unit<Q> systemUnit = q1.getUnit().getSystemUnit();
177        return ToSystemUnitConverter.forQuantity(q2, systemUnit);
178    }
179
180    // used for multiplication, honors RELATIVE scale
181    private static <T extends Quantity<T>> 
182    ToSystemUnitConverter toSystemUnitConverterForMul(Quantity<T> quantity) {
183        final Unit<T> systemUnit = quantity.getUnit().getSystemUnit();
184        return ToSystemUnitConverter.forQuantity(quantity, systemUnit);
185    }
186
187    private static Optional<Number> linearFactorOf(UnitConverter converter) {
188        return (converter instanceof AbstractConverter)
189                ? ((AbstractConverter)converter).linearFactor()
190                : Optional.empty();
191    }
192
193    // honors RELATIVE scale
194    private static class ToSystemUnitConverter implements UnaryOperator<Number> {
195        private final UnaryOperator<Number> unaryOperator;
196        private final UnaryOperator<Number> inverseOperator;
197
198        public static <Q extends Quantity<Q>>  
199        ToSystemUnitConverter forQuantity(Quantity<Q> quantity, Unit<Q> systemUnit) {
200            if(quantity.getUnit().equals(systemUnit)) {
201                return ToSystemUnitConverter.noop(); // no conversion required
202            }
203
204            final UnitConverter converter = quantity.getUnit().getConverterTo(systemUnit);
205
206            if(isAbsolute(quantity)) {
207
208                return ToSystemUnitConverter.of(converter::convert); // convert to system units
209
210            } else {
211                final Number linearFactor = linearFactorOf(converter).orElse(null);
212                if(linearFactor!=null) {
213                    // conversion by factor required ... Δ2°C -> Δ2K , Δ2°F -> 5/9 * Δ2K
214                    return ToSystemUnitConverter.factor(linearFactor); 
215                }
216                // convert any other cases of RELATIVE scale to system unit (ABSOLUTE) ...
217                throw unsupportedConverter(converter, quantity.getUnit());
218            }
219        }
220
221        public Number invert(Number x) {
222            return isNoop() 
223                    ? x
224                    : inverseOperator.apply(x); 
225        }
226
227        public static ToSystemUnitConverter of(UnaryOperator<Number> unaryOperator) {
228            return new ToSystemUnitConverter(unaryOperator, null);
229        }
230        public static ToSystemUnitConverter noop() {
231            return new ToSystemUnitConverter(null, null);
232        }
233        public static ToSystemUnitConverter factor(Number factor) {
234            return new ToSystemUnitConverter(
235                    number->Calculator.of(number).multiply(factor).peek(),
236                    number->Calculator.of(number).divide(factor).peek());
237        }
238        private ToSystemUnitConverter(
239                UnaryOperator<Number> unaryOperator, 
240                UnaryOperator<Number> inverseOperator) {
241            this.unaryOperator = unaryOperator;
242            this.inverseOperator = inverseOperator;
243        }
244        public boolean isNoop() {
245            return unaryOperator==null;
246        }
247        @Override
248        public Number apply(Number x) {
249            return isNoop() 
250                    ? x
251                    : unaryOperator.apply(x); 
252        }
253
254    }
255    
256    // -- OPERANDS
257    
258    private static enum OperandMode {
259        ALL_ABSOLUTE,
260        ALL_RELATIVE,
261        MIXED;
262        public static OperandMode get(
263                final Quantity<?> q1,
264                final Quantity<?> q2) {
265            if(q1.getScale()!=q2.getScale()) {
266                return OperandMode.MIXED;
267            }
268            return isAbsolute(q1)
269                    ? OperandMode.ALL_ABSOLUTE
270                    : OperandMode.ALL_RELATIVE;
271        }
272//        public boolean isAllAbsolute() {
273//            return this==ALL_ABSOLUTE;
274//        }
275        public boolean isAllRelative() {
276            return this==ALL_RELATIVE;
277        }
278    }
279    
280    
281    // -- EXCEPTIONS
282    
283    private static <Q extends Quantity<Q>> UnsupportedOperationException unsupportedRelativeScaleConversion(
284            Quantity<Q> quantity, 
285            Unit<Q> anotherUnit) {
286        return new UnsupportedOperationException(
287                String.format(
288                        "Conversion of Quantitity %s to Unit %s is not supported for realtive scale.", 
289                        quantity, anotherUnit));
290    }
291    
292    private static UnsupportedOperationException unsupportedConverter(UnitConverter converter, Unit<?> unit) {
293        return new UnsupportedOperationException(
294                String.format(
295                        "Scale conversion from RELATIVE to ABSOLUTE for Unit %s having Converter %s is not implemented.", 
296                        unit, converter));
297    }
298
299}