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.0, Feb 18, 2020
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().filter((u) -> string.equals(u.toString())).findAny().orElse(null);
113        }
114
115        /**
116         * <p>
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         * <p>
122         * <b>NOTE:</b> Use {@code ignoreCase} carefully, as it will find the
123         * <b>FIRST</b> unit for a particular string, e.g. the symbol of {@code SECOND}
124         * and {@code SIEMENS} would be the same without case, but the UPPERCASE letter
125         * sorted first.
126         * </p>
127         *
128         * @param string     the string representation of a unit, not {@code null}.
129         * @param style      the style of unit representation.
130         * @param ignoreCase ignore the case or not?
131         * @return the unit with the given string representation.
132         * @since 2.0
133         */
134        public Unit<?> getUnit(String string, UnitStyle style, boolean ignoreCase) {
135                Objects.requireNonNull(string);
136                switch (style) {
137                        case NAME:
138                                if (ignoreCase) {
139                                        return this.getUnits().stream().filter((u) -> string.equalsIgnoreCase(u.getName())).findFirst()
140                                                        .orElse(null);
141                                } else {
142                                        return this.getUnits().stream().filter((u) -> string.equals(u.getName())).findFirst().orElse(null);
143                                }
144                        case SYMBOL:
145                                if (ignoreCase) {
146                                        return this.getUnits().stream().filter((u) -> string.equalsIgnoreCase(u.getSymbol())).findFirst()
147                                                        .orElse(null);
148                                } else {
149                                        return this.getUnits().stream().filter((u) -> string.equals(u.getSymbol())).findFirst().orElse(null);
150                                }
151                        default:
152                                return getUnit(string);
153                }
154        }
155
156        /**
157         * Returns a unit with the given {@linkplain String string} representation in a
158         * particular {@linkplain UnitStyle style} or {@code null} if none is found in
159         * this unit system and requested style.
160         *
161         * @param string the string representation of a unit, not {@code null}.
162         * @param style  the style of unit representation.
163         * @return the unit with the given string representation.
164         * @since 2.0
165         */
166        public Unit<?> getUnit(String string, UnitStyle style) {
167                return getUnit(string, style, false);
168        }
169
170        protected static class Helper {
171                static Set<Unit<?>> getUnitsOfDimension(final Set<Unit<?>> units, Dimension dimension) {
172                        if (dimension != null) {
173                                return units.stream().filter(u -> dimension.equals(u.getDimension())).collect(Collectors.toSet());
174
175                        }
176                        return null;
177                }
178
179                /**
180                 * Adds a new named unit to the collection.
181                 *
182                 * @param unit the unit being added.
183                 * @param name the name of the unit.
184                 * @return <code>unit</code>.
185                 * @since 1.0
186                 */
187                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, String name) {
188                        return addUnit(units, unit, name, NAME);
189                }
190
191                /**
192                 * Adds a new named unit to the collection.
193                 *
194                 * @param unit the unit being added.
195                 * @param name the name of the unit.
196                 * @param name the symbol of the unit.
197                 * @return <code>unit</code>.
198                 * @since 1.0
199                 */
200                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, String name, String symbol) {
201                        return addUnit(units, unit, name, symbol, NAME_AND_SYMBOL);
202                }
203
204                /**
205                 * Adds a new named unit to the collection.
206                 *
207                 * @param unit  the unit being added.
208                 * @param name  the name of the unit.
209                 * @param name  the symbol of the unit.
210                 * @param style style of the unit.
211                 * @return <code>unit</code>.
212                 * @since 1.0.1
213                 */
214                @SuppressWarnings("unchecked")
215                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, final String name, final String symbol,
216                                                                                                        UnitStyle style) {
217                        switch (style) {
218                                case NAME:
219                                        if (name != null && unit instanceof AbstractUnit) {
220                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
221                                                aUnit.setName(name);
222                                                units.add(aUnit);
223                                                return (U) aUnit;
224                                        }
225                                        break;
226                                case NAME_AND_SYMBOL:
227                                case SYMBOL:
228                                        if (unit instanceof AbstractUnit) {
229                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
230                                                if (name != null && NAME_AND_SYMBOL.equals(style)) {
231                                                        aUnit.setName(name);
232                                                }
233                                                if (name != null && (SYMBOL.equals(style) || NAME_AND_SYMBOL.equals(style))) {
234                                                        aUnit.setSymbol(symbol);
235                                                }
236                                                units.add(aUnit);
237                                                return (U) aUnit;
238                                        }
239                                        break;
240                                case SYMBOL_AND_LABEL:
241                                        if (name != null && symbol != null && unit instanceof AbstractUnit) {
242                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
243                                                aUnit.setName(name);
244                                                if (SYMBOL.equals(style) || SYMBOL_AND_LABEL.equals(style)) {
245                                                        aUnit.setSymbol(symbol);
246                                                }
247                                                if (LABEL.equals(style) || SYMBOL_AND_LABEL.equals(style)) {
248                                                        SimpleUnitFormat.getInstance().label(unit, symbol);
249                                                }
250                                                units.add(aUnit);
251                                                return (U) aUnit;
252                                        }
253                                        break;
254                                default:
255                                        if (logger.isLoggable(Level.FINEST)) {
256                                                logger.log(Level.FINEST,
257                                                                "Unknown style " + style + "; unit " + unit + " can't be rendered with '" + symbol + "'.");
258                                        }
259                                        break;
260                        }
261                        if (LABEL.equals(style) || SYMBOL_AND_LABEL.equals(style)) {
262                                SimpleUnitFormat.getInstance().label(unit, symbol);
263                        }
264                        units.add(unit);
265                        return unit;
266                }
267
268                /**
269                 * Adds a new labeled unit to the set.
270                 *
271                 * @param units the set to add to.
272                 * @param unit  the unit being added.
273                 * @param text  the text for the unit.
274                 * @param style style of the unit.
275                 * @return <code>unit</code>.
276                 * @since 1.0.1
277                 */
278                @SuppressWarnings("unchecked")
279                public static <U extends Unit<?>> U addUnit(Set<Unit<?>> units, U unit, String text, UnitStyle style) {
280                        switch (style) {
281                                case NAME:
282                                        if (text != null && unit instanceof AbstractUnit) {
283                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
284                                                aUnit.setName(text);
285                                                units.add(aUnit);
286                                                return (U) aUnit;
287                                        }
288                                        break;
289                                case SYMBOL:
290                                        if (text != null && unit instanceof AbstractUnit) {
291                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
292                                                aUnit.setSymbol(text);
293                                                units.add(aUnit);
294                                                return (U) aUnit;
295                                        }
296                                        break;
297                                case SYMBOL_AND_LABEL:
298                                        if (text != null && unit instanceof AbstractUnit) {
299                                                AbstractUnit<?> aUnit = (AbstractUnit<?>) unit;
300                                                aUnit.setSymbol(text);
301                                                units.add(aUnit);
302                                                SimpleUnitFormat.getInstance().label(aUnit, text);
303                                                return (U) aUnit;
304                                        }
305                                        // label in any case, returning below
306                                        SimpleUnitFormat.getInstance().label(unit, text);
307                                        break;
308                                case LABEL:
309                                        SimpleUnitFormat.getInstance().label(unit, text);
310                                        break;
311                                default:
312                                        logger.log(Level.FINEST,
313                                                        "Unknown style " + style + "; unit " + unit + " can't be rendered with '" + text + "'.");
314                                        break;
315                        }
316                        units.add(unit);
317                        return unit;
318                }
319        }
320}