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