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.unit;
031
032import javax.measure.Dimension;
033import javax.measure.Quantity;
034import javax.measure.Unit;
035
036import tech.units.indriya.AbstractUnit;
037import java.io.Serializable;
038import java.util.HashMap;
039import java.util.Map;
040import java.util.Objects;
041import java.util.logging.Level;
042import java.util.logging.Logger;
043
044/**
045 * <p>
046 * This class represents a dimension of a unit of measurement.
047 * </p>
048 *
049 * <p>
050 * The dimension associated to any given quantity are given by the published
051 * {@link Dimension} instances. For convenience, a static method
052 * <code>UnitDimension.of(Class)</code> aggregating the results of all
053 * 
054 * {@link Dimension} instances is provided.<br>
055 * <br>
056 * <code>
057 *        Dimension speedDimension
058 *            = UnitDimension.of(Speed.class);
059 *     </code>
060 * </p>
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  Martin Desruisseaux (Geomatys)
065 * @author  Andi Huber
066 * @version 2.1, $Date: 2021-03-13 $
067 * @since 2.0
068 */
069public class UnitDimension implements Dimension, Serializable {
070    /** */
071        private static final long serialVersionUID = 7806787530512644696L;
072
073        private static final Logger LOGGER = Logger.getLogger(UnitDimension.class.getName());
074
075        /**
076         * Holds dimensionless.
077         * 
078         * @since 1.0
079         */
080        public static final Dimension NONE = new UnitDimension(AbstractUnit.ONE);
081
082        /**
083         * Holds length dimension (L).
084         * 
085         * @since 1.0
086         */
087        public static final Dimension LENGTH = new UnitDimension('L');
088
089        /**
090         * Holds mass dimension (M).
091         * 
092         * @since 1.0
093         */
094        public static final Dimension MASS = new UnitDimension('M');
095
096        /**
097         * Holds time dimension (T).
098         * 
099         * @since 1.0
100         */
101        public static final Dimension TIME = new UnitDimension('T');
102
103        /**
104         * Holds electric current dimension (I).
105         * 
106         * @since 1.0
107         */
108        public static final Dimension ELECTRIC_CURRENT = new UnitDimension('I');
109
110        /**
111         * Holds temperature dimension (Θ).
112         * 
113         * @since 1.0
114         */
115        public static final Dimension TEMPERATURE = new UnitDimension('\u0398');
116
117        /**
118         * Holds amount of substance dimension (N).
119         * 
120         * @since 1.0
121         */
122        public static final Dimension AMOUNT_OF_SUBSTANCE = new UnitDimension('N');
123
124        /**
125         * Holds luminous intensity dimension (J).
126         */
127        public static final Dimension LUMINOUS_INTENSITY = new UnitDimension('J');
128
129        /**
130         * Holds the pseudo unit associated to this dimension.
131         */
132        private final Unit<?> pseudoUnit;
133
134        /**
135         * Returns the dimension for the specified quantity type by aggregating the
136         * results from the default {@link javax.measure.spi.SystemOfUnits SystemOfUnits} or <code>null</code> if the specified
137         * quantity is unknown.
138         *
139         * @param quantityType the quantity type.
140         * @return the dimension for the quantity type or <code>null</code>.
141         * @since 1.1
142         */
143        public static <Q extends Quantity<Q>> Dimension of(Class<Q> quantityType) {
144                // TODO: Track services and aggregate results (register custom types)
145                Unit<Q> siUnit = Units.getInstance().getUnit(quantityType);
146                if (siUnit == null && LOGGER.isLoggable(Level.FINE)) {
147                        LOGGER.log(Level.FINE, "Quantity type: " + quantityType + " unknown");
148                }
149                return (siUnit != null) ? siUnit.getDimension() : null;
150        }
151
152        /**
153         * Returns the dimension for the specified symbol.
154         *
155         * @param sambol the quantity symbol.
156         * @return the dimension for the given symbol.
157         * @since 1.0.1
158         */
159        public static Dimension parse(char symbol) {
160                return new UnitDimension(symbol);
161        }
162
163        /**
164         * Returns the unit dimension having the specified symbol.
165         *
166         * @param symbol the associated symbol.
167         */
168        @SuppressWarnings("rawtypes")
169        private UnitDimension(char symbol) {
170                pseudoUnit = new BaseUnit("[" + symbol + ']', NONE);
171        }
172
173        /**
174         * Constructor from pseudo-unit (not visible).
175         *
176         * @param pseudoUnit the pseudo-unit.
177         */
178        private UnitDimension(Unit<?> pseudoUnit) {
179                this.pseudoUnit = pseudoUnit;
180        }
181        
182        /**
183         * Default Constructor (not visible).
184         *
185         */
186        protected UnitDimension() {
187                this(AbstractUnit.ONE);
188        }
189        
190
191        /**
192         * Returns the product of this dimension with the one specified. 
193         * If the specified dimension is not a <code>UnitDimension</code>, then
194         * <code>that.multiply(this)</code> is returned.
195         *
196         * @param that the dimension multiplicand.
197         * @return <code>this * that</code>
198         * @since 1.0
199         */
200        public Dimension multiply(Dimension that) {
201                return that instanceof UnitDimension
202                        ? this.multiply((UnitDimension) that)
203                : that.multiply(this);
204        }
205
206        /**
207         * Returns the product of this dimension with the one specified.
208         *
209         * @param that the dimension multiplicand.
210         * @return <code>this * that</code>
211         * @since 1.0
212         */
213        private UnitDimension multiply(UnitDimension that) {
214                return new UnitDimension(this.pseudoUnit.multiply(that.pseudoUnit));
215        }
216
217        /**
218         * Returns the quotient of this dimension with the one specified.
219         * If the specified dimension is not a <code>UnitDimension</code>, then
220     * <code>that.divide(this).pow(-1)</code> is returned.
221         *
222         * @param that the dimension divisor.
223         * @return <code>this / that</code>
224         * @since 1.0
225         */
226        public Dimension divide(Dimension that) {
227                return that instanceof UnitDimension
228                        ? this.divide((UnitDimension) that)
229                : that.divide(this).pow(-1);
230        }
231
232        /**
233         * Returns the quotient of this dimension with the one specified.
234         *
235         * @param that the dimension divisor.
236         * @return <code>this / that</code>
237         * @since 1.0
238         */
239        private UnitDimension divide(UnitDimension that) {
240                return new UnitDimension(ProductUnit.ofQuotient(pseudoUnit, that.pseudoUnit));
241        }
242
243        /**
244         * Returns this dimension raised to an exponent.
245         *
246         * @param n the exponent.
247         * @return the result of raising this dimension to the exponent.
248         * @since 1.0
249         */
250        public UnitDimension pow(int n) {
251                return new UnitDimension(this.pseudoUnit.pow(n));
252        }
253
254        /**
255         * Returns the given root of this dimension.
256         *
257         * @param n the root's order.
258         * @return the result of taking the given root of this dimension.
259         * @throws ArithmeticException if <code>n == 0</code>.
260         * @since 1.0
261         */
262        public UnitDimension root(int n) {
263                return new UnitDimension(this.pseudoUnit.root(n));
264        }
265
266        /**
267         * Returns the fundamental (base) dimensions and their exponent whose product is
268         * this dimension or <code>null</code> if this dimension is a fundamental
269         * dimension.
270         *
271         * @return the mapping between the base dimensions and their exponent.
272         * @since 1.0
273         */
274        @SuppressWarnings("rawtypes")
275        public Map<? extends Dimension, Integer> getBaseDimensions() {
276                Map<? extends Unit, Integer> pseudoUnits = pseudoUnit.getBaseUnits();
277                if (pseudoUnits == null) {
278                        return null;
279                }
280                final Map<UnitDimension, Integer> baseDimensions = new HashMap<>();
281                for (Map.Entry<? extends Unit, Integer> entry : pseudoUnits.entrySet()) {
282                        baseDimensions.put(new UnitDimension(entry.getKey()), entry.getValue());
283                }
284                return baseDimensions;
285        }
286
287        @Override
288        public String toString() {
289                return pseudoUnit.toString();
290        }
291
292        @Override
293        public boolean equals(Object obj) {
294                if (this == obj) {
295                        return true;
296                }
297                if (obj instanceof UnitDimension) {
298                        UnitDimension other = (UnitDimension) obj;
299                        return Objects.equals(pseudoUnit, other.pseudoUnit);
300                }
301                return false;
302        }
303
304        @Override
305        public int hashCode() {
306                return Objects.hashCode(pseudoUnit);
307        }
308}