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