001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2021, 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;
034import java.util.function.DoubleSupplier;
035
036import javax.measure.UnitConverter;
037
038import tech.units.indriya.internal.function.Calculator;
039
040/**
041 * <p>
042 * This class represents a converter multiplying numeric values by an exact scaling factor (represented as the quotient of two <code>BigInteger</code>
043 * numbers).
044 * </p>
045 *
046 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
047 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
048 * @author Andi Huber
049 * @version 2.0, October 8, 2020
050 * @since 1.0
051 */
052public final class RationalConverter extends AbstractConverter 
053implements MultiplyConverter {
054
055        /**
056     * 
057     */
058    private static final long serialVersionUID = -9192231963353351648L;
059
060        /**
061     * Holds the scale factor.
062     */
063        private final RationalNumber factor;
064
065        /**
066     * Creates a rational converter with the specified scale factor.
067     *
068     * @param factor
069     *          the scale factor.
070     * @throws NullPointerException
071     *           if factor is {@code null}
072     */
073    RationalConverter(RationalNumber factor) {
074        Objects.requireNonNull(factor);
075        this.factor = factor;
076    }
077    
078        /**
079         * Creates a rational converter with the specified dividend and divisor.
080         *
081         * @param dividend
082         *          the dividend.
083         * @param divisor
084         *          the non-zero divisor.
085         * @throws IllegalArgumentException
086         *           if <code>divisor = 0</code>
087         * @throws NullPointerException
088         *           if dividend is {@code null} or divisor is {@code null}
089         */
090        RationalConverter(BigInteger dividend, BigInteger divisor) {
091            factor = RationalNumber.of(dividend, divisor);
092        }
093
094        /**
095         * Convenience method equivalent to <code>new RationalConverter(BigInteger.valueOf(dividend), BigInteger.valueOf(divisor))</code>
096         *
097         * @param dividend
098         *          the dividend.
099         * @param divisor
100         *          the positive divisor.
101         * @throws IllegalArgumentException
102         *           if <code>divisor = 0</code>
103         */
104        RationalConverter(long dividend, long divisor) {
105            factor = RationalNumber.of(dividend, divisor);
106        }
107
108        /**
109     * Creates a rational converter with the specified scale factor.
110     *
111     * @param factor
112     *          the scale factor.
113     * @throws NullPointerException
114     *           if factor is {@code null}
115     */
116    static RationalConverter of(RationalNumber factor) {
117        return new RationalConverter(factor);
118    }
119        
120        /**
121         * Convenience method equivalent to <code>new RationalConverter(dividend, divisor)</code>
122         *
123         * @param dividend
124         *          the dividend.
125         * @param divisor
126         *          the positive divisor.
127         * @throws IllegalArgumentException
128         *           if <code>divisor = 0</code>
129     * @throws NullPointerException
130     *           if dividend is {@code null} or divisor is {@code null}
131         */
132        static RationalConverter of(BigInteger dividend, BigInteger divisor) {
133                return new RationalConverter(dividend, divisor);
134        }
135
136        /**
137         * Convenience method equivalent to <code>new RationalConverter(dividend, divisor)</code>
138         *
139         * @param dividend
140         *          the dividend.
141         * @param divisor
142         *          the positive divisor.
143         * @throws IllegalArgumentException
144         *           if <code>divisor = 0</code>
145         */
146        static RationalConverter of(long dividend, long divisor) {
147                return new RationalConverter(dividend, divisor);
148        }
149
150        /**
151         * Returns the integer dividend for this rational converter.
152         *
153         * @return this converter dividend.
154         */
155        public BigInteger getDividend() {
156                return factor.getDividend();
157        }
158
159        /**
160         * Returns the integer (positive) divisor for this rational converter.
161         *
162         * @return this converter divisor.
163         */
164        public BigInteger getDivisor() {
165                return factor.getDivisor();
166        }
167
168    @Override
169    protected Number convertWhenNotIdentity(Number value) {
170        return Calculator.of(factor)
171              .multiply(value)
172              .peek();
173    }
174        
175        @Override
176        public boolean isIdentity() {
177                return factor.compareTo(RationalNumber.ONE)==0;
178        }
179
180        @Override
181        protected boolean canReduceWith(AbstractConverter that) {
182                if (that instanceof RationalConverter) {
183                        return true; 
184                }
185                return that instanceof PowerOfIntConverter;
186        }
187
188        @Override
189        protected AbstractConverter reduce(AbstractConverter that) {
190                if (that instanceof RationalConverter) {
191                        return composeSameType((RationalConverter) that); 
192                }
193                if (that instanceof PowerOfIntConverter) {
194                        return composeSameType(((PowerOfIntConverter) that).toRationalConverter()); 
195                }
196                throw new IllegalStateException(String.format(
197                                "%s.simpleCompose() not handled for converter %s", 
198                                this, that));
199        }
200
201
202        @Override
203        protected RationalConverter inverseWhenNotIdentity() {
204                return RationalConverter.of(factor.reciprocal());
205        }
206
207        @Override
208        protected final String transformationLiteral() {
209                return String.format("x -> x * %s", factor);
210        }
211
212        @Override
213        public boolean equals(Object obj) {
214                if (this == obj) {
215                        return true;
216                }
217                if (obj instanceof RationalConverter) {
218                        RationalConverter that = (RationalConverter) obj;
219                        return Objects.equals(this.factor, that.factor);
220                }
221                return false;
222        }
223
224        @Override
225        public int hashCode() {
226                return factor.hashCode();
227        }
228
229        @Override
230        public Number getValue() {
231                return factor;
232        }
233        
234        @Override
235        public double getAsDouble() {
236                return factor.doubleValue();
237        }
238
239        @Override
240        public int compareTo(UnitConverter o) {
241                if (this == o) {
242                        return 0;
243                }
244                if (o instanceof RationalConverter) {
245                    RationalConverter that = (RationalConverter) o;
246                        return this.factor.compareTo(that.factor);
247                }
248                return this.getClass().getName().compareTo(o.getClass().getName());
249        }
250
251        // -- HELPER
252
253        private AbstractConverter composeSameType(RationalConverter that) {
254                BigInteger newDividend = this.getDividend().multiply(that.getDividend());
255                BigInteger newDivisor = this.getDivisor().multiply(that.getDivisor());
256                BigInteger gcd = newDividend.gcd(newDivisor);
257                newDividend = newDividend.divide(gcd);
258                newDivisor = newDivisor.divide(gcd);
259                return (newDividend.equals(BigInteger.ONE) && newDivisor.equals(BigInteger.ONE)) 
260                                ? IDENTITY 
261                                                : new RationalConverter(newDividend, newDivisor);
262        }
263}