View Javadoc
1   package org.djunits.unit;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.djunits.locale.Localization;
13  import org.djunits.unit.scale.Scale;
14  import org.djunits.unit.scale.StandardScale;
15  import org.djunits.unit.unitsystem.UnitSystem;
16  
17  /**
18   * All units are internally <i>stored</i> relative to a standard unit with conversion factor. This means that e.g., a meter is
19   * stored with conversion factor 1.0, whereas kilometer is stored with a conversion factor 1000.0. This means that if we want to
20   * express a length meter in kilometers, we have to <i>divide</i> by the conversion factor.
21   * <p>
22   * Copyright (c) 2015-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="http://djunits.org/docs/license.html">DJUNITS License</a>.
24   * <p>
25   * $LastChangedDate: 2019-03-03 00:53:50 +0100 (Sun, 03 Mar 2019) $, @version $Revision: 349 $, by $Author: averbraeck $,
26   * initial version May 15, 2014 <br>
27   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
28   * @param <U> the unit for transformation reasons
29   */
30  public abstract class Unit<U extends Unit<U>> implements Serializable
31  {
32      /** */
33      private static final long serialVersionUID = 20140607;
34  
35      /** The key to the locale file for the abbreviation of the unit, or null when it is a user-defined unit. */
36      private final String abbreviationKey;
37  
38      /**
39       * 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
40       * be null if the locale has to be used, i.e. for standard units.
41       */
42      private final String name;
43  
44      /**
45       * The abbreviation of the unit in case it does not exist in the locale file, e.g. when defining a run-time unit. The
46       * abbreviation will be null if the locale has to be used, i.e. for standard units.
47       */
48      private final String abbreviation;
49  
50      /** The unit system, e.g. SI or Imperial. */
51      private final UnitSystem unitSystem;
52  
53      /** The scale to use to convert between this unit and the standard (e.g., SI) unit. */
54      private final Scale scale;
55  
56      /** SI unit information. */
57      private SICoefficients siCoefficients;
58  
59      /** A static map of all defined coefficient strings, to avoid double creation and allow lookup. */
60      private static final Map<String, SICoefficients> SI_COEFFICIENTS = new HashMap<String, SICoefficients>();
61  
62      /** A static map of all defined coefficient strings, mapped to the existing units. */
63      private static final Map<String, Map<Class<Unit<?>>, Unit<?>>> SI_UNITS =
64              new HashMap<String, Map<Class<Unit<?>>, Unit<?>>>();
65  
66      /** A static map of all defined units. */
67      private static final Map<String, Set<Unit<?>>> UNITS = new HashMap<String, Set<Unit<?>>>();
68  
69      /** Has this class been initialized? */
70      private static boolean standardUnitsInitialized = false;
71  
72      /** Standard (SI) unit or not? */
73      private boolean baseSIUnit;
74  
75      /** The array of the names of the standard units. */
76      public static final String[] STANDARD_UNITS = new String[] {"AbsoluteTemperatureUnit", "AccelerationUnit", "AngleSolidUnit",
77              "AngleUnit", "AreaUnit", "DensityUnit", "DimensionlessUnit", "DirectionUnit", "DurationUnit",
78              "ElectricalChargeUnit", "ElectricalCurrentUnit", "ElectricalPotentialUnit", "ElectricalResistanceUnit",
79              "EnergyUnit", "FlowMassUnit", "FlowVolumeUnit", "ForceUnit", "FrequencyUnit", "LengthUnit", "LinearDensityUnit",
80              "MassUnit", "MoneyUnit", "MoneyPerAreaUnit", "MoneyPerEnergyUnit", "MoneyPerLengthUnit", "MoneyPerMassUnit",
81              "MoneyPerDurationUnit", "MoneyPerVolumeUnit", "PositionUnit", "PowerUnit", "PressureUnit", "SpeedUnit",
82              "TemperatureUnit", "TimeUnit", "TorqueUnit", "VolumeUnit"};
83  
84      /** the cached hashcode. */
85      private final int cachedHashCode;
86  
87      /** The default locale. */
88      private static Localization localization = new Localization("localeunit");
89  
90      /** The cached default locale information. */
91      private String[] cachedDefaultLocaleInfo;
92  
93      /**
94       * Force all units to be loaded so we can use static lookup functions for the standard units.
95       */
96      private static void initializeStandardUnits()
97      {
98          for (String className : STANDARD_UNITS)
99          {
100             try
101             {
102                 Class.forName("org.djunits.unit." + className);
103             }
104             catch (Exception exception)
105             {
106                 // TODO professional logging of errors
107                 System.err.println("Could not load class org.djunits.unit." + className);
108             }
109         }
110         standardUnitsInitialized = true;
111     }
112 
113     /**
114      * Build a standard unit and create the fields for that unit. For a standard unit, a UnitException is silently ignored.
115      * @param abbreviationKey String; the key to the locale file for the abbreviation of the unit
116      * @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
117      */
118     protected Unit(final String abbreviationKey, final UnitSystem unitSystem)
119     {
120         this.scale = StandardScale.SCALE;
121         this.baseSIUnit = true;
122         this.abbreviationKey = abbreviationKey;
123         this.name = null;
124         this.abbreviation = null;
125         this.cachedDefaultLocaleInfo = localization.getDefaultString(this.abbreviationKey).split("\\|");
126         this.unitSystem = unitSystem;
127         this.cachedHashCode = generateHashCode();
128         try
129         {
130             addUnit(this);
131         }
132         catch (UnitException ue)
133         {
134             // silently ignore.
135         }
136     }
137 
138     /**
139      * Build a standard unit with a specific conversion scale to/from the standard unit. For a standard unit, a UnitException is
140      * silently ignored.
141      * @param abbreviationKey String; the key to the locale file for the abbreviation of the unit, otherwise the abbreviation
142      *            itself
143      * @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
144      * @param scale Scale; the conversion scale to use for this unit
145      */
146     protected Unit(final String abbreviationKey, final UnitSystem unitSystem, final Scale scale)
147     {
148         this.scale = scale;
149         this.baseSIUnit = scale.isBaseSIScale();
150         this.abbreviationKey = abbreviationKey;
151         this.name = null;
152         this.abbreviation = null;
153         this.cachedDefaultLocaleInfo = localization.getDefaultString(this.abbreviationKey).split("\\|");
154         this.unitSystem = unitSystem;
155         this.cachedHashCode = generateHashCode();
156         try
157         {
158             addUnit(this);
159         }
160         catch (UnitException ue)
161         {
162             // silently ignore
163         }
164     }
165 
166     /**
167      * Build a user-defined unit and create the fields for that unit. This unit is a user-defined unit where the localization
168      * files do not have an entry. A UnitException is thrown as a RunTimeException.
169      * @param name String; the key to the locale file for the long name of the unit, otherwise the name itself
170      * @param abbreviation String; the key to the locale file for the abbreviation of the unit, otherwise the abbreviation
171      *            itself
172      * @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
173      */
174     protected Unit(final String name, final String abbreviation, final UnitSystem unitSystem)
175     {
176         this.scale = StandardScale.SCALE;
177         this.baseSIUnit = true;
178         this.abbreviationKey = null;
179         this.name = name;
180         this.abbreviation = abbreviation;
181         this.unitSystem = unitSystem;
182         this.cachedHashCode = generateHashCode();
183         try
184         {
185             addUnit(this);
186         }
187         catch (UnitException ue)
188         {
189             throw new RuntimeException(ue);
190         }
191     }
192 
193     /**
194      * Build a user-defined unit with a specific conversion scale to/from the standard unit. This unit is a user-defined unit
195      * where the localization files do not have an entry. A UnitException is thrown as a RunTimeException.
196      * @param name String; the key to the locale file for the long name of the unit, otherwise the name itself
197      * @param abbreviation String; the key to the locale file for the abbreviation of the unit, otherwise the abbreviation
198      *            itself
199      * @param unitSystem UnitSystem; the unit system, e.g. SI or Imperial
200      * @param scale Scale; the conversion scale to use for this unit
201      */
202     protected Unit(final String name, final String abbreviation, final UnitSystem unitSystem, final Scale scale)
203     {
204         this.scale = scale;
205         this.baseSIUnit = scale.isBaseSIScale();
206         this.abbreviationKey = null;
207         this.name = name;
208         this.abbreviation = abbreviation;
209         this.unitSystem = unitSystem;
210         this.cachedHashCode = generateHashCode();
211         try
212         {
213             addUnit(this);
214         }
215         catch (UnitException ue)
216         {
217             throw new RuntimeException(ue);
218         }
219     }
220 
221     /**
222      * Report if this unit support localization.
223      * @return boolean; true if this unit supports localization; false if it does not
224      */
225     public final boolean isLocalizable()
226     {
227         return this.abbreviationKey != null;
228     }
229 
230     /**
231      * Add a unit to the overview collection of existing units, and resolve the coefficients.
232      * @param unit Unit&lt;U&gt;; the unit to add. It will be stored in a set belonging to the simple class name String, e.g.
233      *            "ForceUnit".
234      * @throws UnitException when parsing or normalizing the SI coefficient string fails.
235      */
236     private void addUnit(final Unit<U> unit) throws UnitException
237     {
238         if (!UNITS.containsKey(unit.getClass().getSimpleName()))
239         {
240             UNITS.put(unit.getClass().getSimpleName(), new HashSet<Unit<?>>());
241         }
242         UNITS.get(unit.getClass().getSimpleName()).add(unit);
243 
244         // resolve the SI coefficients, and normalize string
245         String siCoefficientsString = SICoefficients.normalize(getSICoefficientsString()).toString();
246         if (SI_COEFFICIENTS.containsKey(siCoefficientsString))
247         {
248             this.siCoefficients = SI_COEFFICIENTS.get(siCoefficientsString);
249         }
250         else
251         {
252             this.siCoefficients = new SICoefficients(SICoefficients.parse(siCoefficientsString));
253             SI_COEFFICIENTS.put(siCoefficientsString, this.siCoefficients);
254         }
255 
256         // add the standard unit
257         Map<Class<Unit<?>>, Unit<?>> unitMap = SI_UNITS.get(siCoefficientsString);
258         if (unitMap == null)
259         {
260             unitMap = new HashMap<Class<Unit<?>>, Unit<?>>();
261             SI_UNITS.put(siCoefficientsString, unitMap);
262         }
263         if (!unitMap.containsKey(unit.getClass()))
264         {
265             @SuppressWarnings("unchecked")
266             Class<Unit<?>> clazz = (Class<Unit<?>>) unit.getClass();
267             if (this.getStandardUnit() == null)
268             {
269                 unitMap.put(clazz, this);
270             }
271             else
272             {
273                 unitMap.put(clazz, this.getStandardUnit());
274             }
275         }
276     }
277 
278     /**
279      * Return a set of defined units for a given unit type.
280      * @param <V> the unit type to use in this method.
281      * @param unitClass Class&lt;V&gt;; the class for which the units are requested, e.g. ForceUnit.class
282      * @return the set of defined units belonging to the provided class. The empty set will be returned in case the unit type
283      *         does not have any units.
284      */
285     @SuppressWarnings("unchecked")
286     public static <V extends Unit<V>> Set<V> getUnits(final Class<V> unitClass)
287     {
288         if (!standardUnitsInitialized)
289         {
290             initializeStandardUnits();
291         }
292         Set<V> returnSet = new HashSet<V>();
293         if (UNITS.containsKey(unitClass.getSimpleName()))
294         {
295             for (Unit<?> unit : UNITS.get(unitClass.getSimpleName()))
296             {
297                 returnSet.add((V) unit);
298             }
299         }
300         return returnSet;
301     }
302 
303     /**
304      * Return a copy of the set of all defined units for this unit type.
305      * @return the set of defined units belonging to this Unit class. The empty set will be returned in case the unit type does
306      *         not have any units.
307      */
308     @SuppressWarnings("unchecked")
309     public final Set<Unit<U>> getAllUnitsOfThisType()
310     {
311         if (!standardUnitsInitialized)
312         {
313             initializeStandardUnits();
314         }
315         Set<Unit<U>> returnSet = new HashSet<Unit<U>>();
316         if (UNITS.containsKey(this.getClass().getSimpleName()))
317         {
318             for (Unit<?> unit : UNITS.get(this.getClass().getSimpleName()))
319             {
320                 returnSet.add((Unit<U>) unit);
321             }
322         }
323         return returnSet;
324     }
325 
326     /**
327      * @return name, e.g. meters per second
328      */
329     public final String getName()
330     {
331         if (this.name != null)
332         {
333             return this.name;
334         }
335         if (localization.isDefault())
336         {
337             return getDefaultLocaleName();
338         }
339         String[] loc = localization.getString(this.abbreviationKey).split("\\|");
340         if (loc.length >= 2)
341         {
342             return loc[1].trim();
343         }
344         if (loc.length >= 1)
345         {
346             return loc[0].trim();
347         }
348         return this.abbreviationKey;
349     }
350 
351     /**
352      * @return the name in the default locale, e.g. meters per second
353      */
354     public final String getDefaultLocaleName()
355     {
356         if (this.name != null)
357         {
358             return this.name;
359         }
360         if (this.cachedDefaultLocaleInfo.length >= 2)
361         {
362             return this.cachedDefaultLocaleInfo[1].trim();
363         }
364         if (this.cachedDefaultLocaleInfo.length >= 1)
365         {
366             return this.cachedDefaultLocaleInfo[0].trim();
367         }
368         return this.abbreviationKey;
369     }
370 
371     /**
372      * @return abbreviation, e.g., m/s
373      */
374     public final String getAbbreviation()
375     {
376         if (this.abbreviation != null)
377         {
378             return this.abbreviation;
379         }
380         if (localization.isDefault())
381         {
382             return getDefaultLocaleAbbreviation();
383         }
384         String[] loc = localization.getString(this.abbreviationKey).split("\\|");
385         if (loc.length >= 1)
386         {
387             return loc[0].trim();
388         }
389         return this.abbreviationKey;
390     }
391 
392     /**
393      * @return the abbreviation in the default locale, e.g., m/s
394      */
395     public final String getDefaultLocaleAbbreviation()
396     {
397         if (this.abbreviation != null)
398         {
399             return this.abbreviation;
400         }
401         if (this.cachedDefaultLocaleInfo.length >= 1)
402         {
403             return this.cachedDefaultLocaleInfo[0].trim();
404         }
405         return this.abbreviationKey;
406     }
407 
408     /**
409      * This method returns the abbreviation key, or null in case the abbreviation is hard coded.
410      * @return abbreviation key, e.g. DurationUnit.m/s, or null for a user-defined unit
411      */
412     public final String getAbbreviationKey()
413     {
414         return this.abbreviationKey;
415     }
416 
417     /**
418      * @return the textual display types of the unit. In case the list contains more than one abbreviation, the first one is the
419      *         default one. In case none is available, the standard abbreviation is used. In case that one is also not available
420      *         the abbreviation key is used.
421      */
422     public final List<String> getTextualRepresentations()
423     {
424         if (this.abbreviation != null)
425         {
426             return Arrays.asList(new String[] {this.abbreviation});
427         }
428         if (localization.isDefault())
429         {
430             return getDefaultLocaleTextualRepresentations();
431         }
432         String[] loc = localization.getString(this.abbreviationKey).split("\\|");
433         if (loc.length >= 3)
434         {
435             List<String> textList = new ArrayList<>();
436             for (int i = 2; i < loc.length; i++)
437             {
438                 textList.add(loc[i].trim());
439             }
440             return textList;
441         }
442         if (loc.length >= 1)
443         {
444             return Arrays.asList(new String[] {loc[0].trim()});
445         }
446         return Arrays.asList(new String[] {this.abbreviationKey});
447     }
448 
449     /**
450      * @return the textual display types of the unit in the default locale; In case the list contains more than one
451      *         abbreviation, the first one is the default one. In case none is available, the standard abbreviation is used. In
452      *         case that one is also not available the abbreviation key is used.
453      */
454     public final List<String> getDefaultLocaleTextualRepresentations()
455     {
456         if (this.abbreviation != null)
457         {
458             return Arrays.asList(new String[] {this.abbreviation});
459         }
460         if (this.cachedDefaultLocaleInfo.length >= 3)
461         {
462             List<String> textList = new ArrayList<>();
463             for (int i = 2; i < this.cachedDefaultLocaleInfo.length; i++)
464             {
465                 textList.add(this.cachedDefaultLocaleInfo[i].trim());
466             }
467             return textList;
468         }
469         if (this.cachedDefaultLocaleInfo.length >= 1)
470         {
471             return Arrays.asList(new String[] {this.cachedDefaultLocaleInfo[0].trim()});
472         }
473         return Arrays.asList(new String[] {this.abbreviationKey});
474     }
475 
476     /**
477      * @return the default textual display representation of the unit; In case there are more textual representations, the first
478      *         one is the default one. In case none is available, the standard abbreviation is used. In case that one is also
479      *         not available the abbreviation key is used.
480      */
481     public final String getDefaultTextualRepresentation()
482     {
483         if (this.abbreviation != null)
484         {
485             return this.abbreviation;
486         }
487         if (localization.isDefault())
488         {
489             return getDefaultLocaleTextualRepresentation();
490         }
491         String[] loc = localization.getString(this.abbreviationKey).split("\\|");
492         if (loc.length >= 3)
493         {
494             return loc[2].trim();
495         }
496         if (loc.length >= 1)
497         {
498             return loc[0].trim();
499         }
500         return this.abbreviationKey;
501     }
502 
503     /**
504      * @return the default textual display representation of the unit in the default locale; In case there are more textual
505      *         representations, the first one is the default one. In case none is available, the standard abbreviation is used.
506      *         In case that one is also not available the abbreviation key is used.
507      */
508     public final String getDefaultLocaleTextualRepresentation()
509     {
510         if (this.abbreviation != null)
511         {
512             return this.abbreviation;
513         }
514         if (this.cachedDefaultLocaleInfo.length >= 3)
515         {
516             return this.cachedDefaultLocaleInfo[2].trim();
517         }
518         if (this.cachedDefaultLocaleInfo.length >= 1)
519         {
520             return this.cachedDefaultLocaleInfo[0].trim();
521         }
522         return this.abbreviationKey;
523     }
524 
525     /**
526      * @return the scale to transform between this unit and the reference (e.g., SI) unit.
527      */
528     @SuppressWarnings("checkstyle:designforextension")
529     public Scale getScale()
530     {
531         return this.scale;
532     }
533 
534     /**
535      * @return unitSystem, e.g. SI or Imperial
536      */
537     public final UnitSystem getUnitSystem()
538     {
539         return this.unitSystem;
540     }
541 
542     /**
543      * @return the SI standard unit for this unit, or the de facto standard unit if SI is not available
544      */
545     public abstract U getStandardUnit();
546 
547     /**
548      * @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
549      *         normalized)
550      */
551     public abstract String getSICoefficientsString();
552 
553     /**
554      * @return the SI coefficients
555      */
556     public final SICoefficients getSICoefficients()
557     {
558         return this.siCoefficients;
559     }
560 
561     /**
562      * Determine whether this unit is the standard unit.
563      * @return boolean; whether this is the standard unit or not
564      */
565     public final boolean isBaseSIUnit()
566     {
567         return this.baseSIUnit;
568     }
569 
570     /**
571      * @param normalizedSICoefficientsString String; the normalized string (e.g., kg.m/s2) to look up
572      * @return a set with the Units belonging to this string, or an empty set when it does not exist
573      */
574     public static Set<Unit<?>> lookupUnitWithSICoefficients(final String normalizedSICoefficientsString)
575     {
576         if (!standardUnitsInitialized)
577         {
578             initializeStandardUnits();
579         }
580         if (SI_UNITS.containsKey(normalizedSICoefficientsString))
581         {
582             return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
583         }
584         return new HashSet<Unit<?>>();
585     }
586 
587     /**
588      * @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
589      * @return a set of Units belonging to this string, or a set with a new unit when it does not yet exist
590      */
591     public static Set<Unit<?>> lookupOrCreateUnitWithSICoefficients(final String normalizedSICoefficientsString)
592     {
593         if (!standardUnitsInitialized)
594         {
595             initializeStandardUnits();
596         }
597         if (SI_UNITS.containsKey(normalizedSICoefficientsString))
598         {
599             return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
600         }
601         SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString);
602         Set<Unit<?>> unitSet = new HashSet<Unit<?>>();
603         unitSet.add(unit);
604         return unitSet;
605     }
606 
607     /**
608      * @param normalizedSICoefficientsString String; the normalized string (e.g., kg.m/s2) to look up
609      * @return the Unit belonging to this string, or a new unit when it does not yet exist
610      */
611     public static SIUnit lookupOrCreateSIUnitWithSICoefficients(final String normalizedSICoefficientsString)
612     {
613         if (!standardUnitsInitialized)
614         {
615             initializeStandardUnits();
616         }
617         if (SI_UNITS.containsKey(normalizedSICoefficientsString)
618                 && SI_UNITS.get(normalizedSICoefficientsString).containsKey(SIUnit.class))
619         {
620             return (SIUnit) SI_UNITS.get(normalizedSICoefficientsString).get(SIUnit.class);
621         }
622         SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString);
623         return unit;
624     }
625 
626     /** {@inheritDoc} */
627     @Override
628     public final String toString()
629     {
630         return getAbbreviation();
631     }
632 
633     /**
634      * Generate a hashCode for caching.
635      * @return a hashCode that is consistent with the equals() method.
636      */
637     public final int generateHashCode()
638     {
639         final int prime = 31;
640         int result = 1;
641         result = prime * result + ((this.abbreviation == null) ? 0 : this.abbreviation.hashCode());
642         result = prime * result + ((this.abbreviationKey == null) ? 0 : this.abbreviationKey.hashCode());
643         result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
644         return result;
645     }
646 
647     /** {@inheritDoc} */
648     @SuppressWarnings("checkstyle:designforextension")
649     @Override
650     public int hashCode()
651     {
652         return this.cachedHashCode;
653     }
654 
655     /** {@inheritDoc} */
656     @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
657     @Override
658     public boolean equals(final Object obj)
659     {
660         if (this == obj)
661             return true;
662         if (obj == null)
663             return false;
664         if (getClass() != obj.getClass())
665             return false;
666         Unit<?> other = (Unit<?>) obj;
667         if (this.abbreviation == null)
668         {
669             if (other.abbreviation != null)
670                 return false;
671         }
672         else if (!this.abbreviation.equals(other.abbreviation))
673             return false;
674         if (this.abbreviationKey == null)
675         {
676             if (other.abbreviationKey != null)
677                 return false;
678         }
679         else if (!this.abbreviationKey.equals(other.abbreviationKey))
680             return false;
681         if (this.name == null)
682         {
683             if (other.name != null)
684                 return false;
685         }
686         else if (!this.name.equals(other.name))
687             return false;
688         return true;
689     }
690 
691     /**
692      * Test if two units are the same, except for the name and abbreviation. This means for instance that for the MassUnits
693      * METRIC_TON and MEGAGRAM (both 1000 kg) equals(...) will yield false, but equalsIgnoreNaming will yield true.
694      * @param obj Object; the object to compare with
695      * @return true if the two units are the same numerically, except for the naming and/or abbreviation
696      */
697     public abstract boolean equalsIgnoreNaming(Object obj);
698 
699 }