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}