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 */
030
031package tech.units.indriya.internal.simplify;
032
033import java.util.HashMap;
034import java.util.Map;
035import java.util.Objects;
036import java.util.function.BiPredicate;
037import java.util.function.BinaryOperator;
038
039import tech.units.indriya.AbstractConverter;
040import tech.units.indriya.function.AddConverter;
041import tech.units.indriya.function.ExpConverter;
042import tech.units.indriya.function.LogConverter;
043import tech.units.indriya.function.MultiplyConverter;
044import tech.units.indriya.function.PowerOfIntConverter;
045import tech.units.indriya.function.PowerOfPiConverter;
046import tech.units.indriya.function.RationalConverter;
047
048/**
049 * Simplifier for UnitConverter composition yielding a normal-form.
050 * A normal-form is required to decide whether two UnitConverters are equivalent.
051 * 
052 * @author Andi Huber
053 * @version 1.0
054 * @since 2.0
055 */
056public final class Simplifier {
057        
058        final static Map<Class<?>, Integer> normalFormOrder = new HashMap<>(6);
059        static {
060                normalFormOrder.put(AbstractConverter.IDENTITY.getClass(), 0);
061                normalFormOrder.put(PowerOfIntConverter.class, 1); 
062                normalFormOrder.put(RationalConverter.class, 2); 
063                normalFormOrder.put(PowerOfPiConverter.class, 3);
064                normalFormOrder.put(MultiplyConverter.class, 4);
065                normalFormOrder.put(AddConverter.class, 5);
066                normalFormOrder.put(LogConverter.class, 6); 
067                normalFormOrder.put(ExpConverter.class, 7);
068                normalFormOrder.put(AbstractConverter.Pair.class, 99);
069        }
070        
071        /**
072         * 
073         * @param a
074         * @param b
075         * @param simpleComposeTest
076         * @param simpleComposeAction
077         * @return normal-form
078         */
079        public static AbstractConverter compose(
080                        AbstractConverter a, 
081                        AbstractConverter b,
082                        BiPredicate<AbstractConverter, AbstractConverter> simpleComposeTest,
083                        BinaryOperator<AbstractConverter> simpleComposeAction
084                        ) {
085                
086                if(a.isIdentity()) {
087                        if(b.isIdentity()) {
088                                return isNormalFormOrderWhenIdentity(a, b) ? a : b;
089                        }
090                        return b;
091                }
092                if(b.isIdentity()) {
093                        return a;
094                }
095                
096                if(simpleComposeTest.test(a, b)) {
097                        return simpleComposeAction.apply(a, b);
098                }
099                
100                final boolean commutative = a.isLinear() && b.isLinear(); 
101                final boolean swap = commutative && !isNormalFormOrderWhenCommutative(a, b);
102                
103                final AbstractConverter.Pair nonSimplifiedForm = swap 
104                                ? new AbstractConverter.Pair(b, a) 
105                                : new AbstractConverter.Pair(a, b); 
106                
107                return new SimplificationWorker(
108                                simpleComposeTest,
109                                simpleComposeAction     )
110                                .simplify(nonSimplifiedForm.getConversionSteps());
111        }
112
113
114        static boolean isNormalFormOrderWhenIdentity(AbstractConverter a, AbstractConverter b) {
115                if(a.getClass().equals(b.getClass())) {
116                        return true;
117                }
118                return normalFormOrder.get(a.getClass()) <= normalFormOrder.get(b.getClass());
119        }
120        
121        static boolean isNormalFormOrderWhenCommutative(AbstractConverter a, AbstractConverter b) {
122                if(a.getClass().equals(b.getClass())) {
123                        if(a instanceof PowerOfIntConverter) {
124                                return  ((PowerOfIntConverter)a).getBase() <= ((PowerOfIntConverter)b).getBase();
125                        }
126//                      if(a instanceof LogConverter) {
127//                              return  ((LogConverter)a).getBase() <= ((LogConverter)b).getBase();
128//                      }
129//                      if(a instanceof ExpConverter) {
130//                              return  ((ExpConverter)a).getBase() <= ((ExpConverter)b).getBase();
131//                      }
132                        return true;
133                }
134                
135                Integer orderA = Objects.requireNonNull(normalFormOrder.get(a.getClass()), 
136                                ()->String.format("no normal-form order defined for class '%s'", a.getClass().getName()));
137                Integer orderB = Objects.requireNonNull(normalFormOrder.get(b.getClass()), 
138                                ()->String.format("no normal-form order defined for class '%s'", b.getClass().getName()));
139                
140                return orderA <= orderB;
141        }
142}