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.unit;
031
032import java.io.InvalidObjectException;
033import java.io.ObjectInputStream;
034import java.io.Serializable;
035import java.util.Arrays;
036import java.util.LinkedHashMap;
037import java.util.Map;
038import java.util.Objects;
039
040import javax.measure.Dimension;
041import javax.measure.Quantity;
042import javax.measure.Unit;
043import javax.measure.UnitConverter;
044
045import tech.units.indriya.AbstractUnit;
046import tech.units.indriya.function.AbstractConverter;
047import tech.units.indriya.internal.function.Lazy;
048
049/**
050 * <p>
051 * This class represents units formed by the product of rational powers of existing physical units.
052 * </p>
053 *
054 * <p>
055 * This class maintains the canonical form of this product (simplest form after factorization). For example: <code>METRE.pow(2).divide(METRE)</code>
056 * returns <code>METRE</code>.
057 * </p>
058 *
059 * @param <Q>
060 *            The type of the quantity measured by this unit.
061 *
062 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
063 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
064 * @author Andi Huber
065 * @version 2.0, November 21, 2020
066 * @since 1.0
067 */
068public final class ProductUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> {
069
070    /**
071     *
072     */
073    private static final long serialVersionUID = 962983585531030093L;
074
075    /**
076     * Holds the units composing this product unit.
077     * 
078     * <dl>
079     * <dt><span class="strong">Implementation Note:</span></dt><dd>considered immutable after constructor was called</dd>           
080     * </dl> 
081     */
082    private final Element[] elements;
083
084    /**
085     * DefaultQuantityFactory constructor (used solely to create <code>ONE</code> instance).
086     */
087    public ProductUnit() {
088        super("");
089        elements = new Element[0];
090    }
091
092    /**
093     * Copy constructor (allows for parameterization of product units).
094     *
095     * @param productUnit
096     *            the product unit source.
097     * @throws ClassCastException
098     *             if the specified unit is not a product unit.
099     */
100    public ProductUnit(Unit<?> productUnit) {
101        super(productUnit.getSymbol());
102        this.elements = ((ProductUnit<?>) productUnit).elements;
103    }
104
105    /**
106     * Product unit constructor.
107     *
108     * @param elements
109     *            the product elements.
110     */
111    private ProductUnit(Element[] elements) {
112        super(null);
113        this.elements = elements;
114    }
115
116    /**
117     * Returns the product of the specified units.
118     *
119     * @param left
120     *            the left unit operand.
121     * @param right
122     *            the right unit operand.
123     * @return <code>left * right</code>
124     */
125    public static Unit<?> ofProduct(Unit<?> left, Unit<?> right) {
126        Element[] leftElems;
127        if (left instanceof ProductUnit<?>) {
128            leftElems = ((ProductUnit<?>) left).elements;
129        } else {
130            leftElems = new Element[] { new Element(left, 1, 1) };
131        }
132        Element[] rightElems;
133        if (right instanceof ProductUnit<?>) {
134            rightElems = ((ProductUnit<?>) right).elements;
135        } else {
136            rightElems = new Element[] { new Element(right, 1, 1) };
137        }
138        return getInstance(leftElems, rightElems);
139    }
140
141    /**
142     * Returns the quotient of the specified units.
143     *
144     * @param left
145     *            the dividend unit operand.
146     * @param right
147     *            the divisor unit operand.
148     * @return <code>dividend / divisor</code>
149     */
150    public static Unit<?> ofQuotient(Unit<?> left, Unit<?> right) {
151        Element[] leftElems;
152        if (left instanceof ProductUnit<?>)
153            leftElems = ((ProductUnit<?>) left).elements;
154        else
155            leftElems = new Element[] { new Element(left, 1, 1) };
156        Element[] rightElems;
157        if (right instanceof ProductUnit<?>) {
158            Element[] elems = ((ProductUnit<?>) right).elements;
159            rightElems = new Element[elems.length];
160            for (int i = 0; i < elems.length; i++) {
161                rightElems[i] = new Element(elems[i].unit, -elems[i].pow, elems[i].root);
162            }
163        } else
164            rightElems = new Element[] { new Element(right, -1, 1) };
165        return getInstance(leftElems, rightElems);
166    }
167
168    /**
169     * Returns the product unit corresponding to the specified root of the specified unit.
170     *
171     * @param unit
172     *            the unit.
173     * @param n
174     *            the root's order (n &gt; 0).
175     * @return <code>unit^(1/nn)</code>
176     * @throws ArithmeticException
177     *             if <code>n == 0</code>.
178     */
179    public static Unit<?> ofRoot(Unit<?> unit, int n) {
180        Element[] unitElems;
181        if (unit instanceof ProductUnit<?>) {
182            Element[] elems = ((ProductUnit<?>) unit).elements;
183            unitElems = new Element[elems.length];
184            for (int i = 0; i < elems.length; i++) {
185                int gcd = gcd(Math.abs(elems[i].pow), elems[i].root * n);
186                unitElems[i] = new Element(elems[i].unit, elems[i].pow / gcd, elems[i].root * n / gcd);
187            }
188        } else
189            unitElems = new Element[] { new Element(unit, 1, n) };
190        return getInstance(unitElems, new Element[0]);
191    }
192
193    /**
194     * Returns the product unit corresponding to this unit raised to the specified exponent.
195     *
196     * @param unit
197     *            the unit.
198     * @param nn
199     *            the exponent (nn &gt; 0).
200     * @return <code>unit^n</code>
201     */
202    public static Unit<?> ofPow(Unit<?> unit, int n) {
203        Element[] unitElems;
204        if (unit instanceof ProductUnit<?>) {
205            Element[] elems = ((ProductUnit<?>) unit).elements;
206            unitElems = new Element[elems.length];
207            for (int i = 0; i < elems.length; i++) {
208                int gcd = gcd(Math.abs(elems[i].pow * n), elems[i].root);
209                unitElems[i] = new Element(elems[i].unit, elems[i].pow * n / gcd, elems[i].root / gcd);
210            }
211        } else
212            unitElems = new Element[] { new Element(unit, n, 1) };
213        return getInstance(unitElems, new Element[0]);
214    }
215
216    @Override
217    public Unit<?> pow(int n) {
218      return ofPow(this, n);
219    }
220
221    /**
222     * Returns the number of unit elements in this product.
223     *
224     * @return the number of unit elements.
225     */
226    public int getUnitCount() {
227        return elements.length;
228    }
229
230    /**
231     * Returns the unit element at the specified position.
232     *
233     * @param index
234     *            the index of the unit element to return.
235     * @return the unit element at the specified position.
236     * @throws IndexOutOfBoundsException
237     *             if index is out of range <code>(index &lt; 0 || index &gt;= getUnitCount())</code>.
238     */
239    public Unit<?> getUnit(int index) {
240        return elements[index].getUnit();
241    }
242
243    /**
244     * Returns the power exponent of the unit element at the specified position.
245     *
246     * @param index
247     *            the index of the unit element.
248     * @return the unit power exponent at the specified position.
249     * @throws IndexOutOfBoundsException
250     *             if index is out of range <code>(index &lt; 0 || index &gt;= getUnitCount())</code>.
251     */
252    public int getUnitPow(int index) {
253        return elements[index].getPow();
254    }
255
256    /**
257     * Returns the root exponent of the unit element at the specified position.
258     *
259     * @param index
260     *            the index of the unit element.
261     * @return the unit root exponent at the specified position.
262     * @throws IndexOutOfBoundsException
263     *             if index is out of range <code>(index &lt; 0 || index &gt;= getUnitCount())</code>.
264     */
265    public int getUnitRoot(int index) {
266        return elements[index].getRoot();
267    }
268
269    @Override
270    public Map<Unit<?>, Integer> getBaseUnits() {
271        final Map<Unit<?>, Integer> units = new LinkedHashMap<>();
272        for (int i = 0; i < getUnitCount(); i++) {
273            units.put(getUnit(i), getUnitPow(i));
274        }
275        return units;
276    }
277
278    @Override
279    public boolean equals(Object obj) {
280        if (this == obj) {
281            return true;
282        }
283        if (obj instanceof ProductUnit<?>) {
284            final ProductUnit<?> other = ((ProductUnit<?>) obj); 
285            return ElementUtil.arrayEqualsArbitraryOrder(this.elements, other.elements);
286        }
287        return false;
288    }
289
290    // thread safe cache for the expensive hashCode calculation 
291    private transient Lazy<Integer> hashCode = new Lazy<>(this::calculateHashCode); 
292    private int calculateHashCode() {
293        return Objects.hash((Object[]) ElementUtil.copyAndSort(elements));
294    }
295    
296    @Override
297    public int hashCode() {
298        return hashCode.get(); // lazy and thread-safe
299    }
300
301    @SuppressWarnings("unchecked")
302    @Override
303    public Unit<Q> toSystemUnit() {
304        Unit<?> systemUnit = AbstractUnit.ONE;
305        for (Element element : elements) {
306            Unit<?> unit = element.unit.getSystemUnit();
307            unit = unit.pow(element.pow);
308            unit = unit.root(element.root);
309            systemUnit = systemUnit.multiply(unit);
310        }
311        return (AbstractUnit<Q>) systemUnit;
312    }
313
314    @Override
315    public UnitConverter getSystemConverter() {
316        UnitConverter converter = AbstractConverter.IDENTITY;
317        for (Element e : elements) {
318            if (e.unit instanceof AbstractUnit) {
319                UnitConverter cvtr = ((AbstractUnit<?>) e.unit).getSystemConverter();
320                if (!(cvtr.isLinear()))
321                    throw new UnsupportedOperationException(e.unit + " is non-linear, cannot convert");
322                if (e.root != 1)
323                    throw new UnsupportedOperationException(e.unit + " holds a base unit with fractional exponent");
324                int pow = e.pow;
325                if (pow < 0) { // Negative power.
326                    pow = -pow;
327                    cvtr = cvtr.inverse();
328                }
329                for (int j = 0; j < pow; j++) {
330                    converter = converter.concatenate(cvtr);
331                }
332            }
333        }
334        return converter;
335    }
336
337    @Override
338    public Dimension getDimension() {
339        Dimension dimension = UnitDimension.NONE;
340        for (int i = 0; i < this.getUnitCount(); i++) {
341            Unit<?> unit = this.getUnit(i);
342            if (this.elements != null && unit.getDimension() != null) {
343                Dimension d = unit.getDimension().pow(this.getUnitPow(i)).root(this.getUnitRoot(i));
344                dimension = dimension.multiply(d);
345            }
346        }
347        return dimension;
348    }
349
350    /**
351     * Returns the unit defined from the product of the specified elements.
352     *
353     * @param leftElems
354     *            left multiplicand elements.
355     * @param rightElems
356     *            right multiplicand elements.
357     * @return the corresponding unit.
358     */
359    @SuppressWarnings("rawtypes")
360    private static Unit<?> getInstance(Element[] leftElems, Element[] rightElems) {
361
362        // Merges left elements with right elements.
363        Element[] result = new Element[leftElems.length + rightElems.length];
364        int resultIndex = 0;
365        for (Element leftElem : leftElems) {
366            Unit<?> unit = leftElem.unit;
367            int p1 = leftElem.pow;
368            int r1 = leftElem.root;
369            int p2 = 0;
370            int r2 = 1;
371            for (Element rightElem : rightElems) {
372                if (unit.equals(rightElem.unit)) {
373                    p2 = rightElem.pow;
374                    r2 = rightElem.root;
375                    break; // No duplicate.
376                }
377            }
378            int pow = p1 * r2 + p2 * r1;
379            int root = r1 * r2;
380            if (pow != 0) {
381                int gcd = gcd(Math.abs(pow), root);
382                result[resultIndex++] = new Element(unit, pow / gcd, root / gcd);
383            }
384        }
385
386        // Appends remaining right elements not merged.
387        for (Element rightElem : rightElems) {
388            Unit<?> unit = rightElem.unit;
389            boolean hasBeenMerged = false;
390            for (Element leftElem : leftElems) {
391                if (unit.equals(leftElem.unit)) {
392                    hasBeenMerged = true;
393                    break;
394                }
395            }
396            if (!hasBeenMerged)
397                result[resultIndex++] = rightElem;
398        }
399
400        // Returns or creates instance.
401        if (resultIndex == 0)
402            return AbstractUnit.ONE;
403        else if (resultIndex == 1 && result[0].pow == result[0].root)
404            return result[0].unit;
405        else {
406            Element[] elems = new Element[resultIndex];
407            System.arraycopy(result, 0, elems, 0, resultIndex);
408            return new ProductUnit(elems);
409        }
410    }
411
412    /**
413     * Returns the greatest common divisor (Euclid's algorithm).
414     *
415     * @param m
416     *            the first number.
417     * @param nn
418     *            the second number.
419     * @return the greatest common divisor.
420     */
421    private static int gcd(int m, int n) {
422        return n == 0 ? m : gcd(n, m % n);
423    }
424
425    /**
426     * Inner product element represents a rational power of a single unit.
427     */
428    private final static class Element implements Serializable {
429
430        /**
431         *
432         */
433        private static final long serialVersionUID = 452938412398890507L;
434
435        /**
436         * Holds the single unit.
437         */
438        private final Unit<?> unit;
439
440        /**
441         * Holds the power exponent.
442         */
443        private final int pow;
444
445        /**
446         * Holds the root exponent.
447         */
448        private final int root;
449
450        /**
451         * Structural constructor.
452         *
453         * @param unit
454         *            the unit.
455         * @param pow
456         *            the power exponent.
457         * @param root
458         *            the root exponent.
459         */
460        private Element(Unit<?> unit, int pow, int root) {
461            this.unit = unit;
462            this.pow = pow;
463            this.root = root;
464        }
465
466        /**
467         * Returns this element's unit.
468         *
469         * @return the single unit.
470         */
471        public Unit<?> getUnit() {
472            return unit;
473        }
474
475        /**
476         * Returns the power exponent. The power exponent can be negative but is always different from zero.
477         *
478         * @return the power exponent of the single unit.
479         */
480        public int getPow() {
481            return pow;
482        }
483
484        /**
485         * Returns the root exponent. The root exponent is always greater than zero.
486         *
487         * @return the root exponent of the single unit.
488         */
489        public int getRoot() {
490            return root;
491        }
492
493        @Override
494        public boolean equals(Object o) {
495            if (this == o)
496                return true;
497            if (o == null || getClass() != o.getClass())
498                return false;
499
500            final Element other = (Element) o;
501
502            if (!Objects.equals(this.pow, other.pow)) {
503                return false;
504            }
505            if (!Objects.equals(this.root, other.root)) {
506                return false;
507            }
508            return Objects.equals(this.unit, other.unit);
509        }
510
511        @Override
512        public int hashCode() {
513            return Objects.hash(unit, pow, root);
514        }
515    }
516
517    // Element specific algorithms provided locally to this class
518    private final static class ElementUtil {
519        
520        // -- returns a defensive sorted copy, unless size <= 1 
521        private static Element[] copyAndSort(final Element[] elements) {
522            if (elements == null || elements.length <= 1) {
523                return elements;
524            }
525            final Element[] elementsSorted = Arrays.copyOf(elements, elements.length);
526            Arrays.sort(elementsSorted, ElementUtil::compare);
527            return elementsSorted;
528        }
529        
530        private static int compare(final Element e0, final Element e1) {
531            final Unit<?> sysUnit0 = e0.getUnit().getSystemUnit();
532            final Unit<?> sysUnit1 = e1.getUnit().getSystemUnit();
533            final String symbol0 = sysUnit0.getSymbol();
534            final String symbol1 = sysUnit1.getSymbol();
535            
536            if (symbol0 != null && symbol1 != null) {
537                return symbol0.compareTo(symbol1);
538            } else {
539                return sysUnit0.toString().compareTo(sysUnit1.toString());
540            }
541        }
542        
543        // optimized for the fact, that can only return true, if for each element in e0 there exist a single match in e1
544        private static boolean arrayEqualsArbitraryOrder(final Element[] e0, final Element[] e1) {
545            if (e0.length != e1.length) {
546                return false;
547            }
548            for (Element left : e0) {
549                boolean unitFound = false;
550                for (Element right : e1) {
551                    if (left.unit.equals(right.unit)) {
552                        if (left.pow != right.pow || left.root != right.root) {
553                            return false;
554                        } else {
555                            unitFound = true;
556                            break;
557                        }
558                    }
559                }
560                if (!unitFound) {
561                    return false;
562                }
563            }
564            return true;
565        }
566    }
567    
568    
569    // -- SERIALIZATION PROXY
570    
571    // Chapter 12. Serialization
572    // Bloch, Joshua. Effective Java (p. 339). Pearson Education. 
573    
574    private Object writeReplace() {
575        return new SerializationProxy(this);
576    }
577
578    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
579        throw new InvalidObjectException("Proxy required");
580    }
581
582    private static class SerializationProxy implements Serializable {
583        private static final long serialVersionUID = 1L;
584        private final Element[] elements;
585        private final String symbol;
586        
587        private SerializationProxy(ProductUnit<?> productUnit) {
588            this.elements = productUnit.elements;
589            this.symbol = productUnit.getSymbol();
590        }
591
592        private Object readResolve() {
593            ProductUnit<?> pu = new ProductUnit<>(elements);
594            pu.setSymbol(symbol);
595            return pu;
596        }
597    }
598    
599}