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