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}