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}