AbstractUnit.java
package org.djunits.unit;
import java.util.Objects;
import org.djunits.quantity.def.Quantity;
import org.djunits.unit.scale.Scale;
import org.djunits.unit.si.SIPrefix;
import org.djunits.unit.si.SIPrefixes;
import org.djunits.unit.system.UnitSystem;
import org.djutils.exceptions.Throw;
/**
* The AbstractUnit is the parent class of all units, and encodes the common behavior of the units. 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, and 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>
* The conversion to and from the base unit is left to a Scale. Many scales are linear (e.g., converting dm, cm, and mm to
* meters), but there are also non-linear scales such as the percentage for an angle, where 90 degrees equals an infinite
* percentage. Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights
* reserved. See for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS
* project is distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style
* license</a>.
* @author Alexander Verbraeck
* @param <U> the unit type
* @param <Q> the quantity type belonging to this unit
*/
public abstract class AbstractUnit<U extends UnitInterface<U, Q>, Q extends Quantity<Q, U>> implements UnitInterface<U, Q>
{
/** The textual abbreviation of the unit, which is also the id. */
private final String textualAbbreviation;
/** The symbolic representation of the unit, which is the default for display. */
private final String displayAbbreviation;
/** The full name of the unit. */
private final String name;
/** The scale to use to convert between this unit and the standard (e.g., SI, BASE) unit. */
private final Scale scale;
/** The unit system, e.g. SI or Imperial. */
private final UnitSystem unitSystem;
/** The SI-prefix, if any, to allow localization of the SI-prefix. */
private SIPrefix siPrefix = null;
/**
* Create a new unit, where the textual abbreviation is the same as the display abbreviation.
* @param textualAbbreviation the textual abbreviation of the unit, which also serves as the id
* @param name the full name of the unit
* @param scale the scale to use to convert between this unit and the standard (e.g., SI, BASE) unit
* @param unitSystem unit system, e.g. SI or Imperial
*/
public AbstractUnit(final String textualAbbreviation, final String name, final Scale scale, final UnitSystem unitSystem)
{
this(textualAbbreviation, textualAbbreviation, name, scale, unitSystem);
}
/**
* Create a new unit, with textual abbreviation(s) and a display abbreviation.
* @param textualAbbreviation the textual abbreviation of the unit, which also serves as the id
* @param displayAbbreviation the display abbreviation of the unit
* @param name the full name of the unit
* @param scale the scale to use to convert between this unit and the standard (e.g., SI, BASE) unit
* @param unitSystem unit system, e.g. SI or Imperial
*/
public AbstractUnit(final String textualAbbreviation, final String displayAbbreviation, final String name,
final Scale scale, final UnitSystem unitSystem)
{
// Check the validity
String cName = Units.unitClassName(getClass());
Throw.whenNull(textualAbbreviation, "Constructing unit %s: textualAbbreviation cannot be null", cName);
Throw.whenNull(displayAbbreviation, "Constructing unit %s: displayAbbreviation cannot be null", cName);
Throw.when(textualAbbreviation.length() == 0, UnitRuntimeException.class,
"Constructing unit %s: textualAbbreviation string cannot be empty", cName);
Throw.whenNull(name, "Constructing unit %s.%s: name cannot be null", cName, textualAbbreviation);
Throw.when(name.length() == 0, UnitRuntimeException.class, "Constructing unit %s.%s: name.length cannot be 0", cName,
textualAbbreviation);
Throw.whenNull(scale, "Constructing unit %s.%s: scale cannot be null", cName, textualAbbreviation);
Throw.whenNull(unitSystem, "Constructing unit %s.%s: unitSystem cannot be null", cName, textualAbbreviation);
// Build the unit
this.displayAbbreviation = displayAbbreviation;
this.textualAbbreviation = textualAbbreviation;
this.name = name;
this.scale = scale;
this.unitSystem = unitSystem;
// Register the unit
Units.register(this);
}
/**
* Generate and register the units with all SI-prefixes.
* @param kilo whether the base unit already has a "kilo" in its abbreviation/name, such as the kilogram
* @param perUnit whether it is a "per unit" such as "per meter"
* @return the unit for method chaining
*/
@SuppressWarnings("unchecked")
public U generateSiPrefixes(final boolean kilo, final boolean perUnit)
{
String cName = getClass().getSimpleName();
Throw.when(!getScale().isBaseScale(), UnitRuntimeException.class,
"SI prefixes generation for unit %s only applicable to unit with base scale", cName);
Throw.when(kilo && !perUnit && !getId().startsWith("k"), UnitRuntimeException.class,
"SI prefixes generated for kilo class for unit %s, but abbreviation %s does not start with a 'k'", cName,
getId());
Throw.when(kilo && perUnit && !getId().startsWith("/k"), UnitRuntimeException.class,
"SI prefixes generated for per-kilo class for unit %s, but abbreviation %s does not start with '/k'", cName,
getId());
Throw.when(kilo && !perUnit && !getStoredName().startsWith("kilo"), UnitRuntimeException.class,
"SI prefixes generated for kilo class for unit %s, but name %s does not start with 'kilo'", cName,
getStoredName());
Throw.when(kilo && perUnit && !getStoredName().startsWith("per kilo"), UnitRuntimeException.class,
"SI prefixes generated for kilo class for unit %s, but name %s does not start with 'per kilo'", cName,
getStoredName());
Throw.when(perUnit && !getId().startsWith("/"), UnitRuntimeException.class,
"SI prefixes generated for 'per' class for unit %s, but abbreviation %s does not start with '/'", cName,
getId());
Throw.when(perUnit && !getStoredName().startsWith("per "), UnitRuntimeException.class,
"SI prefixes generated for 'per' class for unit %s, but name %s does not start with 'per '", cName,
getStoredName());
if (!kilo)
{
if (!perUnit)
{
for (SIPrefix sip : SIPrefixes.UNIT_PREFIXES.values())
{
U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation(),
sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation(),
sip.getPrefixName() + getStoredName(), sip.getFactor(), getUnitSystem());
unit.setSiPrefix(sip);
}
}
else
{
for (SIPrefix sip : SIPrefixes.PER_UNIT_PREFIXES.values())
{
U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation().substring(1),
sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation().substring(1),
sip.getPrefixName() + getStoredName().substring(4), sip.getFactor(), getUnitSystem());
unit.setSiPrefix(sip);
}
}
}
else
{
if (!perUnit)
{
for (SIPrefix sip : SIPrefixes.KILO_PREFIXES.values())
{
U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation().substring(1),
sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation().substring(1),
sip.getPrefixName() + getStoredName().substring(4), sip.getFactor(), getUnitSystem());
unit.setSiPrefix(sip);
}
}
else
{
for (SIPrefix sip : SIPrefixes.PER_KILO_PREFIXES.values())
{
U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation().substring(2),
sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation().substring(2),
sip.getPrefixName() + getStoredName().substring(8), sip.getFactor(), getUnitSystem());
unit.setSiPrefix(sip);
}
}
}
return (U) this;
}
/**
* Return a linearly scaled derived unit for this unit, where the textual abbreviation is the same as the display
* abbreviation.
* @param textualAbbreviation the textual abbreviation of the unit, which doubles as the id
* @param name the full name of the unit
* @param scaleFactor the (linear) scale factor to multiply with the current (linear) scale factor
* @param unitSystem unit system, e.g. SI or Imperial
* @return a derived unit for this unit
*/
@SuppressWarnings("checkstyle:hiddenfield")
public U deriveUnit(final String textualAbbreviation, final String name, final double scaleFactor,
final UnitSystem unitSystem)
{
return deriveUnit(textualAbbreviation, textualAbbreviation, name, scaleFactor, unitSystem);
}
/**
* Return a linearly scaled derived unit for this unit, with textual abbreviation(s) and a display abbreviation.
* @param textualAbbreviation the textual abbreviation of the unit, which doubles as the id
* @param displayAbbreviation the display abbreviation of the unit
* @param name the full name of the unit
* @param scaleFactor the (linear) scale factor to multiply with the current (linear) scale factor
* @param unitSystem unit system, e.g. SI or Imperial
* @return a derived unit for this unit
*/
@SuppressWarnings("checkstyle:hiddenfield")
public abstract U deriveUnit(String textualAbbreviation, String displayAbbreviation, String name, double scaleFactor,
UnitSystem unitSystem);
@Override
public String getId()
{
return this.textualAbbreviation;
}
@Override
public String getStoredTextualAbbreviation()
{
return this.textualAbbreviation;
}
@Override
public String getTextualAbbreviation()
{
return Units.localizedUnitTextualAbbr(getClass(), getId());
}
@Override
public String getStoredDisplayAbbreviation()
{
return this.displayAbbreviation;
}
@Override
public String getDisplayAbbreviation()
{
return Units.localizedUnitDisplayAbbr(getClass(), getId());
}
@Override
public String getStoredName()
{
return this.name;
}
@Override
public String getName()
{
return Units.localizedUnitName(getClass(), getId());
}
@Override
public Scale getScale()
{
return this.scale;
}
@Override
public UnitSystem getUnitSystem()
{
return this.unitSystem;
}
@SuppressWarnings({"unchecked", "hiddenfield"})
@Override
public U setSiPrefix(final SIPrefix siPrefix)
{
this.siPrefix = siPrefix;
return (U) this;
}
@Override
public U setSiPrefix(final String prefix)
{
SIPrefix sip = SIPrefixes.getSiPrefix(prefix);
return setSiPrefix(sip);
}
@Override
public U setSiPrefixKilo(final String prefix)
{
SIPrefix sip = SIPrefixes.getSiPrefixKilo(prefix);
return setSiPrefix(sip);
}
@Override
public U setSiPrefixPer(final String prefix)
{
SIPrefix sip = SIPrefixes.getSiPrefixPer(prefix);
return setSiPrefix(sip);
}
@Override
public SIPrefix getSiPrefix()
{
return this.siPrefix;
}
@Override
public int hashCode()
{
return Objects.hash(this.displayAbbreviation, this.name, this.scale, this.textualAbbreviation, this.unitSystem);
}
@Override
@SuppressWarnings("checkstyle:needbraces")
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractUnit<?, ?> other = (AbstractUnit<?, ?>) obj;
return Objects.equals(this.displayAbbreviation, other.displayAbbreviation) && Objects.equals(this.name, other.name)
&& Objects.equals(this.scale, other.scale)
&& Objects.equals(this.textualAbbreviation, other.textualAbbreviation)
&& Objects.equals(this.unitSystem, other.unitSystem);
}
@Override
public String toString()
{
return this.displayAbbreviation;
}
}