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.function;
031
032import java.math.BigInteger;
033import java.util.Objects;
034
035import javax.measure.Prefix;
036import javax.measure.UnitConverter;
037
038import tech.units.indriya.internal.function.calc.Calculator;
039import tech.uom.lib.common.function.IntBaseSupplier;
040import tech.uom.lib.common.function.IntExponentSupplier;
041
042/**
043 * UnitConverter for numbers in base^exponent representation.
044 * @author Andi Huber
045 * @author Werner Keil
046 * @version 1.5, Jun 25, 2019
047 * @since 2.0
048 */
049//TODO[220] make this like all the other MultiplyConverter package private
050public final class PowerOfIntConverter extends AbstractConverter 
051 implements MultiplyConverter, IntBaseSupplier, IntExponentSupplier {
052        private static final long serialVersionUID = 3546932001671571300L;
053
054        private final int base;
055        private final int exponent;
056        private final int hashCode;
057        private final RationalNumber rationalFactor;
058
059        /**
060         * Creates a converter with the specified Prefix.
061         * 
062         * @param prefix
063         *            the prefix for the factor.
064         */
065        static PowerOfIntConverter of(Prefix prefix) {
066                return of(prefix.getValue(), prefix.getExponent());
067        }
068
069        /**
070         * Creates a converter with a factor represented by specified base^exponent.
071         * 
072         * @param base
073         * @param exponent
074         * @return
075         */
076        static PowerOfIntConverter of(int base, int exponent) {
077                return new PowerOfIntConverter(base, exponent);
078        }
079        
080        /**
081         * Creates a converter with a factor represented by specified base^exponent.
082         * 
083         * @param base
084         * @param exponent
085         * @return
086         */
087        static PowerOfIntConverter of(Number base, int exponent) {
088                return new PowerOfIntConverter(base.intValue(), exponent);
089        }
090
091        protected PowerOfIntConverter(int base, int exponent) {
092                if(base == 0) {
093                        throw new IllegalArgumentException("base cannot be zero (because 0^0 is undefined)");
094                }
095                this.base = base;
096                this.exponent = exponent;
097                this.hashCode = Objects.hash(base, exponent);
098                this.rationalFactor = calculateRationalNumberFactor();
099        }
100
101        public int getBase() {
102                return base;
103        }
104
105        public int getExponent() {
106                return exponent;
107        }
108
109        @Override
110        public boolean isIdentity() {
111                if( base == 1 ) {
112                        return true; // 1^x = 1
113                }
114                return exponent == 0; // x^0 = 1, for any x!=0
115                // [ahuber] 0^0 is undefined, but we guard against base==0 in the constructor,
116                // and there is no composition, that changes the base
117        }
118
119        @Override
120        protected boolean canReduceWith(AbstractConverter that) {
121                if (that instanceof PowerOfIntConverter) {
122                        return ((PowerOfIntConverter) that).base == this.base;
123                }
124                return that instanceof RationalConverter;
125        }
126
127        @Override
128        protected AbstractConverter reduce(AbstractConverter that) {
129                if (that instanceof PowerOfIntConverter) {
130                        PowerOfIntConverter other = (PowerOfIntConverter) that;
131                        if(this.base == other.base) { // always true due to guard above
132                                return composeSameBaseNonIdentity(other);
133                        } 
134                }
135                if (that instanceof RationalConverter) {
136                        return (AbstractConverter) toRationalConverter().concatenate(that);
137                }
138                throw new IllegalStateException(String.format(
139                                "%s.simpleCompose() not handled for converter %s", 
140                                this, that));
141        }
142
143        @Override
144        public AbstractConverter inverseWhenNotIdentity() {
145                return new PowerOfIntConverter(base, -exponent);
146        }
147
148    @Override
149    protected Number convertWhenNotIdentity(Number value) {
150        return Calculator.of(rationalFactor)
151                .multiply(value)
152                .peek();
153    }
154    
155        @Override
156        public boolean equals(Object obj) {
157                if (this == obj) {
158                        return true;
159                }
160                if (obj instanceof UnitConverter) {
161                        UnitConverter other = (UnitConverter) obj;
162                        if(this.isIdentity() && other.isIdentity()) {
163                                return true;
164                        }
165                }
166                if (obj instanceof PowerOfIntConverter) {
167                        PowerOfIntConverter other = (PowerOfIntConverter) obj;
168                        return this.base == other.base && this.exponent == other.exponent;
169                }
170                return false;
171        }
172
173        @Override
174        public final String transformationLiteral() {
175                if(base<0) {
176                        return String.format("x -> x * (%s)^%s", base, exponent);
177                }
178                return String.format("x -> x * %s^%s", base, exponent);
179        }
180
181        @Override
182        public int compareTo(UnitConverter o) {
183                if (this == o) {
184                        return 0;
185                }
186                if(this.isIdentity() && o.isIdentity()) {
187                        return 0;
188                }
189                if (o instanceof PowerOfIntConverter) {
190                        PowerOfIntConverter other = (PowerOfIntConverter) o;
191                        int c = Integer.compare(base, other.base);
192                        if(c!=0) {
193                                return c;
194                        }
195                        return Integer.compare(exponent, other.exponent);
196                }
197                return this.getClass().getName().compareTo(o.getClass().getName());
198        }
199
200        @Override
201        public int hashCode() {
202                return hashCode;
203        }
204
205        // -- HELPER
206        
207        private RationalNumber calculateRationalNumberFactor() {
208        if(exponent==0) {
209            return RationalNumber.ONE;
210        }
211        BigInteger bintFactor = BigInteger.valueOf(base).pow(Math.abs(exponent));
212        if(exponent>0) {
213            return RationalNumber.ofInteger(bintFactor);
214        }
215        return RationalNumber.of(BigInteger.ONE, bintFactor);
216    }
217
218        private PowerOfIntConverter composeSameBaseNonIdentity(PowerOfIntConverter other) {
219                // no check for identity required
220                return new PowerOfIntConverter(this.base, this.exponent + other.exponent);
221        }
222
223        public RationalConverter toRationalConverter() {
224                return new RationalConverter(rationalFactor);
225        }
226
227    @Override
228    public Number getValue() {
229        return rationalFactor;
230    }
231
232}