Unit.java
package org.opentrafficsim.core.unit;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.opentrafficsim.core.locale.Localization;
import org.opentrafficsim.core.unit.unitsystem.UnitSystem;
import org.reflections.Reflections;
/**
* All units are internally <i>stored</i> relative to a standard unit with conversion factor. This means that e.g., a meter is
* stored with conversion factor 1.0, whereas kilometer is stored with a conversion factor 1000.0. This means that if we want to
* display a meter as kilometers, we have to <i>divide</i> by the conversion factor.
* <p>
* Copyright (c) 2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
* <p>
* @version May 15, 2014 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @param <U> the unit for transformation reasons
*/
public abstract class Unit<U extends Unit<U>> implements Serializable
{
/** */
private static final long serialVersionUID = 20140607;
/** the key to the locale file for the long name of the unit. */
private final String nameKey;
/** the key to the locale file for the abbreviation of the unit. */
private final String abbreviationKey;
/** the unit system, e.g. SI or Imperial. */
private final UnitSystem unitSystem;
/** multiply by this number to convert to the standard (e.g., SI) unit. */
private final double conversionFactorToStandardUnit;
/** SI unit information. */
private SICoefficients siCoefficients;
/** static map of all defined coefficient strings, to avoid double creation and allow lookup. */
private static final Map<String, SICoefficients> SI_COEFFICIENTS = new HashMap<String, SICoefficients>();
/** static map of all defined coefficient strings, mapped to the existing units. */
private static final Map<String, Map<Class<Unit<?>>, Unit<?>>> SI_UNITS =
new HashMap<String, Map<Class<Unit<?>>, Unit<?>>>();
/** a static map of all defined units. */
private static final Map<String, Set<Unit<?>>> UNITS = new HashMap<String, Set<Unit<?>>>();
/** localization information. */
private static Localization localization = new Localization("localeunit");
/** has this class been initialized? */
private static boolean initialized = false;
/** force all units to be loaded. */
private static void initialize()
{
Reflections reflections = new Reflections("org.opentrafficsim.core.unit");
@SuppressWarnings("rawtypes")
Set<Class<? extends Unit>> classes = reflections.getSubTypesOf(Unit.class);
for (@SuppressWarnings("rawtypes")
Class<? extends Unit> clazz : classes)
{
try
{
Class.forName(clazz.getCanonicalName());
}
catch (Exception exception)
{
// TODO professional logging of errors
// exception.printStackTrace();
System.err.println("Could not load class " + clazz.getCanonicalName());
}
}
initialized = true;
}
/**
* Build a standard unit.
* @param nameKey the key to the locale file for the long name of the unit
* @param abbreviationKey the key to the locale file for the abbreviation of the unit
* @param unitSystem the unit system, e.g. SI or Imperial
* @throws UnitException when unit cannot be added to the list of units
*/
/*-
public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem) throws UnitException
{
this.conversionFactorToStandardUnit = 1.0;
this.nameKey = nameKey;
this.abbreviationKey = abbreviationKey;
this.unitSystem = unitSystem;
addUnit(this);
}
*/
/**
* Build a unit with a conversion factor to another unit.
* @param nameKey the key to the locale file for the long name of the unit
* @param abbreviationKey the key to the locale file for the abbreviation of the unit
* @param unitSystem the unit system, e.g. SI or Imperial
* @param referenceUnit the unit to convert to
* @param conversionFactorToReferenceUnit multiply a value in this unit by the factor to convert to the given reference unit
* @throws UnitException when unit cannot be added to the list of units
*/
/*-
public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem, final U referenceUnit,
final double conversionFactorToReferenceUnit) throws UnitException
{
// as it can happen that this method is called for the standard unit (when it is still null) we have to catch
// the null pointer for the reference unit here.
if (referenceUnit == null)
{
this.conversionFactorToStandardUnit = 1.0;
}
else
{
this.conversionFactorToStandardUnit =
referenceUnit.getConversionFactorToStandardUnit() * conversionFactorToReferenceUnit;
}
this.nameKey = nameKey;
this.abbreviationKey = abbreviationKey;
this.unitSystem = unitSystem;
addUnit(this);
}
*/
/**
* Build a standard unit.
* @param nameKey the key to the locale file for the long name of the unit
* @param abbreviationKey the key to the locale file for the abbreviation of the unit
* @param unitSystem the unit system, e.g. SI or Imperial
* @param safe Boolean; if true, a UnitException is silently ignored; if false a UnitException is an Error
*/
public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem, final boolean safe)
{
this.conversionFactorToStandardUnit = 1.0;
this.nameKey = nameKey;
this.abbreviationKey = abbreviationKey;
this.unitSystem = unitSystem;
try
{
addUnit(this);
}
catch (UnitException ue)
{
if (!safe)
{
throw new Error(ue);
}
// TODO complain wherever we can
}
}
/**
* Build a unit with a conversion factor to another unit.
* @param nameKey the key to the locale file for the long name of the unit
* @param abbreviationKey the key to the locale file for the abbreviation of the unit
* @param unitSystem the unit system, e.g. SI or Imperial
* @param referenceUnit the unit to convert to
* @param conversionFactorToReferenceUnit multiply a value in this unit by the factor to convert to the given reference unit
* @param safe Boolean; if true, a UnitException is silently ignored; if false a UnitException is an Error
*/
public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem, final U referenceUnit,
final double conversionFactorToReferenceUnit, final boolean safe)
{
// as it can happen that this method is called for the standard unit (when it is still null) we have to catch
// the null pointer for the reference unit here.
if (referenceUnit == null)
{
this.conversionFactorToStandardUnit = 1.0;
}
else
{
this.conversionFactorToStandardUnit =
referenceUnit.getConversionFactorToStandardUnit() * conversionFactorToReferenceUnit;
}
this.nameKey = nameKey;
this.abbreviationKey = abbreviationKey;
this.unitSystem = unitSystem;
try
{
addUnit(this);
}
catch (UnitException ue)
{
if (!safe)
{
throw new Error(ue);
}
// TODO complain wherever we can
}
}
/**
* Add a unit to the overview collection of existing units, and resolve the coefficients.
* @param unit the unit to add. It will be stored in a set belonging to the simple class name String, e.g. "ForceUnit".
* @throws UnitException when parsing or normalizing the SI coefficient string fails.
*/
private void addUnit(final Unit<U> unit) throws UnitException
{
if (!UNITS.containsKey(unit.getClass().getSimpleName()))
{
UNITS.put(unit.getClass().getSimpleName(), new HashSet<Unit<?>>());
}
UNITS.get(unit.getClass().getSimpleName()).add(unit);
// resolve the SI coefficients, and normalize string
String siCoefficientsString = SICoefficients.normalize(getSICoefficientsString()).toString();
if (SI_COEFFICIENTS.containsKey(siCoefficientsString))
{
this.siCoefficients = SI_COEFFICIENTS.get(siCoefficientsString);
}
else
{
this.siCoefficients = new SICoefficients(SICoefficients.parse(siCoefficientsString));
SI_COEFFICIENTS.put(siCoefficientsString, this.siCoefficients);
}
// add the standard unit
Map<Class<Unit<?>>, Unit<?>> unitMap = SI_UNITS.get(siCoefficientsString);
if (unitMap == null)
{
unitMap = new HashMap<Class<Unit<?>>, Unit<?>>();
SI_UNITS.put(siCoefficientsString, unitMap);
}
if (!unitMap.containsKey(unit.getClass()))
{
@SuppressWarnings("unchecked")
Class<Unit<?>> clazz = (Class<Unit<?>>) unit.getClass();
if (this.getStandardUnit() == null)
{
unitMap.put(clazz, this);
}
else
{
unitMap.put(clazz, this.getStandardUnit());
}
}
}
/**
* Return a set of defined units for a given unit type.
* @param <V> the unit type to use in this method.
* @param unitClass the class for which the units are requested, e.g. ForceUnit.class
* @return the set of defined units belonging to the provided class. The empty set will be returned in case the unit type
* does not have any units.
*/
@SuppressWarnings("unchecked")
public static <V extends Unit<V>> Set<V> getUnits(final Class<V> unitClass)
{
if (!initialized)
{
initialize();
}
Set<V> returnSet = new HashSet<V>();
if (UNITS.containsKey(unitClass.getSimpleName()))
{
for (Unit<?> unit : UNITS.get(unitClass.getSimpleName()))
{
returnSet.add((V) unit);
}
}
return returnSet;
}
/**
* Return a copy of the set of all defined units for this unit type.
* @return the set of defined units belonging to this Unit class. The empty set will be returned in case the unit type does
* not have any units.
*/
// TODO call static method from the instance method? The two are now too similar.
@SuppressWarnings("unchecked")
public final Set<Unit<U>> getAllUnitsOfThisType()
{
if (!initialized)
{
initialize();
}
Set<Unit<U>> returnSet = new HashSet<Unit<U>>();
if (UNITS.containsKey(this.getClass().getSimpleName()))
{
for (Unit<?> unit : UNITS.get(this.getClass().getSimpleName()))
{
returnSet.add((Unit<U>) unit);
}
}
return returnSet;
}
/**
* @return name, e.g. meters per second
*/
public final String getName()
{
return localization.getString(this.nameKey);
}
/**
* @return name key, e.g. TimeUnit.MetersPerSecond
*/
public final String getNameKey()
{
return this.nameKey;
}
/**
* @return abbreviation, e.g., m/s
*/
public final String getAbbreviation()
{
return localization.getString(this.abbreviationKey);
}
/**
* @return abbreviation key, e.g. TimeUnit.m/s
*/
public final String getAbbreviationKey()
{
return this.abbreviationKey;
}
/**
* @return conversionFactorToStandardUnit. Multiply by this number to convert to the standard (e.g., SI) unit
*/
public final double getConversionFactorToStandardUnit()
{
return this.conversionFactorToStandardUnit;
}
/**
* @return unitSystem, e.g. SI or Imperial
*/
public final UnitSystem getUnitSystem()
{
return this.unitSystem;
}
/**
* @return the SI standard unit for this unit, or the de facto standard unit if SI is not available
*/
public abstract U getStandardUnit();
/**
* @return the SI standard coefficients for this unit, e.g., kgm/s2 or m-2s2A or m^-2.s^2.A or m^-2s^2A (not necessarily
* normalized)
*/
public abstract String getSICoefficientsString();
/**
* @return the SI coefficients
*/
public final SICoefficients getSICoefficients()
{
return this.siCoefficients;
}
/**
* @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
* @return a set with the Units belonging to this string, or an empty set when it does not exist
*/
public static Set<Unit<?>> lookupUnitWithSICoefficients(final String normalizedSICoefficientsString)
{
if (!initialized)
{
initialize();
}
if (SI_UNITS.containsKey(normalizedSICoefficientsString))
{
return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
}
return new HashSet<Unit<?>>();
}
/**
* @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
* @return a set of Units belonging to this string, or a set with a new unit when it does not yet exist
*/
// TODO call other static method? The two are now too similar.
public static Set<Unit<?>> lookupOrCreateUnitWithSICoefficients(final String normalizedSICoefficientsString)
{
if (!initialized)
{
initialize();
}
if (SI_UNITS.containsKey(normalizedSICoefficientsString))
{
return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
}
SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString);
Set<Unit<?>> unitSet = new HashSet<Unit<?>>();
unitSet.add(unit);
return unitSet;
}
/**
* @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
* @return the Unit belonging to this string, or a new unit when it does not yet exist
*/
public static SIUnit lookupOrCreateSIUnitWithSICoefficients(final String normalizedSICoefficientsString)
{
if (!initialized)
{
initialize();
}
if (SI_UNITS.containsKey(normalizedSICoefficientsString)
&& SI_UNITS.get(normalizedSICoefficientsString).containsKey(SIUnit.class))
{
return (SIUnit) SI_UNITS.get(normalizedSICoefficientsString).get(SIUnit.class);
}
SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString);
return unit;
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return getAbbreviation();
}
}