001/*
002 * Units of Measurement Implementation for Java SE
003 * Copyright (c) 2005-2017, Jean-Marie Dautelle, Werner Keil, V2COM.
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-363 nor the names of its 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 tec.uom.se.format;
031
032import tec.uom.se.AbstractConverter;
033import tec.uom.se.AbstractUnit;
034import tec.uom.se.unit.MetricPrefix;
035
036import javax.measure.Unit;
037import javax.measure.UnitConverter;
038
039import java.lang.reflect.Field;
040import java.util.Collections;
041import java.util.Comparator;
042import java.util.Enumeration;
043import java.util.HashMap;
044import java.util.List;
045import java.util.Map;
046import java.util.ResourceBundle;
047import java.util.TreeMap;
048import java.util.logging.Level;
049import java.util.logging.Logger;
050import java.util.stream.Collectors;
051
052/**
053 * <p>
054 * This class provides a set of mappings between {@link AbstractUnit units} and symbols (both ways), between {@link MetricPrefix prefixes} and symbols
055 * (both ways), and from {@link AbstractConverter unit converters} to {@link MetricPrefix prefixes} (one way). No attempt is made to verify the
056 * uniqueness of the mappings.
057 * </p>
058 *
059 * <p>
060 * Mappings are read from a <code>ResourceBundle</code>, the keys of which should consist of a fully-qualified class name, followed by a dot ('.'),
061 * and then the name of a static field belonging to that class, followed optionally by another dot and a number. If the trailing dot and number are
062 * not present, the value associated with the key is treated as a {@link SymbolMap#label(AbstractUnit, String) label}, otherwise if the trailing dot
063 * and number are present, the value is treated as an {@link SymbolMap#alias(AbstractUnit,String) alias}. Aliases map from String to Unit only,
064 * whereas labels map in both directions. A given unit may have any number of aliases, but may have only one label.
065 * </p>
066 *
067 * @author <a href="mailto:eric-r@northwestern.edu">Eric Russell</a>
068 * @author <a href="mailto:units@catmedia.us">Werner Keil</a>
069 * @version 1.7, February 25, 2017
070 */
071@SuppressWarnings("rawtypes")
072public final class SymbolMap {
073  private static final Logger logger = Logger.getLogger(SymbolMap.class.getName());
074
075  private final Map<String, Unit<?>> symbolToUnit;
076  private final Map<Unit<?>, String> unitToSymbol;
077  private final Map<String, Object> symbolToPrefix;
078  private final Map<Object, String> prefixToSymbol;
079  private final Map<UnitConverter, MetricPrefix> converterToPrefix;
080
081  /**
082   * Creates an empty mapping.
083   */
084  private SymbolMap() {
085    symbolToUnit = new TreeMap<>();
086    unitToSymbol = new HashMap<>();
087    symbolToPrefix = new TreeMap<>();
088    prefixToSymbol = new HashMap<>();
089    converterToPrefix = new HashMap<>();
090  }
091
092  /**
093   * Creates a symbol map from the specified resource bundle,
094   *
095   * @param rb
096   *          the resource bundle.
097   */
098  private SymbolMap(ResourceBundle rb) {
099    this();
100    for (Enumeration<String> i = rb.getKeys(); i.hasMoreElements();) {
101      String fqn = i.nextElement();
102      String symbol = rb.getString(fqn);
103      boolean isAlias = false;
104      int lastDot = fqn.lastIndexOf('.');
105      String className = fqn.substring(0, lastDot);
106      String fieldName = fqn.substring(lastDot + 1, fqn.length());
107      if (Character.isDigit(fieldName.charAt(0))) {
108        isAlias = true;
109        fqn = className;
110        lastDot = fqn.lastIndexOf('.');
111        className = fqn.substring(0, lastDot);
112        fieldName = fqn.substring(lastDot + 1, fqn.length());
113      }
114      try {
115        Class<?> c = Class.forName(className);
116        Field field = c.getField(fieldName);
117        Object value = field.get(null);
118        if (value instanceof Unit<?>) {
119          if (isAlias) {
120            alias((Unit) value, symbol);
121          } else {
122            label((AbstractUnit<?>) value, symbol);
123          }
124        } else if (value instanceof MetricPrefix) {
125          label((MetricPrefix) value, symbol);
126        } else {
127          throw new ClassCastException("unable to cast " + value + " to Unit or Prefix");
128        }
129      } catch (Exception error) {
130        logger.log(Level.SEVERE, "Error", error);
131      }
132    }
133  }
134
135  /**
136   * Creates a symbol map from the specified resource bundle,
137   *
138   * @param rb
139   *          the resource bundle.
140   */
141  public static SymbolMap of(ResourceBundle rb) {
142    return new SymbolMap(rb);
143  }
144
145  /**
146   * Attaches a label to the specified unit. For example:<br>
147   * <code> symbolMap.label(DAY.multiply(365), "year"); symbolMap.label(US.FOOT, "ft");
148   * </code>
149   *
150   * @param unit
151   *          the unit to label.
152   * @param symbol
153   *          the new symbol for the unit.
154   */
155  public void label(Unit<?> unit, String symbol) {
156    symbolToUnit.put(symbol, unit);
157    unitToSymbol.put(unit, symbol);
158  }
159
160  /**
161   * Attaches an alias to the specified unit. Multiple aliases may be attached to the same unit. Aliases are used during parsing to recognize
162   * different variants of the same unit.<code> symbolMap.alias(US.FOOT, "foot"); symbolMap.alias(US.FOOT, "feet");
163   * symbolMap.alias(Units.METER, "meter"); symbolMap.alias(Units.METER, "metre"); </code>
164   *
165   * @param unit
166   *          the unit to label.
167   * @param symbol
168   *          the new symbol for the unit.
169   */
170  public void alias(Unit<?> unit, String symbol) {
171    symbolToUnit.put(symbol, unit);
172  }
173
174  /**
175   * Attaches a label to the specified prefix. For example:<br>
176   * <code> symbolMap.label(MetricPrefix.GIGA, "G"); symbolMap.label(MetricPrefix.MICRO, "ยต");
177   * </code>
178   */
179  public void label(MetricPrefix prefix, String symbol) {
180    symbolToPrefix.put(symbol, prefix);
181    prefixToSymbol.put(prefix, symbol);
182    converterToPrefix.put(prefix.getConverter(), prefix);
183  }
184
185  /**
186   * Returns the unit for the specified symbol.
187   *
188   * @param symbol
189   *          the symbol.
190   * @return the corresponding unit or <code>null</code> if none.
191   */
192  public Unit<?> getUnit(String symbol) {
193    return symbolToUnit.get(symbol);
194  }
195
196  /**
197   * Returns the symbol (label) for the specified unit.
198   *
199   * @param unit
200   *          the corresponding symbol.
201   * @return the corresponding symbol or <code>null</code> if none.
202   */
203  public String getSymbol(Unit<?> unit) {
204    return unitToSymbol.get(unit);
205  }
206
207  /**
208   * Returns the prefix (if any) for the specified symbol.
209   *
210   * @param symbol
211   *          the unit symbol.
212   * @return the corresponding prefix or <code>null</code> if none.
213   */
214  public MetricPrefix getPrefix(String symbol) {
215        final List<String> list = symbolToPrefix.keySet().stream().collect(Collectors.toList());
216        final Comparator<String> comparator = Comparator.comparing(String::length);
217        Collections.sort(list, comparator.reversed());
218
219        for (String key : list) {
220            if (symbol.startsWith(key)) {
221                return (MetricPrefix) symbolToPrefix.get(key);
222            }
223        }
224        return null;
225    }
226
227  /**
228   * Returns the prefix for the specified converter.
229   *
230   * @param converter
231   *          the unit converter.
232   * @return the corresponding prefix or <code>null</code> if none.
233   */
234  public MetricPrefix getPrefix(UnitConverter converter) {
235    return converterToPrefix.get(converter);
236  }
237
238  /**
239   * Returns the symbol for the specified prefix.
240   *
241   * @param prefix
242   *          the prefix.
243   * @return the corresponding symbol or <code>null</code> if none.
244   */
245  public String getSymbol(MetricPrefix prefix) {
246    return prefixToSymbol.get(prefix);
247  }
248
249  @Override
250  public String toString() {
251    StringBuilder sb = new StringBuilder();
252    sb.append("tec.uom.se.format.SymbolMap: [");
253    sb.append("symbolToUnit: ").append(symbolToUnit).append(',');
254    sb.append("unitToSymbol: ").append(unitToSymbol).append(',');
255    sb.append("symbolToPrefix: ").append(symbolToPrefix).append(',');
256    sb.append("prefixToSymbol: ").append(prefixToSymbol).append(',');
257    sb.append("converterToPrefix: ").append(converterToPrefix).append(',');
258    sb.append("converterToPrefix: ").append(converterToPrefix);
259    sb.append(" ]");
260    return sb.toString();
261  }
262
263}