001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2018, 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; 031 032import java.io.Serializable; 033import java.math.BigDecimal; 034import java.math.BigInteger; 035import java.math.MathContext; 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.List; 040import java.util.Objects; 041import java.util.stream.Collectors; 042 043import javax.measure.UnitConverter; 044 045import tech.units.indriya.function.Calculus; 046import tech.units.indriya.function.PowersOfIntConverter; 047import tech.units.indriya.function.UnitComparator; 048import tech.units.indriya.internal.simplify.Simplifier; 049import tech.units.indriya.unit.Prefix; 050 051/** 052 * <p> 053 * The base class for our {@link UnitConverter} implementations. 054 * </p> 055 * 056 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 057 * @author <a href="mailto:units@catmedia.us">Werner Keil</a> 058 * @author Andi Huber 059 * @version 1.6, April 26, 2018 060 * @since 1.0 061 */ 062public abstract class AbstractConverter 063 implements UnitConverter, Serializable, Comparable<UnitConverter> { 064 065 /** 066 * 067 */ 068 private static final long serialVersionUID = 5790242858468427131L; 069 070 /** 071 * Holds identity converter. 072 */ 073 // [ahuber] potentially misused: checking whether a UnitConverter is an identity operator 074 // should be done with unitConverter.isIdentity() rather then unitConverter == AbstractConverter.IDENTITY 075 public static final AbstractConverter IDENTITY = new Identity(); 076 077 /** 078 * memoization for getConversionSteps 079 */ 080 protected List<? extends UnitConverter> conversionSteps; 081 082 /** 083 * DefaultQuantityFactory constructor. 084 */ 085 protected AbstractConverter() { 086 } 087 088 /** 089 * Creates a converter with the specified Prefix. 090 * 091 * @param prefix 092 * the prefix for the factor. 093 */ 094 public static UnitConverter of(Prefix prefix) { 095 return PowersOfIntConverter.of(prefix); 096 } 097 098 @Override 099 public abstract boolean equals(Object cvtr); 100 101 @Override 102 public abstract int hashCode(); 103 104 // -- TO-STRING - CONTRACT AND INTERFACE IMPLEMENTATION (FINAL) 105 106 /** 107 * Non-API 108 * <p> 109 * Returns a String describing the transformation that is represented by this converter. 110 * Contributes to converter's {@code toString} method. If null or empty 111 * {@code toString} output becomes simplified. 112 * </p> 113 * @return 114 */ 115 protected abstract String transformationLiteral(); 116 117 @Override 118 public final String toString() { 119 String converterName = getClass().getSimpleName(); 120 // omit trailing 'Converter' 121 if(converterName.endsWith("Converter")) { 122 converterName = converterName.substring(0, converterName.length()-"Converter".length()); 123 } 124 if(isIdentity()) { 125 return String.format("%s(IDENTITY)", converterName); 126 } 127 final String transformationLiteral = transformationLiteral(); 128 if(transformationLiteral==null || transformationLiteral.length()==0) { 129 return String.format("%s", converterName); 130 } 131 return String.format("%s(%s)", converterName, transformationLiteral); 132 } 133 134 // -- INVERSION - CONTRACT AND INTERFACE IMPLEMENTATION (FINAL) 135 136 /** 137 * Non-API 138 * <p> 139 * Returns an AbstractConverter that represents the inverse transformation of this converter, 140 * for cases where the transformation is not the identity transformation. 141 * </p> 142 * @return 143 */ 144 protected abstract AbstractConverter inverseWhenNotIdentity(); 145 146 @Override 147 public final AbstractConverter inverse() { 148 if(isIdentity()) { 149 return this; 150 } 151 return inverseWhenNotIdentity(); 152 } 153 154 // -- COMPOSITION CONTRACTS (TO BE IMPLEMENTED BY SUB-CLASSES) 155 156 /** 157 * Non-API 158 * Guard for {@link #simpleCompose(AbstractConverter)} 159 * @param that 160 * @return whether or not a 'simple' composition of transformations is possible 161 */ 162 protected abstract boolean isSimpleCompositionWith(AbstractConverter that); 163 164 /** 165 * Non-API 166 * Guarded by {@link #isSimpleCompositionWith(AbstractConverter)} 167 * @param that 168 * @return a new AbstractConverter that adds no additional conversion step 169 */ 170 protected AbstractConverter simpleCompose(AbstractConverter that) { 171 throw new IllegalStateException( 172 String.format("Concrete UnitConverter '%s' does not implement simpleCompose(...).", this)); 173 } 174 175 // -- COMPOSITION INTERFACE IMPLEMENTATION (FINAL) 176 177 @Override 178 public final UnitConverter concatenate(UnitConverter converter) { 179 Objects.requireNonNull(converter, "Can not concatenate null."); 180 if(converter instanceof AbstractConverter) { 181 // let Simplifier decide 182 AbstractConverter other = (AbstractConverter) converter; 183 return Simplifier.compose(this, other, 184 AbstractConverter::isSimpleCompositionWith, 185 AbstractConverter::simpleCompose); 186 } 187 // converter is not known to this implementation ... 188 if(converter.isIdentity()) { 189 return this; 190 } 191 if(this.isIdentity()) { 192 return converter; 193 } 194 throw new IllegalArgumentException( 195 "Concatenate is currently only supported for sub-classes of AbstractConverter"); 196 //[ahuber] we don't know how to simplify into a 'normal-form' with 'foreign' converters 197 //return new Pair(this, converter); 198 } 199 200 @Override 201 public final List<? extends UnitConverter> getConversionSteps() { 202 if(conversionSteps != null) { 203 return conversionSteps; 204 } 205 if(this instanceof Pair) { 206 return conversionSteps = ((Pair)this).createConversionSteps(); 207 } 208 return conversionSteps = Collections.singletonList(this); 209 } 210 211 // -- CONVERSION CONTRACTS (TO BE IMPLEMENTED BY SUB-CLASSES) 212 213 /** 214 * Non-API 215 * @param value 216 * @return transformed value 217 */ 218 protected abstract double convertWhenNotIdentity(double value); 219 220 /** 221 * Non-API 222 * @param value 223 * @param ctx 224 * @return transformed value (most likely a BigInteger or BigDecimal) 225 */ 226 protected Number convertWhenNotIdentity(BigInteger value, MathContext ctx) { 227 return convertWhenNotIdentity(new BigDecimal(value), ctx); 228 } 229 230 /** 231 * Non-API 232 * @param value 233 * @param ctx 234 * @return transformed value 235 */ 236 protected abstract BigDecimal convertWhenNotIdentity(BigDecimal value, MathContext ctx); 237 238 // -- CONVERSION INTERFACE IMPLEMENTATION (FINAL) 239 240 @Override 241 public final double convert(double value) { 242 if(isIdentity()) { 243 return value; 244 } 245 return convertWhenNotIdentity(value); 246 } 247 248 /** 249 * @throws IllegalArgumentException 250 * if the value is </code>null</code>. 251 */ 252 @Override 253 public final Number convert(Number value) { 254 if(isIdentity()) { 255 return value; 256 } 257 if (value == null) { 258 throw new IllegalArgumentException("Value cannot be null"); 259 } 260 if (value instanceof BigDecimal) { 261 return convertWhenNotIdentity((BigDecimal) value, Calculus.MATH_CONTEXT); 262 } 263 if (value instanceof BigInteger) { 264 return convertWhenNotIdentity((BigInteger) value, Calculus.MATH_CONTEXT); 265 } 266 return convertWhenNotIdentity(value.doubleValue()); 267 } 268 269 // -- DEFAULT IMPLEMENTATION OF IDENTITY 270 271 /** 272 * This class represents the identity converter (singleton). 273 */ 274 private static final class Identity extends AbstractConverter { 275 276 /** 277 * 278 */ 279 private static final long serialVersionUID = -4460463244427587361L; 280 281 @Override 282 public boolean isIdentity() { 283 return true; 284 } 285 286 @Override 287 public double convertWhenNotIdentity(double value) { 288 throw new IllegalStateException("code was reached, that is expected unreachable"); 289 } 290 291 @Override 292 public Number convertWhenNotIdentity(BigInteger value, MathContext ctx) { 293 throw new IllegalStateException("code was reached, that is expected unreachable"); 294 } 295 296 @Override 297 public BigDecimal convertWhenNotIdentity(BigDecimal value, MathContext ctx) { 298 throw new IllegalStateException("code was reached, that is expected unreachable"); 299 } 300 301 @Override 302 public boolean equals(Object cvtr) { 303 return (cvtr instanceof Identity); 304 } 305 306 @Override 307 public int hashCode() { 308 return 0; 309 } 310 311 @Override 312 public boolean isLinear() { 313 return true; 314 } 315 316 @Override 317 public int compareTo(UnitConverter o) { 318 if (o instanceof Identity) { 319 return 0; 320 } 321 return -1; 322 } 323 324 @Override 325 protected boolean isSimpleCompositionWith(AbstractConverter that) { 326 throw new IllegalStateException("code was reached, that is expected unreachable"); 327 } 328 329 @Override 330 protected AbstractConverter simpleCompose(AbstractConverter that) { 331 throw new IllegalStateException("code was reached, that is expected unreachable"); 332 } 333 334 @Override 335 protected AbstractConverter inverseWhenNotIdentity() { 336 throw new IllegalStateException("code was reached, that is expected unreachable"); 337 } 338 339 @Override 340 protected String transformationLiteral() { 341 return null; 342 } 343 344 } 345 346 // -- BINARY TREE (PAIR) 347 348 /** 349 * This class represents converters made up of two or more separate converters 350 * (in matrix notation <code>[pair] = [left] x [right]</code>). 351 */ 352 public static final class Pair extends AbstractConverter implements Serializable { 353 354 /** 355 * 356 */ 357 private static final long serialVersionUID = -123063827821728331L; 358 359 /** 360 * Holds the first converter. 361 */ 362 private final UnitConverter left; 363 364 /** 365 * Holds the second converter. 366 */ 367 private final UnitConverter right; 368 369 /** 370 * Creates a pair converter resulting from the combined transformation of the 371 * specified converters. 372 * 373 * @param left 374 * the left converter, not <code>null</code>. 375 * @param right 376 * the right converter. 377 * @throws IllegalArgumentException 378 * if either the left or right converter are </code> null</code> 379 */ 380 public Pair(UnitConverter left, UnitConverter right) { 381 if (left != null && right != null) { 382 this.left = left; 383 this.right = right; 384 } else { 385 throw new IllegalArgumentException("Converters cannot be null"); 386 } 387 } 388 389 @Override 390 public boolean isLinear() { 391 return left.isLinear() && right.isLinear(); 392 } 393 394 @Override 395 public boolean isIdentity() { 396 return false; 397 } 398 399 /* 400 * Non-API 401 */ 402 protected List<? extends UnitConverter> createConversionSteps(){ 403 List<? extends UnitConverter> leftCompound = left.getConversionSteps(); 404 List<? extends UnitConverter> rightCompound = right.getConversionSteps(); 405 final List<UnitConverter> steps = new ArrayList<>(leftCompound.size() + rightCompound.size()); 406 steps.addAll(leftCompound); 407 steps.addAll(rightCompound); 408 return steps; 409 } 410 411 @Override 412 public Pair inverseWhenNotIdentity() { 413 return new Pair(right.inverse(), left.inverse()); 414 } 415 416 @Override 417 public double convertWhenNotIdentity(double value) { 418 return left.convert(right.convert(value)); 419 } 420 421 @Override 422 public Number convertWhenNotIdentity(BigInteger value, MathContext ctx) { 423 if (right instanceof AbstractConverter) { 424 //TODO [ahuber] assumes left is always instanceof AbstractConverter, why? 425 final AbstractConverter _left = (AbstractConverter) left; 426 final AbstractConverter _right = (AbstractConverter) right; 427 428 final Number rightValue = _right.convertWhenNotIdentity(value, ctx); 429 if(rightValue instanceof BigDecimal) { 430 return _left.convertWhenNotIdentity((BigDecimal) rightValue, ctx); 431 } 432 if(rightValue instanceof BigInteger) { 433 return _left.convertWhenNotIdentity((BigInteger) rightValue, ctx); 434 } 435 return _left.convertWhenNotIdentity(Calculus.toBigDecimal(rightValue), ctx); 436 } 437 return convertWhenNotIdentity(new BigDecimal(value), ctx); 438 } 439 440 @Override 441 public BigDecimal convertWhenNotIdentity(BigDecimal value, MathContext ctx) { 442 if (right instanceof AbstractConverter) { 443 //TODO [ahuber] assumes left is always instanceof AbstractConverter, why? 444 final AbstractConverter _left = (AbstractConverter) left; 445 final AbstractConverter _right = (AbstractConverter) right; 446 return _left.convertWhenNotIdentity(_right.convertWhenNotIdentity(value, ctx), ctx); 447 } 448 return Calculus.toBigDecimal(left.convert(right.convert(value))); 449 } 450 451 @Override 452 public boolean equals(Object obj) { 453 if (this == obj) { 454 return true; 455 } 456 if (obj instanceof Pair) { 457 Pair that = (Pair) obj; 458 return Objects.equals(left, that.left) && Objects.equals(right, that.right); 459 } 460 return false; 461 } 462 463 @Override 464 public int hashCode() { 465 return Objects.hash(left, right); 466 } 467 468 public UnitConverter getLeft() { 469 return left; 470 } 471 472 public UnitConverter getRight() { 473 return right; 474 } 475 476 @SuppressWarnings("unchecked") 477 @Override 478 public int compareTo(UnitConverter obj) { 479 if (this == obj) { 480 return 0; 481 } 482 if (obj instanceof Pair) { 483 Pair that = (Pair) obj; 484 @SuppressWarnings("rawtypes") 485 Comparator c = new UnitComparator<>(); 486 return Objects.compare(left, that.left, c) + Objects.compare(right, that.right, c); 487 } 488 return -1; 489 } 490 491 @Override 492 protected String transformationLiteral() { 493 return String.format("%s", 494 getConversionSteps().stream() 495 .map(UnitConverter::toString) 496 .collect(Collectors.joining(" ○ ")) ); 497 } 498 499 500 @Override 501 protected boolean isSimpleCompositionWith(AbstractConverter that) { 502 return false; 503 } 504 505 } 506 507 508}