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