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}