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