View Javadoc
1   package org.djunits.unit;
2   
3   import java.util.Objects;
4   
5   import org.djunits.quantity.def.Quantity;
6   import org.djunits.unit.scale.Scale;
7   import org.djunits.unit.si.SIPrefix;
8   import org.djunits.unit.si.SIPrefixes;
9   import org.djunits.unit.system.UnitSystem;
10  import org.djutils.exceptions.Throw;
11  
12  /**
13   * The AbstractUnit is the parent class of all units, and encodes the common behavior of the units. All units are internally
14   * <i>stored</i> relative to a standard unit with conversion factor. This means that e.g., a meter is stored with conversion
15   * 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
16   * kilometers, we have to <i>divide</i> by the conversion factor.
17   * <p>
18   * 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
19   * meters), but there are also non-linear scales such as the percentage for an angle, where 90 degrees equals an infinite
20   * percentage. Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights
21   * reserved. See for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS
22   * project is distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style
23   * license</a>.
24   * @author Alexander Verbraeck
25   * @param <U> the unit type
26   * @param <Q> the quantity type belonging to this unit
27   */
28  public abstract class AbstractUnit<U extends UnitInterface<U, Q>, Q extends Quantity<Q, U>> implements UnitInterface<U, Q>
29  {
30      /** The textual abbreviation of the unit, which is also the id. */
31      private final String textualAbbreviation;
32  
33      /** The symbolic representation of the unit, which is the default for display. */
34      private final String displayAbbreviation;
35  
36      /** The full name of the unit. */
37      private final String name;
38  
39      /** The scale to use to convert between this unit and the standard (e.g., SI, BASE) unit. */
40      private final Scale scale;
41  
42      /** The unit system, e.g. SI or Imperial. */
43      private final UnitSystem unitSystem;
44  
45      /** The SI-prefix, if any, to allow localization of the SI-prefix. */
46      private SIPrefix siPrefix = null;
47  
48      /**
49       * Create a new unit, where the textual abbreviation is the same as the display abbreviation.
50       * @param textualAbbreviation the textual abbreviation of the unit, which also serves as the id
51       * @param name the full name of the unit
52       * @param scale the scale to use to convert between this unit and the standard (e.g., SI, BASE) unit
53       * @param unitSystem unit system, e.g. SI or Imperial
54       */
55      public AbstractUnit(final String textualAbbreviation, final String name, final Scale scale, final UnitSystem unitSystem)
56      {
57          this(textualAbbreviation, textualAbbreviation, name, scale, unitSystem);
58      }
59  
60      /**
61       * Create a new unit, with textual abbreviation(s) and a display abbreviation.
62       * @param textualAbbreviation the textual abbreviation of the unit, which also serves as the id
63       * @param displayAbbreviation the display abbreviation of the unit
64       * @param name the full name of the unit
65       * @param scale the scale to use to convert between this unit and the standard (e.g., SI, BASE) unit
66       * @param unitSystem unit system, e.g. SI or Imperial
67       */
68      public AbstractUnit(final String textualAbbreviation, final String displayAbbreviation, final String name,
69              final Scale scale, final UnitSystem unitSystem)
70      {
71          // Check the validity
72          String cName = Units.unitClassName(getClass());
73          Throw.whenNull(textualAbbreviation, "Constructing unit %s: textualAbbreviation cannot be null", cName);
74          Throw.whenNull(displayAbbreviation, "Constructing unit %s: displayAbbreviation cannot be null", cName);
75          Throw.when(textualAbbreviation.length() == 0, UnitRuntimeException.class,
76                  "Constructing unit %s: textualAbbreviation string cannot be empty", cName);
77          Throw.whenNull(name, "Constructing unit %s.%s: name cannot be null", cName, textualAbbreviation);
78          Throw.when(name.length() == 0, UnitRuntimeException.class, "Constructing unit %s.%s: name.length cannot be 0", cName,
79                  textualAbbreviation);
80          Throw.whenNull(scale, "Constructing unit %s.%s: scale cannot be null", cName, textualAbbreviation);
81          Throw.whenNull(unitSystem, "Constructing unit %s.%s: unitSystem cannot be null", cName, textualAbbreviation);
82  
83          // Build the unit
84          this.displayAbbreviation = displayAbbreviation;
85          this.textualAbbreviation = textualAbbreviation;
86          this.name = name;
87          this.scale = scale;
88          this.unitSystem = unitSystem;
89  
90          // Register the unit
91          Units.register(this);
92      }
93  
94      /**
95       * Generate and register the units with all SI-prefixes.
96       * @param kilo whether the base unit already has a "kilo" in its abbreviation/name, such as the kilogram
97       * @param perUnit whether it is a "per unit" such as "per meter"
98       * @return the unit for method chaining
99       */
100     @SuppressWarnings("unchecked")
101     public U generateSiPrefixes(final boolean kilo, final boolean perUnit)
102     {
103         String cName = getClass().getSimpleName();
104         Throw.when(!getScale().isBaseScale(), UnitRuntimeException.class,
105                 "SI prefixes generation for unit %s only applicable to unit with base scale", cName);
106         Throw.when(kilo && !perUnit && !getId().startsWith("k"), UnitRuntimeException.class,
107                 "SI prefixes generated for kilo class for unit %s, but abbreviation %s does not start with a 'k'", cName,
108                 getId());
109         Throw.when(kilo && perUnit && !getId().startsWith("/k"), UnitRuntimeException.class,
110                 "SI prefixes generated for per-kilo class for unit %s, but abbreviation %s does not start with '/k'", cName,
111                 getId());
112         Throw.when(kilo && !perUnit && !getStoredName().startsWith("kilo"), UnitRuntimeException.class,
113                 "SI prefixes generated for kilo class for unit %s, but name %s does not start with 'kilo'", cName,
114                 getStoredName());
115         Throw.when(kilo && perUnit && !getStoredName().startsWith("per kilo"), UnitRuntimeException.class,
116                 "SI prefixes generated for kilo class for unit %s, but name %s does not start with 'per kilo'", cName,
117                 getStoredName());
118         Throw.when(perUnit && !getId().startsWith("/"), UnitRuntimeException.class,
119                 "SI prefixes generated for 'per' class for unit %s, but abbreviation %s does not start with '/'", cName,
120                 getId());
121         Throw.when(perUnit && !getStoredName().startsWith("per "), UnitRuntimeException.class,
122                 "SI prefixes generated for 'per' class for unit %s, but name %s does not start with 'per '", cName,
123                 getStoredName());
124 
125         if (!kilo)
126         {
127             if (!perUnit)
128             {
129                 for (SIPrefix sip : SIPrefixes.UNIT_PREFIXES.values())
130                 {
131                     U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation(),
132                             sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation(),
133                             sip.getPrefixName() + getStoredName(), sip.getFactor(), getUnitSystem());
134                     unit.setSiPrefix(sip);
135                 }
136             }
137             else
138             {
139                 for (SIPrefix sip : SIPrefixes.PER_UNIT_PREFIXES.values())
140                 {
141                     U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation().substring(1),
142                             sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation().substring(1),
143                             sip.getPrefixName() + getStoredName().substring(4), sip.getFactor(), getUnitSystem());
144                     unit.setSiPrefix(sip);
145                 }
146             }
147         }
148         else
149         {
150             if (!perUnit)
151             {
152                 for (SIPrefix sip : SIPrefixes.KILO_PREFIXES.values())
153                 {
154                     U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation().substring(1),
155                             sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation().substring(1),
156                             sip.getPrefixName() + getStoredName().substring(4), sip.getFactor(), getUnitSystem());
157                     unit.setSiPrefix(sip);
158                 }
159             }
160             else
161             {
162                 for (SIPrefix sip : SIPrefixes.PER_KILO_PREFIXES.values())
163                 {
164                     U unit = deriveUnit(sip.getDefaultTextualPrefix() + getStoredTextualAbbreviation().substring(2),
165                             sip.getDefaultDisplayPrefix() + getStoredDisplayAbbreviation().substring(2),
166                             sip.getPrefixName() + getStoredName().substring(8), sip.getFactor(), getUnitSystem());
167                     unit.setSiPrefix(sip);
168                 }
169             }
170         }
171         return (U) this;
172     }
173 
174     /**
175      * Return a linearly scaled derived unit for this unit, where the textual abbreviation is the same as the display
176      * abbreviation.
177      * @param textualAbbreviation the textual abbreviation of the unit, which doubles as the id
178      * @param name the full name of the unit
179      * @param scaleFactor the (linear) scale factor to multiply with the current (linear) scale factor
180      * @param unitSystem unit system, e.g. SI or Imperial
181      * @return a derived unit for this unit
182      */
183     @SuppressWarnings("checkstyle:hiddenfield")
184     public U deriveUnit(final String textualAbbreviation, final String name, final double scaleFactor,
185             final UnitSystem unitSystem)
186     {
187         return deriveUnit(textualAbbreviation, textualAbbreviation, name, scaleFactor, unitSystem);
188     }
189 
190     /**
191      * Return a linearly scaled derived unit for this unit, with textual abbreviation(s) and a display abbreviation.
192      * @param textualAbbreviation the textual abbreviation of the unit, which doubles as the id
193      * @param displayAbbreviation the display abbreviation of the unit
194      * @param name the full name of the unit
195      * @param scaleFactor the (linear) scale factor to multiply with the current (linear) scale factor
196      * @param unitSystem unit system, e.g. SI or Imperial
197      * @return a derived unit for this unit
198      */
199     @SuppressWarnings("checkstyle:hiddenfield")
200     public abstract U deriveUnit(String textualAbbreviation, String displayAbbreviation, String name, double scaleFactor,
201             UnitSystem unitSystem);
202 
203     @Override
204     public String getId()
205     {
206         return this.textualAbbreviation;
207     }
208 
209     @Override
210     public String getStoredTextualAbbreviation()
211     {
212         return this.textualAbbreviation;
213     }
214 
215     @Override
216     public String getTextualAbbreviation()
217     {
218         return Units.localizedUnitTextualAbbr(getClass(), getId());
219     }
220 
221     @Override
222     public String getStoredDisplayAbbreviation()
223     {
224         return this.displayAbbreviation;
225     }
226 
227     @Override
228     public String getDisplayAbbreviation()
229     {
230         return Units.localizedUnitDisplayAbbr(getClass(), getId());
231     }
232 
233     @Override
234     public String getStoredName()
235     {
236         return this.name;
237     }
238 
239     @Override
240     public String getName()
241     {
242         return Units.localizedUnitName(getClass(), getId());
243     }
244 
245     @Override
246     public Scale getScale()
247     {
248         return this.scale;
249     }
250 
251     @Override
252     public UnitSystem getUnitSystem()
253     {
254         return this.unitSystem;
255     }
256 
257     @SuppressWarnings({"unchecked", "hiddenfield"})
258     @Override
259     public U setSiPrefix(final SIPrefix siPrefix)
260     {
261         this.siPrefix = siPrefix;
262         return (U) this;
263     }
264 
265     @Override
266     public U setSiPrefix(final String prefix)
267     {
268         SIPrefix sip = SIPrefixes.getSiPrefix(prefix);
269         return setSiPrefix(sip);
270     }
271 
272     @Override
273     public U setSiPrefixKilo(final String prefix)
274     {
275         SIPrefix sip = SIPrefixes.getSiPrefixKilo(prefix);
276         return setSiPrefix(sip);
277     }
278 
279     @Override
280     public U setSiPrefixPer(final String prefix)
281     {
282         SIPrefix sip = SIPrefixes.getSiPrefixPer(prefix);
283         return setSiPrefix(sip);
284     }
285 
286     @Override
287     public SIPrefix getSiPrefix()
288     {
289         return this.siPrefix;
290     }
291 
292     @Override
293     public int hashCode()
294     {
295         return Objects.hash(this.displayAbbreviation, this.name, this.scale, this.textualAbbreviation, this.unitSystem);
296     }
297 
298     @Override
299     @SuppressWarnings("checkstyle:needbraces")
300     public boolean equals(final Object obj)
301     {
302         if (this == obj)
303             return true;
304         if (obj == null)
305             return false;
306         if (getClass() != obj.getClass())
307             return false;
308         AbstractUnit<?, ?> other = (AbstractUnit<?, ?>) obj;
309         return Objects.equals(this.displayAbbreviation, other.displayAbbreviation) && Objects.equals(this.name, other.name)
310                 && Objects.equals(this.scale, other.scale)
311                 && Objects.equals(this.textualAbbreviation, other.textualAbbreviation)
312                 && Objects.equals(this.unitSystem, other.unitSystem);
313     }
314 
315     @Override
316     public String toString()
317     {
318         return this.displayAbbreviation;
319     }
320 }