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