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;
031
032import tech.units.indriya.format.SimpleUnitFormat;
033import tech.units.indriya.format.UnitStyle;
034import tech.uom.lib.common.function.Nameable;
035
036import javax.measure.Dimension;
037import javax.measure.Quantity;
038import javax.measure.Unit;
039import javax.measure.spi.SystemOfUnits;
040import java.util.*;
041import java.util.logging.Level;
042import java.util.logging.Logger;
043import java.util.stream.Collectors;
044
045import static tech.units.indriya.format.UnitStyle.*;
046
047/**
048 * <p>
049 * An abstract base class for unit systems.
050 * </p>
051 *
052 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
053 * @version 2.3, November 23, 2024
054 * @since 1.0
055 */
056public abstract class AbstractSystemOfUnits implements SystemOfUnits, Nameable {
057        protected static final Logger logger = Logger.getLogger(AbstractSystemOfUnits.class.getName());
058        
059        /**
060         * Holds the units.
061         */
062        protected final Set<Unit<?>> units = new HashSet<>();
063        
064        /**
065         * Holds the mapping quantity to unit.
066         */
067        @SuppressWarnings("rawtypes")
068        protected final Map<Class<? extends Quantity>, Unit> quantityToUnit = new HashMap<>();
069
070        /*
071         * (non-Javadoc)
072         *
073         * @see SystemOfUnits#getName()
074         */
075        public abstract String getName();
076
077        /////////////////////
078        // Collection View //
079        /////////////////////
080        @Override
081        public Set<Unit<?>> getUnits() {
082                return Collections.unmodifiableSet(units);
083        }
084
085        @Override
086        public Set<? extends Unit<?>> getUnits(Dimension dimension) {
087                return this.getUnits().stream().filter(unit -> dimension.equals(unit.getDimension()))
088                                .collect(Collectors.toSet());
089        }
090
091        /*
092         * (non-Javadoc)
093         *
094         * @see SystemOfUnits#getUnit()
095         */
096        @SuppressWarnings("unchecked")
097        @Override
098        public <Q extends Quantity<Q>> Unit<Q> getUnit(Class<Q> quantityType) {
099                return quantityToUnit.get(quantityType);
100        }
101
102        /*
103         * (non-Javadoc)
104         *
105         * @see SystemOfUnits#getUnit()
106         */
107        @Override
108        public Unit<?> getUnit(String string) {
109                Objects.requireNonNull(string);
110        return this.getUnits().stream()
111            .filter((u) -> string.equals(u.toString()))
112            .findAny()
113            .orElse(null);
114    }
115
116        /**
117         * Returns a unit with the given {@linkplain String string} representation in a
118         * particular {@linkplain UnitStyle style} or {@code null} if none is found in
119         * this unit system and requested style.
120         * <p>
121         * <b>NOTE:</b> Use {@code ignoreCase} carefully, as it will find the
122         * <b>FIRST</b> unit for a particular string, e.g. the symbol of {@code SECOND}
123         * and {@code SIEMENS} would be the same without case, but the UPPERCASE letter
124         * sorted first.
125         * </p>
126         *
127         * @param string     the string representation of a unit, not {@code null}.
128         * @param style      the style of unit representation.
129         * @param ignoreCase ignore the case or not?
130         * @return the unit with the given string representation.
131         * @since 2.0
132         */
133        public Unit<?> getUnit(String string, UnitStyle style, boolean ignoreCase) {
134                Objects.requireNonNull(string);
135                switch (style) {
136                        case NAME:
137                                if (ignoreCase) {
138                                        return this.getUnits().stream().filter((u) -> string.equalsIgnoreCase(u.getName())).findFirst()
139                                                        .orElse(null);
140                                } else {
141                                        return this.getUnits().stream().filter((u) -> string.equals(u.getName())).findFirst().orElse(null);
142                                }
143                        case SYMBOL:
144                                if (ignoreCase) {
145                                        return this.getUnits().stream().filter((u) -> string.equalsIgnoreCase(u.getSymbol())).findFirst()
146                                                        .orElse(null);
147                                } else {
148                                        return this.getUnits().stream().filter((u) -> string.equals(u.getSymbol())).findFirst().orElse(null);
149                                }
150                        default:
151                                return getUnit(string);
152                }
153        }
154
155        /**
156         * Returns a unit with the given {@linkplain String string} representation in a
157         * particular {@linkplain UnitStyle style} or {@code null} if none is found in
158         * this unit system and requested style.
159         *
160         * @param string the string representation of a unit, not {@code null}.
161         * @param style  the style of unit representation.
162         * @return the unit with the given string representation.
163         * @since 2.0
164         */
165        public Unit<?> getUnit(String string, UnitStyle style) {
166                return getUnit(string, style, false);
167        }
168
169        /**
170         * Static helper class.
171         *
172         * @since 1.0
173         */
174        protected static class Helper {
175                static Set<Unit<?>> getUnitsOfDimension(final Set<Unit<?>> units, Dimension dimension) {
176                        if (dimension != null) {
177                                return units.stream().filter(u -> dimension.equals(u.getDimension())).collect(Collectors.toSet());
178
179                        }
180                        return null;
181                }
182
183                /**
184                 * Adds a new named unit to a collection.
185                 *
186                 * @param units the collection to add to.
187                 * @param unit the unit being added.
188                 * @param name the name of the unit.
189                 * @return <code>unit</code>.
190                 * @since 1.0
191                 */
192                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, String name) {
193                        return addUnit(units, unit, name, NAME);
194                }
195
196                /**
197                 * Adds a new named unit to a collection.
198                 *
199                 * @param units the collection to add to.
200                 * @param unit the unit being added.
201                 * @param name the name of the unit.
202                 * @param symbol the symbol of the unit.
203                 * @return <code>unit</code>.
204                 * @since 1.0
205                 */
206                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, String name, String symbol) {
207                        return addUnit(units, unit, name, symbol, NAME_AND_SYMBOL);
208                }
209
210                /**
211                 * Adds a new named unit to a set.
212                 *               
213                 * @param units the set to add to.
214                 * @param unit  the unit being added.
215                 * @param name  the name of the unit.
216                 * @param symbol  the symbol of the unit.
217                 * @param style style of the unit.
218                 * @return <code>unit</code>.
219                 * @since 1.0.1
220                 */
221                @SuppressWarnings("unchecked")
222                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, final String name, final String symbol,
223                                                                                                        UnitStyle style) {
224                        switch (style) {
225                                case NAME:
226                                        if (name != null && unit instanceof AbstractUnit) {
227                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
228                                                aUnit.setName(name);
229                                                units.add(aUnit);
230                                                return (U) aUnit;
231                                        }
232                                        break;
233                                case NAME_AND_SYMBOL:
234                                case SYMBOL:
235                                        if (unit instanceof AbstractUnit) {
236                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
237                                                if (name != null && NAME_AND_SYMBOL.equals(style)) {
238                                                        aUnit.setName(name);
239                                                }
240                                                if (name != null && (SYMBOL.equals(style) || NAME_AND_SYMBOL.equals(style))) {
241                                                        aUnit.setSymbol(symbol);
242                                                }
243                                                units.add(aUnit);
244                                                return (U) aUnit;
245                                        }
246                                        break;
247                                case SYMBOL_AND_LABEL:
248                                        if (name != null && symbol != null && unit instanceof AbstractUnit) {
249                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
250                                                aUnit.setName(name);
251                                                if (SYMBOL.equals(style) || SYMBOL_AND_LABEL.equals(style)) {
252                                                        aUnit.setSymbol(symbol);
253                                                }
254                                                if (LABEL.equals(style) || SYMBOL_AND_LABEL.equals(style)) {
255                                                        SimpleUnitFormat.getInstance().label(unit, symbol);
256                                                }
257                                                units.add(aUnit);
258                                                return (U) aUnit;
259                                        }
260                                        break;
261                                default:
262                                        if (logger.isLoggable(Level.FINEST)) {
263                                                logger.log(Level.FINEST,
264                                                                "Unknown style " + style + "; unit " + unit + " can't be rendered with '" + symbol + "'.");
265                                        }
266                                        break;
267                        }
268                        if (LABEL.equals(style) || SYMBOL_AND_LABEL.equals(style)) {
269                                SimpleUnitFormat.getInstance().label(unit, symbol);
270                        }
271                        units.add(unit);
272                        return unit;
273                }
274
275                /**
276                 * Adds a new labeled unit to a set.
277                 *
278                 * @param units the set to add to.
279                 * @param unit  the unit being added.
280                 * @param text  the text for the unit.
281                 * @param style style of the unit.
282                 * @return <code>unit</code>.
283                 * @since 1.0.1
284                 */
285                @SuppressWarnings("unchecked")
286                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, String text, UnitStyle style) {
287                        switch (style) {
288                                case NAME:
289                                        if (text != null && unit instanceof AbstractUnit) {
290                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
291                                                aUnit.setName(text);
292                                                units.add(aUnit);
293                                                return (U) aUnit;
294                                        }
295                                        break;
296                                case SYMBOL:
297                                        if (text != null && unit instanceof AbstractUnit) {
298                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
299                                                aUnit.setSymbol(text);
300                                                units.add(aUnit);
301                                                return (U) aUnit;
302                                        }
303                                        break;
304                                case SYMBOL_AND_LABEL:
305                                        if (text != null && unit instanceof AbstractUnit) {
306                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
307                                                aUnit.setSymbol(text);
308                                                units.add(aUnit);
309                                                SimpleUnitFormat.getInstance().label(aUnit, text);
310                                                return (U) aUnit;
311                                        }
312                                        // label in any case, returning below
313                                        SimpleUnitFormat.getInstance().label(unit, text);
314                                        break;
315                                case LABEL:
316                                        SimpleUnitFormat.getInstance().label(unit, text);
317                                        break;
318                                default:
319                                        logger.log(Level.FINEST,
320                                                        "Unknown style " + style + "; unit " + unit + " can't be rendered with '" + text + "'.");
321                                        break;
322                        }
323                        units.add(unit);
324                        return unit;
325                }
326        }
327}