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