Unit.java
package org.djunits.unit;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.djunits.locale.Localization;
import org.djunits.unit.scale.Scale;
import org.djunits.unit.scale.StandardScale;
import org.djunits.unit.unitsystem.UnitSystem;
/**
* 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
* express a length meter in kilometers, we have to <i>divide</i> by the conversion factor.
* <p>
* Copyright (c) 2015-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://djunits.org/docs/license.html">DJUNITS License</a>.
* <p>
* $LastChangedDate: 2019-04-22 12:49:59 +0200 (Mon, 22 Apr 2019) $, @version $Revision: 401 $, by $Author: averbraeck $,
* initial 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 abbreviation of the unit, or null when it is a user-defined unit. */
private final String abbreviationKey;
/**
* The long name of the unit in case it does not exist in the locale file, e.g. when defining a run-time unit. The name will
* be null if the locale has to be used, i.e. for standard units.
*/
private final String name;
/**
* The abbreviation of the unit in case it does not exist in the locale file, e.g. when defining a run-time unit. The
* abbreviation will be null if the locale has to be used, i.e. for standard units.
*/
private final String abbreviation;
/** The unit system, e.g. SI or Imperial. */
private final UnitSystem unitSystem;
/** The scale to use to convert between this unit and the standard (e.g., SI) unit. */
private final Scale scale;
/** SI unit information. */
private SICoefficients siCoefficients;
/** A 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>();
/** A 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<?>>>();
/** Has this class been initialized? */
private static boolean standardUnitsInitialized = false;
/** Standard (SI) unit or not? */
private boolean baseSIUnit;
/** The array of the names of the standard units. */
public static final String[] STANDARD_UNITS = new String[] {"AbsoluteTemperatureUnit", "AccelerationUnit", "AngleSolidUnit",
"AngleUnit", "AreaUnit", "DensityUnit", "DimensionlessUnit", "DirectionUnit", "DurationUnit",
"ElectricalChargeUnit", "ElectricalCurrentUnit", "ElectricalPotentialUnit", "ElectricalResistanceUnit",
"EnergyUnit", "FlowMassUnit", "FlowVolumeUnit", "ForceUnit", "FrequencyUnit", "LengthUnit", "LinearDensityUnit",
"MassUnit", "MoneyUnit", "MoneyPerAreaUnit", "MoneyPerEnergyUnit", "MoneyPerLengthUnit", "MoneyPerMassUnit",
"MoneyPerDurationUnit", "MoneyPerVolumeUnit", "PositionUnit", "PowerUnit", "PressureUnit", "SpeedUnit",
"TemperatureUnit", "TimeUnit", "TorqueUnit", "VolumeUnit"};
/** the cached hashcode. */
private final int cachedHashCode;
/** The default locale. */
private static Localization localization = new Localization("localeunit");
/** The cached default locale information. */
private String[] cachedDefaultLocaleInfo;
/**
* Force all units to be loaded so we can use static lookup functions for the standard units.
*/
private static void initializeStandardUnits()
{
for (String className : STANDARD_UNITS)
{
try
{
Class.forName("org.djunits.unit." + className);
}
catch (Exception exception)
{
// TODO professional logging of errors
System.err.println("Could not load class org.djunits.unit." + className);
}
}
standardUnitsInitialized = true;
}
/**
* Build a standard unit and create the fields for that unit. For a standard unit, a UnitException is silently ignored.
* @param abbreviationKey String; the key to the locale file for the abbreviation of the unit
* @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
*/
protected Unit(final String abbreviationKey, final UnitSystem unitSystem)
{
this.scale = StandardScale.SCALE;
this.baseSIUnit = true;
this.abbreviationKey = abbreviationKey;
this.name = null;
this.abbreviation = null;
this.cachedDefaultLocaleInfo = localization.getDefaultString(this.abbreviationKey).split("\\|");
this.unitSystem = unitSystem;
this.cachedHashCode = generateHashCode();
try
{
addUnit(this);
}
catch (UnitException ue)
{
// silently ignore.
}
}
/**
* Build a standard unit with a specific conversion scale to/from the standard unit. For a standard unit, a UnitException is
* silently ignored.
* @param abbreviationKey String; the key to the locale file for the abbreviation of the unit, otherwise the abbreviation
* itself
* @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
* @param scale Scale; the conversion scale to use for this unit
*/
protected Unit(final String abbreviationKey, final UnitSystem unitSystem, final Scale scale)
{
this.scale = scale;
this.baseSIUnit = scale.isBaseSIScale();
this.abbreviationKey = abbreviationKey;
this.name = null;
this.abbreviation = null;
this.cachedDefaultLocaleInfo = localization.getDefaultString(this.abbreviationKey).split("\\|");
this.unitSystem = unitSystem;
this.cachedHashCode = generateHashCode();
try
{
addUnit(this);
}
catch (UnitException ue)
{
// silently ignore
}
}
/**
* Build a user-defined unit and create the fields for that unit. This unit is a user-defined unit where the localization
* files do not have an entry. A UnitException is thrown as a RunTimeException.
* @param name String; the key to the locale file for the long name of the unit, otherwise the name itself
* @param abbreviation String; the key to the locale file for the abbreviation of the unit, otherwise the abbreviation
* itself
* @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
*/
protected Unit(final String name, final String abbreviation, final UnitSystem unitSystem)
{
this.scale = StandardScale.SCALE;
this.baseSIUnit = true;
this.abbreviationKey = null;
this.name = name;
this.abbreviation = abbreviation;
this.unitSystem = unitSystem;
this.cachedHashCode = generateHashCode();
try
{
addUnit(this);
}
catch (UnitException ue)
{
throw new RuntimeException(ue);
}
}
/**
* Build a user-defined unit with a specific conversion scale to/from the standard unit. This unit is a user-defined unit
* where the localization files do not have an entry. A UnitException is thrown as a RunTimeException.
* @param name String; the key to the locale file for the long name of the unit, otherwise the name itself
* @param abbreviation String; the key to the locale file for the abbreviation of the unit, otherwise the abbreviation
* itself
* @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
* @param scale Scale; the conversion scale to use for this unit
*/
protected Unit(final String name, final String abbreviation, final UnitSystem unitSystem, final Scale scale)
{
this.scale = scale;
this.baseSIUnit = scale.isBaseSIScale();
this.abbreviationKey = null;
this.name = name;
this.abbreviation = abbreviation;
this.unitSystem = unitSystem;
this.cachedHashCode = generateHashCode();
try
{
addUnit(this);
}
catch (UnitException ue)
{
throw new RuntimeException(ue);
}
}
/**
* Report if this unit support localization.
* @return boolean; true if this unit supports localization; false if it does not
*/
public final boolean isLocalizable()
{
return this.abbreviationKey != null;
}
/**
* Add a unit to the overview collection of existing units, and resolve the coefficients.
* @param unit Unit<U>; 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 Class<V>; 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 (!standardUnitsInitialized)
{
initializeStandardUnits();
}
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.
*/
@SuppressWarnings("unchecked")
public final Set<Unit<U>> getAllUnitsOfThisType()
{
if (!standardUnitsInitialized)
{
initializeStandardUnits();
}
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()
{
if (this.name != null)
{
return this.name;
}
if (localization.isDefault())
{
return getDefaultLocaleName();
}
String[] loc = localization.getString(this.abbreviationKey).split("\\|");
if (loc.length >= 2)
{
return loc[1].trim();
}
if (loc.length >= 1)
{
return loc[0].trim();
}
return this.abbreviationKey;
}
/**
* @return the name in the default locale, e.g. meters per second
*/
public final String getDefaultLocaleName()
{
if (this.name != null)
{
return this.name;
}
if (this.cachedDefaultLocaleInfo.length >= 2)
{
return this.cachedDefaultLocaleInfo[1].trim();
}
if (this.cachedDefaultLocaleInfo.length >= 1)
{
return this.cachedDefaultLocaleInfo[0].trim();
}
return this.abbreviationKey;
}
/**
* @return abbreviation, e.g., m/s
*/
public final String getAbbreviation()
{
if (this.abbreviation != null)
{
return this.abbreviation;
}
if (localization.isDefault())
{
return getDefaultLocaleAbbreviation();
}
String[] loc = localization.getString(this.abbreviationKey).split("\\|");
if (loc.length >= 1)
{
return loc[0].trim();
}
return this.abbreviationKey;
}
/**
* @return the abbreviation in the default locale, e.g., m/s
*/
public final String getDefaultLocaleAbbreviation()
{
if (this.abbreviation != null)
{
return this.abbreviation;
}
if (this.cachedDefaultLocaleInfo.length >= 1)
{
return this.cachedDefaultLocaleInfo[0].trim();
}
return this.abbreviationKey;
}
/**
* This method returns the abbreviation key, or null in case the abbreviation is hard coded.
* @return abbreviation key, e.g. DurationUnit.m/s, or null for a user-defined unit
*/
public final String getAbbreviationKey()
{
return this.abbreviationKey;
}
/**
* Return the textual display types of the unit. In case the list contains more than one abbreviation, the first one is the
* default one. In case none is available, the standard abbreviation is used. In case that one is also not available the
* abbreviation key is used. Note: the abbreviation itself is not necessarily a <b>textual</b> representation.
* @return the textual display types of the unit
*/
public final List<String> getTextualRepresentations()
{
if (this.abbreviation != null)
{
return Arrays.asList(new String[] {this.abbreviation});
}
if (localization.isDefault())
{
return getDefaultLocaleTextualRepresentations();
}
String[] loc = localization.getString(this.abbreviationKey).split("\\|");
if (loc.length >= 3)
{
List<String> textList = new ArrayList<>();
for (int i = 2; i < loc.length; i++)
{
textList.add(loc[i].trim());
}
return textList;
}
if (loc.length >= 1)
{
return Arrays.asList(new String[] {loc[0].trim()});
}
return Arrays.asList(new String[] {this.abbreviationKey});
}
/**
* Return the textual display types of the unit in the default locale. In case the list contains more than one abbreviation,
* the first one is the default one. In case none is available, the standard abbreviation is used. In case that one is also
* not available the abbreviation key is used. Note: the abbreviation itself is not necessarily a <b>textual</b>
* representation.
* @return the textual display types of the unit in the default locale
*/
public final List<String> getDefaultLocaleTextualRepresentations()
{
if (this.abbreviation != null)
{
return Arrays.asList(new String[] {this.abbreviation});
}
if (this.cachedDefaultLocaleInfo.length >= 3)
{
List<String> textList = new ArrayList<>();
for (int i = 2; i < this.cachedDefaultLocaleInfo.length; i++)
{
textList.add(this.cachedDefaultLocaleInfo[i].trim());
}
return textList;
}
if (this.cachedDefaultLocaleInfo.length >= 1)
{
return Arrays.asList(new String[] {this.cachedDefaultLocaleInfo[0].trim()});
}
return Arrays.asList(new String[] {this.abbreviationKey});
}
/**
* Return the default textual display representation of the unit. In case there are more textual representations, the first
* one is the default one. In case none is available, the standard abbreviation is used. In case that one is also not
* available the abbreviation key is used. Note: the abbreviation itself is not necessarily a <b>textual</b> representation.
* @return the default textual display representation of the unit
*/
public final String getDefaultTextualRepresentation()
{
if (this.abbreviation != null)
{
return this.abbreviation;
}
if (localization.isDefault())
{
return getDefaultLocaleTextualRepresentation();
}
String[] loc = localization.getString(this.abbreviationKey).split("\\|");
if (loc.length >= 3)
{
return loc[2].trim();
}
if (loc.length >= 1)
{
return loc[0].trim();
}
return this.abbreviationKey;
}
/**
* Return the default textual display representation of the unit in the default locale. In case there are more textual
* representations, the first one is the default one. In case none is available, the standard abbreviation is used. In case
* that one is also not available the abbreviation key is used. Note: the abbreviation itself is not necessarily a
* <b>textual</b> representation.
* @return the default textual display representation of the unit in the default locale
*/
public final String getDefaultLocaleTextualRepresentation()
{
if (this.abbreviation != null)
{
return this.abbreviation;
}
if (this.cachedDefaultLocaleInfo.length >= 3)
{
return this.cachedDefaultLocaleInfo[2].trim();
}
if (this.cachedDefaultLocaleInfo.length >= 1)
{
return this.cachedDefaultLocaleInfo[0].trim();
}
return this.abbreviationKey;
}
/**
* @return the scale to transform between this unit and the reference (e.g., SI) unit.
*/
@SuppressWarnings("checkstyle:designforextension")
public Scale getScale()
{
return this.scale;
}
/**
* @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;
}
/**
* Determine whether this unit is the standard unit.
* @return boolean; whether this is the standard unit or not
*/
public final boolean isBaseSIUnit()
{
return this.baseSIUnit;
}
/**
* @param normalizedSICoefficientsString String; 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 (!standardUnitsInitialized)
{
initializeStandardUnits();
}
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
*/
public static Set<Unit<?>> lookupOrCreateUnitWithSICoefficients(final String normalizedSICoefficientsString)
{
if (!standardUnitsInitialized)
{
initializeStandardUnits();
}
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 String; 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 (!standardUnitsInitialized)
{
initializeStandardUnits();
}
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();
}
/**
* Generate a hashCode for caching.
* @return a hashCode that is consistent with the equals() method.
*/
public final int generateHashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((this.abbreviation == null) ? 0 : this.abbreviation.hashCode());
result = prime * result + ((this.abbreviationKey == null) ? 0 : this.abbreviationKey.hashCode());
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
return result;
}
/** {@inheritDoc} */
@SuppressWarnings("checkstyle:designforextension")
@Override
public int hashCode()
{
return this.cachedHashCode;
}
/** {@inheritDoc} */
@SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
@Override
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Unit<?> other = (Unit<?>) obj;
if (this.abbreviation == null)
{
if (other.abbreviation != null)
return false;
}
else if (!this.abbreviation.equals(other.abbreviation))
return false;
if (this.abbreviationKey == null)
{
if (other.abbreviationKey != null)
return false;
}
else if (!this.abbreviationKey.equals(other.abbreviationKey))
return false;
if (this.name == null)
{
if (other.name != null)
return false;
}
else if (!this.name.equals(other.name))
return false;
return true;
}
/**
* Test if two units are the same, except for the name and abbreviation. This means for instance that for the MassUnits
* METRIC_TON and MEGAGRAM (both 1000 kg) equals(...) will yield false, but equalsIgnoreNaming will yield true.
* @param obj Object; the object to compare with
* @return true if the two units are the same numerically, except for the naming and/or abbreviation
*/
public abstract boolean equalsIgnoreNaming(Object obj);
}