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.quantity; 031 032import static org.apiguardian.api.API.Status.DEPRECATED; 033 034import java.io.Serializable; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collections; 038import java.util.List; 039import java.util.Objects; 040import java.util.stream.Collectors; 041 042import javax.measure.Quantity; 043import javax.measure.Quantity.Scale; 044import javax.measure.Unit; 045 046import org.apiguardian.api.API; 047 048import tech.units.indriya.format.SimpleQuantityFormat; 049import tech.units.indriya.function.Calculus; 050import tech.units.indriya.function.MixedRadix; 051import tech.units.indriya.internal.function.Calculator; 052import tech.units.indriya.spi.NumberSystem; 053import tech.uom.lib.common.function.QuantityConverter; 054 055/** 056 * <p> 057 * This class represents multi-radix quantities (like "1 hour, 5 min, 30 sec" or "6 ft, 3 in"). 058 * </p> 059 * 060 * @param <Q> 061 * The type of the quantity. 062 * 063 * @author <a href="mailto:werner@units.tech">Werner Keil</a> 064 * @author Andi Huber 065 * @version 1.8, May 13, 2021 066 * @see <a href="http://www.thefreedictionary.com/Compound+quantity">Free Dictionary: Compound Quantity</a> 067 * @see <a href="https://www.yourdictionary.com/compound-number">Your Dictionary: Compound Number</a> 068 * @deprecated Use {@link MixedQuantity} 069 */ 070@API(status=DEPRECATED) 071public class CompoundQuantity<Q extends Quantity<Q>> implements QuantityConverter<Q>, Serializable { 072 /** 073 * 074 */ 075 private static final long serialVersionUID = 5863961588282485676L; 076 077 private final List<Quantity<Q>> quantityList; 078 private final Object[] quantityArray; 079 private final List<Unit<Q>> unitList; 080 private Unit<Q> leastSignificantUnit; 081 private Scale commonScale; 082 083 // MixedRadix is optimized for best accuracy, when calculating the radix sum, so we try to use it if possible 084 private MixedRadix<Q> mixedRadixIfPossible; 085 086 /** 087 * @param quantities - the list of quantities to construct this CompoundQuantity. 088 */ 089 protected CompoundQuantity(final List<Quantity<Q>> quantities) { 090 091 final List<Unit<Q>> unitList = new ArrayList<>(); 092 093 for (Quantity<Q> q : quantities) { 094 095 final Unit<Q> unit = q.getUnit(); 096 097 unitList.add(unit); 098 099 commonScale = q.getScale(); 100 101 // keep track of the least significant unit, thats the one that should 'drive' arithmetic operations 102 103 104 if(leastSignificantUnit==null) { 105 leastSignificantUnit = unit; 106 } else { 107 final NumberSystem ns = Calculus.currentNumberSystem(); 108 final Number leastSignificantToCurrentFactor = leastSignificantUnit.getConverterTo(unit).convert(1); 109 final boolean isLessSignificant = ns.isLessThanOne(ns.abs(leastSignificantToCurrentFactor)); 110 if(isLessSignificant) { 111 leastSignificantUnit = unit; 112 } 113 } 114 115 } 116 117 this.quantityList = Collections.unmodifiableList(new ArrayList<>(quantities)); 118 this.quantityArray = quantities.toArray(); 119 120 this.unitList = Collections.unmodifiableList(unitList); 121 122 try { 123 124 // - will throw if units are not in decreasing order of significance 125 mixedRadixIfPossible = MixedRadix.of(getUnits()); 126 127 } catch (Exception e) { 128 129 mixedRadixIfPossible = null; 130 } 131 132 } 133 134 /** 135 * @param <Q> 136 * @param quantities 137 * @return a {@code CompoundQuantity} with the specified {@code quantities} 138 * @throws IllegalArgumentException 139 * if given {@code quantities} is {@code null} or empty 140 * or contains any <code>null</code> values 141 * or contains quantities of mixed scale 142 * 143 */ 144 @SafeVarargs 145 public static <Q extends Quantity<Q>> CompoundQuantity<Q> of(Quantity<Q>... quantities) { 146 guardAgainstIllegalQuantitiesArgument(quantities); 147 return new CompoundQuantity<>(Arrays.asList(quantities)); 148 } 149 150 /** 151 * @param <Q> 152 * @param quantities 153 * @return a {@code CompoundQuantity} with the specified {@code quantities} 154 * @throws IllegalArgumentException 155 * if given {@code quantities} is {@code null} or empty 156 * or contains any <code>null</code> values 157 * or contains quantities of mixed scale 158 * 159 */ 160 public static <Q extends Quantity<Q>> CompoundQuantity<Q> of(List<Quantity<Q>> quantities) { 161 guardAgainstIllegalQuantitiesArgument(quantities); 162 return new CompoundQuantity<>(quantities); 163 } 164 165 /** 166 * Gets the list of units in this CompoundQuantity. 167 * <p> 168 * This list can be used in conjunction with {@link #getQuantities()} to access the entire quantity. 169 * 170 * @return a list containing the units, not null 171 */ 172 public List<Unit<Q>> getUnits() { 173 return unitList; 174 } 175 176 /** 177 * Gets quantities in this CompoundQuantity. 178 * 179 * @return a list containing the quantities, not null 180 */ 181 public List<Quantity<Q>> getQuantities() { 182 return quantityList; 183 } 184 185//TODO[211] deprecated 186// /** 187// * Gets the Quantity of the requested Unit. 188// * <p> 189// * This returns a value for each Unit in this CompoundQuantity. Or <code>null</code> if the given unit is not included. 190// * 191// */ 192// public Quantity<Q> get(Unit<Q> unit) { 193// return quantMap.get(unit); 194// } 195 196 /* 197 * (non-Javadoc) 198 * 199 * @see java.lang.Object#toString() 200 */ 201 @Override 202 public String toString() { 203 return SimpleQuantityFormat.getInstance().format(this); 204 } 205 206 /** 207 * Returns the <b>sum</b> of all quantity values in this CompoundQuantity converted into another (compatible) unit. 208 * @param unit 209 * the {@code Unit unit} in which the returned quantity is stated. 210 * @return the sum of all quantities in this CompoundQuantity or a new quantity stated in the specified unit. 211 * @throws ArithmeticException 212 * if the result is inexact and the quotient has a non-terminating decimal expansion. 213 */ 214 @Override 215 public Quantity<Q> to(Unit<Q> unit) { 216 217 // MixedRadix is optimized for best accuracy, when calculating the radix sum, so we use it if possible 218 if(mixedRadixIfPossible!=null) { 219 Number[] values = getQuantities() 220 .stream() 221 .map(Quantity::getValue) 222 .collect(Collectors.toList()) 223 .toArray(new Number[0]); 224 225 return mixedRadixIfPossible.createQuantity(values).to(unit); 226 } 227 228 // fallback 229 230 final Calculator calc = Calculator.of(0); 231 232 for (Quantity<Q> q : quantityList) { 233 234 final Number termInLeastSignificantUnits = 235 q.getUnit().getConverterTo(leastSignificantUnit).convert(q.getValue()); 236 237 calc.add(termInLeastSignificantUnits); 238 } 239 240 final Number sumInLeastSignificantUnits = calc.peek(); 241 242 return Quantities.getQuantity(sumInLeastSignificantUnits, leastSignificantUnit, commonScale).to(unit); 243 } 244 245 /** 246 * Indicates if this mixed quantity is considered equal to the specified object (both are mixed units with same composing units in the same order). 247 * 248 * @param obj 249 * the object to compare for equality. 250 * @return <code>true</code> if <code>this</code> and <code>obj</code> are considered equal; <code>false</code>otherwise. 251 */ 252 public boolean equals(Object obj) { 253 if (this == obj) { 254 return true; 255 } 256 if (obj instanceof CompoundQuantity) { 257 CompoundQuantity<?> c = (CompoundQuantity<?>) obj; 258 return Arrays.equals(quantityArray, c.quantityArray); 259 } else { 260 return false; 261 } 262 } 263 264 @Override 265 public int hashCode() { 266 return Objects.hash(quantityArray); 267 } 268 269 // -- IMPLEMENTATION DETAILS 270 271 private static void guardAgainstIllegalQuantitiesArgument(Quantity<?>[] quantities) { 272 if (quantities == null || quantities.length < 1) { 273 throw new IllegalArgumentException("At least one quantity is required."); 274 } 275 Scale firstScale = null; 276 for(Quantity<?> q : quantities) { 277 if(q==null) { 278 throw new IllegalArgumentException("Quantities must not contain null."); 279 } 280 if(firstScale==null) { 281 firstScale = q.getScale(); 282 if(firstScale==null) { 283 throw new IllegalArgumentException("Quantities must have a scale."); 284 } 285 } 286 if (!firstScale.equals(q.getScale())) { 287 throw new IllegalArgumentException("Quantities do not have the same scale."); 288 } 289 } 290 } 291 292 // almost a duplicate of the above, this is to keep heap pollution at a minimum 293 private static <Q extends Quantity<Q>> void guardAgainstIllegalQuantitiesArgument(List<Quantity<Q>> quantities) { 294 if (quantities == null || quantities.size() < 1) { 295 throw new IllegalArgumentException("At least one quantity is required."); 296 } 297 Scale firstScale = null; 298 for(Quantity<Q> q : quantities) { 299 if(q==null) { 300 throw new IllegalArgumentException("Quantities must not contain null."); 301 } 302 if(firstScale==null) { 303 firstScale = q.getScale(); 304 if(firstScale==null) { 305 throw new IllegalArgumentException("Quantities must have a scale."); 306 } 307 } 308 if (!firstScale.equals(q.getScale())) { 309 throw new IllegalArgumentException("Quantities do not have the same scale."); 310 } 311 } 312 } 313}