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.math.BigInteger;
033import java.util.Objects;
034
035import javax.measure.Prefix;
036import javax.measure.UnitConverter;
037
038import tech.units.indriya.internal.function.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 2.0, Oct 8, 2020
047 * @since 2.0
048 */
049// As it's used in the "format" package, we may not be able to make it package-private here
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 Number getValue() {
183        return rationalFactor;
184    }
185    
186        @Override
187        public double getAsDouble() {
188                return rationalFactor.doubleValue();
189        }
190
191        @Override
192        public int compareTo(UnitConverter o) {
193                if (this == o) {
194                        return 0;
195                }
196                if(this.isIdentity() && o.isIdentity()) {
197                        return 0;
198                }
199                if (o instanceof PowerOfIntConverter) {
200                        PowerOfIntConverter other = (PowerOfIntConverter) o;
201                        int c = Integer.compare(base, other.base);
202                        if(c!=0) {
203                                return c;
204                        }
205                        return Integer.compare(exponent, other.exponent);
206                }
207                return this.getClass().getName().compareTo(o.getClass().getName());
208        }
209
210        @Override
211        public int hashCode() {
212                return hashCode;
213        }
214
215        // -- HELPER
216        
217        private RationalNumber calculateRationalNumberFactor() {
218        if(exponent==0) {
219            return RationalNumber.ONE;
220        }
221        BigInteger bintFactor = BigInteger.valueOf(base).pow(Math.abs(exponent));
222        if(exponent>0) {
223            return RationalNumber.ofInteger(bintFactor);
224        }
225        return RationalNumber.of(BigInteger.ONE, bintFactor);
226    }
227
228        private PowerOfIntConverter composeSameBaseNonIdentity(PowerOfIntConverter other) {
229                // no check for identity required
230                return new PowerOfIntConverter(this.base, this.exponent + other.exponent);
231        }
232
233        public RationalConverter toRationalConverter() {
234                return new RationalConverter(rationalFactor);
235        }
236}