blob: dbe8b3c1e0364513314ec589a3f571bd02eb6814 [file] [log] [blame]
/*
* Copyright (c) 2005, 2017, Werner Keil and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Werner Keil, Eric Russell - initial API and implementation
*/
package org.eclipse.uomo.units.impl.format;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.eclipse.uomo.units.AbstractUnit;
import org.eclipse.uomo.units.impl.system.MetricPrefix;
import javax.measure.Unit;
import javax.measure.UnitConverter;
/**
* <p>
* This class provides a set of mappings between {@link AbstractUnit units} and symbols (both ways), between {@link MetricPrefix prefixes} and symbols
* (both ways), and from {@link AbstractConverter unit converters} to {@link MetricPrefix prefixes} (one way). No attempt is made to verify the
* uniqueness of the mappings.
* </p>
*
* <p>
* Mappings are read from a <code>ResourceBundle</code>, the keys of which should consist of a fully-qualified class name, followed by a dot ('.'),
* 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
* not present, the value associated with the key is treated as a {@link SymbolMapImpl#label(AbstractUnit, String) label}, otherwise if the trailing dot
* and number are present, the value is treated as an {@link SymbolMapImpl#alias(AbstractUnit,String) alias}. Aliases map from String to Unit only,
* whereas labels map in both directions. A given unit may have any number of aliases, but may have only one label.
* </p>
*
* @author <a href="mailto:eric-r@northwestern.edu">Eric Russell</a>
* @author <a href="mailto:units@catmedia.us">Werner Keil</a>
* @version 1.7, February 25, 2017
*/
@SuppressWarnings("rawtypes")
public final class SymbolMapImpl {
private static final Logger logger = Logger.getLogger(SymbolMapImpl.class.getName());
private final Map<String, Unit<?>> symbolToUnit;
private final Map<Unit<?>, String> unitToSymbol;
private final Map<String, Object> symbolToPrefix;
private final Map<Object, String> prefixToSymbol;
private final Map<UnitConverter, MetricPrefix> converterToPrefix;
/**
* Creates an empty mapping.
*/
private SymbolMapImpl() {
symbolToUnit = new TreeMap<>();
unitToSymbol = new HashMap<>();
symbolToPrefix = new TreeMap<>();
prefixToSymbol = new HashMap<>();
converterToPrefix = new HashMap<>();
}
/**
* Creates a symbol map from the specified resource bundle,
*
* @param rb
* the resource bundle.
*/
private SymbolMapImpl(ResourceBundle rb) {
this();
for (Enumeration<String> i = rb.getKeys(); i.hasMoreElements();) {
String fqn = i.nextElement();
String symbol = rb.getString(fqn);
boolean isAlias = false;
int lastDot = fqn.lastIndexOf('.');
String className = fqn.substring(0, lastDot);
String fieldName = fqn.substring(lastDot + 1, fqn.length());
if (Character.isDigit(fieldName.charAt(0))) {
isAlias = true;
fqn = className;
lastDot = fqn.lastIndexOf('.');
className = fqn.substring(0, lastDot);
fieldName = fqn.substring(lastDot + 1, fqn.length());
}
try {
Class<?> c = Class.forName(className);
Field field = c.getField(fieldName);
Object value = field.get(null);
if (value instanceof Unit<?>) {
if (isAlias) {
alias((Unit) value, symbol);
} else {
label((AbstractUnit<?>) value, symbol);
}
} else if (value instanceof MetricPrefix) {
label((MetricPrefix) value, symbol);
} else {
throw new ClassCastException("unable to cast " + value + " to Unit or Prefix");
}
} catch (Exception error) {
logger.log(Level.SEVERE, "Error", error);
}
}
}
/**
* Creates a symbol map from the specified resource bundle,
*
* @param rb
* the resource bundle.
*/
public static SymbolMapImpl of(ResourceBundle rb) {
return new SymbolMapImpl(rb);
}
/**
* Attaches a label to the specified unit. For example:<br>
* <code> symbolMap.label(DAY.multiply(365), "year"); symbolMap.label(US.FOOT, "ft");
* </code>
*
* @param unit
* the unit to label.
* @param symbol
* the new symbol for the unit.
*/
public void label(Unit<?> unit, String symbol) {
symbolToUnit.put(symbol, unit);
unitToSymbol.put(unit, symbol);
}
/**
* Attaches an alias to the specified unit. Multiple aliases may be attached to the same unit. Aliases are used during parsing to recognize
* different variants of the same unit.<code> symbolMap.alias(US.FOOT, "foot"); symbolMap.alias(US.FOOT, "feet");
* symbolMap.alias(Units.METER, "meter"); symbolMap.alias(Units.METER, "metre"); </code>
*
* @param unit
* the unit to label.
* @param symbol
* the new symbol for the unit.
*/
public void alias(Unit<?> unit, String symbol) {
symbolToUnit.put(symbol, unit);
}
/**
* Attaches a label to the specified prefix. For example:<br>
* <code> symbolMap.label(MetricPrefix.GIGA, "G"); symbolMap.label(MetricPrefix.MICRO, "ยต");
* </code>
*/
public void label(MetricPrefix prefix, String symbol) {
symbolToPrefix.put(symbol, prefix);
prefixToSymbol.put(prefix, symbol);
converterToPrefix.put(prefix.getConverter(), prefix);
}
/**
* Returns the unit for the specified symbol.
*
* @param symbol
* the symbol.
* @return the corresponding unit or <code>null</code> if none.
*/
public Unit<?> getUnit(String symbol) {
return symbolToUnit.get(symbol);
}
/**
* Returns the symbol (label) for the specified unit.
*
* @param unit
* the corresponding symbol.
* @return the corresponding symbol or <code>null</code> if none.
*/
public String getSymbol(Unit<?> unit) {
return unitToSymbol.get(unit);
}
/**
* Returns the prefix (if any) for the specified symbol.
*
* @param symbol
* the unit symbol.
* @return the corresponding prefix or <code>null</code> if none.
*/
public MetricPrefix getPrefix(String symbol) {
final List<String> list = symbolToPrefix.keySet().stream().collect(Collectors.toList());
final Comparator<String> comparator = Comparator.comparing(String::length);
Collections.sort(list, comparator.reversed());
for (String key : list) {
if (symbol.startsWith(key)) {
return (MetricPrefix) symbolToPrefix.get(key);
}
}
return null;
}
/**
* Returns the prefix for the specified converter.
*
* @param converter
* the unit converter.
* @return the corresponding prefix or <code>null</code> if none.
*/
public MetricPrefix getPrefix(UnitConverter converter) {
return converterToPrefix.get(converter);
}
/**
* Returns the symbol for the specified prefix.
*
* @param prefix
* the prefix.
* @return the corresponding symbol or <code>null</code> if none.
*/
public String getSymbol(MetricPrefix prefix) {
return prefixToSymbol.get(prefix);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("tec.units.indriya.format.SymbolMap: [");
sb.append("symbolToUnit: ").append(symbolToUnit).append(',');
sb.append("unitToSymbol: ").append(unitToSymbol).append(',');
sb.append("symbolToPrefix: ").append(symbolToPrefix).append(',');
sb.append("prefixToSymbol: ").append(prefixToSymbol).append(',');
sb.append("converterToPrefix: ").append(converterToPrefix).append(',');
sb.append("converterToPrefix: ").append(converterToPrefix);
sb.append(" ]");
return sb.toString();
}
}