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