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