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.internal.function.simplify;
031
032import java.util.Map;
033import java.util.Objects;
034import java.util.function.BiPredicate;
035import java.util.function.BinaryOperator;
036
037import tech.units.indriya.function.AbstractConverter;
038import tech.units.indriya.function.Calculus;
039import tech.units.indriya.function.ConverterCompositionHandler;
040import tech.units.indriya.function.PowerOfIntConverter;
041
042/**
043 * UnitCompositionHandler yielding a normal-form.
044 * A normal-form is required to decide whether two UnitConverters are equivalent.
045 * 
046 * @author Andi Huber
047 * @version 1.1
048 * @since 2.0
049 */
050public class UnitCompositionHandlerYieldingNormalForm implements ConverterCompositionHandler {
051
052  private final Map<Class<? extends AbstractConverter>, Integer> normalFormOrder;
053
054  public UnitCompositionHandlerYieldingNormalForm() {
055    normalFormOrder = Calculus.getNormalFormOrder();
056  }
057
058  @Override
059  public AbstractConverter compose(
060      AbstractConverter a, 
061      AbstractConverter b,
062      BiPredicate<AbstractConverter, AbstractConverter> canReduce,
063      BinaryOperator<AbstractConverter> doReduce) {
064
065    if(a.isIdentity()) {
066      if(b.isIdentity()) {
067        return isNormalFormOrderWhenIdentity(a, b) ? a : b;
068      }
069      return b;
070    }
071    if(b.isIdentity()) {
072      return a;
073    }
074
075    if(canReduce.test(a, b)) {
076      return doReduce.apply(a, b);
077    }
078
079    final boolean commutative = a.isLinear() && b.isLinear(); 
080    final boolean swap = commutative && !isNormalFormOrderWhenCommutative(a, b);
081
082    final AbstractConverter.Pair nonSimplifiedForm = swap 
083        ? new AbstractConverter.Pair(b, a) 
084            : new AbstractConverter.Pair(a, b); 
085
086        return new CompositionTask(
087            this::isNormalFormOrderWhenIdentity,
088            this::isNormalFormOrderWhenCommutative,
089            canReduce, 
090            doReduce)
091            .reduceToNormalForm(nonSimplifiedForm.getConversionSteps());
092
093  }
094
095  // -- HELPER
096
097  private boolean isNormalFormOrderWhenIdentity(AbstractConverter a, AbstractConverter b) {
098    if(a.getClass().equals(b.getClass())) {
099      return true;
100    }
101    return normalFormOrder.get(a.getClass()) <= normalFormOrder.get(b.getClass());
102  }
103
104  private boolean isNormalFormOrderWhenCommutative(AbstractConverter a, AbstractConverter b) {
105    if(a.getClass().equals(b.getClass())) {
106      if(a instanceof PowerOfIntConverter) {
107        return  ((PowerOfIntConverter)a).getBase() <= ((PowerOfIntConverter)b).getBase();
108      }
109      return true;
110    }
111
112    Integer orderA = Objects.requireNonNull(normalFormOrder.get(a.getClass()), 
113        ()->String.format("no normal-form order defined for class '%s'", a.getClass().getName()));
114    Integer orderB = Objects.requireNonNull(normalFormOrder.get(b.getClass()), 
115        ()->String.format("no normal-form order defined for class '%s'", b.getClass().getName()));
116
117    return orderA <= orderB;
118  }
119
120
121}