View Javadoc
1   package org.djunits.unit;
2   
3   import java.io.Serializable;
4   import java.util.HashMap;
5   import java.util.HashSet;
6   import java.util.Map;
7   import java.util.Set;
8   
9   import org.djunits.locale.Localization;
10  import org.djunits.unit.scale.Scale;
11  import org.djunits.unit.scale.StandardScale;
12  import org.djunits.unit.unitsystem.UnitSystem;
13  
14  /**
15   * All units are internally <i>stored</i> relative to a standard unit with conversion factor. This means that e.g., a meter is
16   * stored with conversion factor 1.0, whereas kilometer is stored with a conversion factor 1000.0. This means that if we want to
17   * express a length meter in kilometers, we have to <i>divide</i> by the conversion factor.
18   * <p>
19   * Copyright (c) 2015-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
20   * BSD-style license. See <a href="http://djunits.org/docs/license.html">DJUNITS License</a>.
21   * <p>
22   * $LastChangedDate: 2018-01-28 03:17:44 +0100 (Sun, 28 Jan 2018) $, @version $Revision: 256 $, by $Author: averbraeck $,
23   * initial version May 15, 2014 <br>
24   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
25   * @param <U> the unit for transformation reasons
26   */
27  public abstract class Unit<U extends Unit<U>> implements Serializable
28  {
29      /** */
30      private static final long serialVersionUID = 20140607;
31  
32      /** The key to the locale file for the long name of the unit, or null when it is a user-defined unit. */
33      private final String nameKey;
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      @SuppressWarnings("checkstyle:visibilitymodifier")
55      protected Scale scale;
56  
57      /** SI unit information. */
58      private SICoefficients siCoefficients;
59  
60      /** A static map of all defined coefficient strings, to avoid double creation and allow lookup. */
61      private static final Map<String, SICoefficients> SI_COEFFICIENTS = new HashMap<String, SICoefficients>();
62  
63      /** A static map of all defined coefficient strings, mapped to the existing units. */
64      private static final Map<String, Map<Class<Unit<?>>, Unit<?>>> SI_UNITS =
65              new HashMap<String, Map<Class<Unit<?>>, Unit<?>>>();
66  
67      /** A static map of all defined units. */
68      private static final Map<String, Set<Unit<?>>> UNITS = new HashMap<String, Set<Unit<?>>>();
69  
70      /** Localization information. */
71      private static Localization localization = new Localization("localeunit");
72  
73      /** Has this class been initialized? */
74      private static boolean initialized = false;
75  
76      /** Standard (SI) unit or not? */
77      private boolean baseSIUnit;
78  
79      /** The array of the names of the standard units. */
80      public static final String[] STANDARD_UNITS = new String[] { "AbsoluteTemperatureUnit", "AccelerationUnit",
81              "AngleSolidUnit", "AngleUnit", "AreaUnit", "DensityUnit", "DimensionlessUnit", "DirectionUnit", "DurationUnit",
82              "ElectricalChargeUnit", "ElectricalCurrentUnit", "ElectricalPotentialUnit", "ElectricalResistanceUnit",
83              "EnergyUnit", "FlowMassUnit", "FlowVolumeUnit", "ForceUnit", "FrequencyUnit", "LengthUnit", "LinearDensityUnit",
84              "MassUnit", "MoneyUnit", "MoneyPerAreaUnit", "MoneyPerEnergyUnit", "MoneyPerLengthUnit", "MoneyPerMassUnit",
85              "MoneyPerDurationUnit", "MoneyPerVolumeUnit", "PositionUnit", "PowerUnit", "PressureUnit", "SpeedUnit",
86              "TemperatureUnit", "TimeUnit", "TorqueUnit", "VolumeUnit" };
87  
88      /** the cached hashcode. */
89      private final int cachedHashCode;
90  
91      /** Force all units to be loaded. */
92      private static void initialize()
93      {
94          for (String className : STANDARD_UNITS)
95          {
96              try
97              {
98                  Class.forName("org.djunits.unit." + className);
99              }
100             catch (Exception exception)
101             {
102                 // TODO professional logging of errors
103                 System.err.println("Could not load class org.djunits.unit." + className);
104             }
105         }
106         initialized = true;
107     }
108 
109     /**
110      * Build a standard unit and create the fields for a unit. If the parameter standardUnit is true, it is a standard unit
111      * where name is the nameKey, and abbreviation is the abbreviationKey; if false, this unit is a user-defined unit where the
112      * localization files do not have an entry. If standardUnit is true, a UnitException is silently ignored; if standardUnit is
113      * false a UnitException is thrown as a RunTimeException.
114      * @param nameOrNameKey if standardUnit: the key to the locale file for the long name of the unit, otherwise the name itself
115      * @param abbreviationOrAbbreviationKey if standardUnit: the key to the locale file for the abbreviation of the unit,
116      *            otherwise the abbreviation itself
117      * @param unitSystem the unit system, e.g. SI or Imperial
118      * @param standardUnit indicates whether it is a standard unit with a definition in the locale, or a user-defined unit
119      */
120     protected Unit(final String nameOrNameKey, final String abbreviationOrAbbreviationKey, final UnitSystem unitSystem,
121             final boolean standardUnit)
122     {
123         this.scale = StandardScale.SCALE;
124         this.baseSIUnit = true;
125         if (standardUnit)
126         {
127             this.nameKey = nameOrNameKey;
128             this.abbreviationKey = abbreviationOrAbbreviationKey;
129             this.name = null;
130             this.abbreviation = null;
131         }
132         else
133         {
134             this.nameKey = null;
135             this.abbreviationKey = null;
136             this.name = nameOrNameKey;
137             this.abbreviation = abbreviationOrAbbreviationKey;
138         }
139         this.unitSystem = unitSystem;
140         this.cachedHashCode = generateHashCode();
141         try
142         {
143             addUnit(this);
144         }
145         catch (UnitException ue)
146         {
147             if (!standardUnit)
148             {
149                 throw new RuntimeException(ue);
150             }
151         }
152     }
153 
154     /**
155      * Build a unit with a specific conversion scale to/from the standard unit. If the parameter standardUnit is true, it is a
156      * standard unit where name is the nameKey, and abbreviation is the abbreviationKey; if false, this unit is a user-defined
157      * unit where the localization files do not have an entry. If standardUnit is true, a UnitException is silently ignored; if
158      * standardUnit is false a UnitException is thrown as a RunTimeException.
159      * @param nameOrNameKey if standardUnit: the key to the locale file for the long name of the unit, otherwise the name itself
160      * @param abbreviationOrAbbreviationKey if standardUnit: the key to the locale file for the abbreviation of the unit,
161      *            otherwise the abbreviation itself
162      * @param unitSystem the unit system, e.g. SI or Imperial
163      * @param scale the conversion scale to use for this unit
164      * @param standardUnit indicates whether it is a standard unit with a definition in the locale, or a user-defined unit
165      */
166     protected Unit(final String nameOrNameKey, final String abbreviationOrAbbreviationKey, final UnitSystem unitSystem,
167             final Scale scale, final boolean standardUnit)
168     {
169         this(nameOrNameKey, abbreviationOrAbbreviationKey, unitSystem, standardUnit);
170         this.scale = scale;
171         this.baseSIUnit = scale.isBaseSIScale();
172     }
173 
174     /**
175      * Report if this unit support localization.
176      * @return boolean; true if this unit supports localization; false if it does not
177      */
178     public final boolean isLocalizable()
179     {
180         return this.nameKey != null;
181     }
182 
183     /**
184      * Add a unit to the overview collection of existing units, and resolve the coefficients.
185      * @param unit the unit to add. It will be stored in a set belonging to the simple class name String, e.g. "ForceUnit".
186      * @throws UnitException when parsing or normalizing the SI coefficient string fails.
187      */
188     private void addUnit(final Unit<U> unit) throws UnitException
189     {
190         if (!UNITS.containsKey(unit.getClass().getSimpleName()))
191         {
192             UNITS.put(unit.getClass().getSimpleName(), new HashSet<Unit<?>>());
193         }
194         UNITS.get(unit.getClass().getSimpleName()).add(unit);
195 
196         // resolve the SI coefficients, and normalize string
197         String siCoefficientsString = SICoefficients.normalize(getSICoefficientsString()).toString();
198         if (SI_COEFFICIENTS.containsKey(siCoefficientsString))
199         {
200             this.siCoefficients = SI_COEFFICIENTS.get(siCoefficientsString);
201         }
202         else
203         {
204             this.siCoefficients = new SICoefficients(SICoefficients.parse(siCoefficientsString));
205             SI_COEFFICIENTS.put(siCoefficientsString, this.siCoefficients);
206         }
207 
208         // add the standard unit
209         Map<Class<Unit<?>>, Unit<?>> unitMap = SI_UNITS.get(siCoefficientsString);
210         if (unitMap == null)
211         {
212             unitMap = new HashMap<Class<Unit<?>>, Unit<?>>();
213             SI_UNITS.put(siCoefficientsString, unitMap);
214         }
215         if (!unitMap.containsKey(unit.getClass()))
216         {
217             @SuppressWarnings("unchecked")
218             Class<Unit<?>> clazz = (Class<Unit<?>>) unit.getClass();
219             if (this.getStandardUnit() == null)
220             {
221                 unitMap.put(clazz, this);
222             }
223             else
224             {
225                 unitMap.put(clazz, this.getStandardUnit());
226             }
227         }
228     }
229 
230     /**
231      * Return a set of defined units for a given unit type.
232      * @param <V> the unit type to use in this method.
233      * @param unitClass the class for which the units are requested, e.g. ForceUnit.class
234      * @return the set of defined units belonging to the provided class. The empty set will be returned in case the unit type
235      *         does not have any units.
236      */
237     @SuppressWarnings("unchecked")
238     public static <V extends Unit<V>> Set<V> getUnits(final Class<V> unitClass)
239     {
240         if (!initialized)
241         {
242             initialize();
243         }
244         Set<V> returnSet = new HashSet<V>();
245         if (UNITS.containsKey(unitClass.getSimpleName()))
246         {
247             for (Unit<?> unit : UNITS.get(unitClass.getSimpleName()))
248             {
249                 returnSet.add((V) unit);
250             }
251         }
252         return returnSet;
253     }
254 
255     /**
256      * Return a copy of the set of all defined units for this unit type.
257      * @return the set of defined units belonging to this Unit class. The empty set will be returned in case the unit type does
258      *         not have any units.
259      */
260     // TODO call static method from the instance method? The two are now too similar.
261     @SuppressWarnings("unchecked")
262     public final Set<Unit<U>> getAllUnitsOfThisType()
263     {
264         if (!initialized)
265         {
266             initialize();
267         }
268         Set<Unit<U>> returnSet = new HashSet<Unit<U>>();
269         if (UNITS.containsKey(this.getClass().getSimpleName()))
270         {
271             for (Unit<?> unit : UNITS.get(this.getClass().getSimpleName()))
272             {
273                 returnSet.add((Unit<U>) unit);
274             }
275         }
276         return returnSet;
277     }
278 
279     /**
280      * @return name, e.g. meters per second
281      */
282     public final String getName()
283     {
284         if (this.name != null)
285         {
286             return this.name;
287         }
288         return localization.getString(this.nameKey);
289     }
290 
291     /**
292      * This method returns the name key, or null in case the name is hard coded.
293      * @return name key, e.g. DurationUnit.MetersPerSecond, or null for a user-defined unit
294      */
295     public final String getNameKey()
296     {
297         return this.nameKey;
298     }
299 
300     /**
301      * @return abbreviation, e.g., m/s
302      */
303     public final String getAbbreviation()
304     {
305         if (this.abbreviation != null)
306         {
307             return this.abbreviation;
308         }
309         return localization.getString(this.abbreviationKey);
310     }
311 
312     /**
313      * This method returns the abbreviation key, or null in case the abbreviation is hard coded.
314      * @return abbreviation key, e.g. DurationUnit.m/s, or null for a user-defined unit
315      */
316     public final String getAbbreviationKey()
317     {
318         return this.abbreviationKey;
319     }
320 
321     /**
322      * @return the scale to transform between this unit and the reference (e.g., SI) unit.
323      */
324     @SuppressWarnings("checkstyle:designforextension")
325     public Scale getScale()
326     {
327         return this.scale;
328     }
329 
330     /**
331      * @return unitSystem, e.g. SI or Imperial
332      */
333     public final UnitSystem getUnitSystem()
334     {
335         return this.unitSystem;
336     }
337 
338     /**
339      * @return the SI standard unit for this unit, or the de facto standard unit if SI is not available
340      */
341     public abstract U getStandardUnit();
342 
343     /**
344      * @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
345      *         normalized)
346      */
347     public abstract String getSICoefficientsString();
348 
349     /**
350      * @return the SI coefficients
351      */
352     public final SICoefficients getSICoefficients()
353     {
354         return this.siCoefficients;
355     }
356 
357     /**
358      * Determine whether this unit is the standard unit.
359      * @return boolean; whether this is the standard unit or not
360      */
361     public final boolean isBaseSIUnit()
362     {
363         return this.baseSIUnit;
364     }
365 
366     /**
367      * @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
368      * @return a set with the Units belonging to this string, or an empty set when it does not exist
369      */
370     public static Set<Unit<?>> lookupUnitWithSICoefficients(final String normalizedSICoefficientsString)
371     {
372         if (!initialized)
373         {
374             initialize();
375         }
376         if (SI_UNITS.containsKey(normalizedSICoefficientsString))
377         {
378             return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
379         }
380         return new HashSet<Unit<?>>();
381     }
382 
383     /**
384      * @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
385      * @return a set of Units belonging to this string, or a set with a new unit when it does not yet exist
386      */
387     // TODO call other static method? The two are now too similar.
388     public static Set<Unit<?>> lookupOrCreateUnitWithSICoefficients(final String normalizedSICoefficientsString)
389     {
390         if (!initialized)
391         {
392             initialize();
393         }
394         if (SI_UNITS.containsKey(normalizedSICoefficientsString))
395         {
396             return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
397         }
398         SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString);
399         Set<Unit<?>> unitSet = new HashSet<Unit<?>>();
400         unitSet.add(unit);
401         return unitSet;
402     }
403 
404     /**
405      * @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
406      * @return the Unit belonging to this string, or a new unit when it does not yet exist
407      */
408     public static SIUnit lookupOrCreateSIUnitWithSICoefficients(final String normalizedSICoefficientsString)
409     {
410         if (!initialized)
411         {
412             initialize();
413         }
414         if (SI_UNITS.containsKey(normalizedSICoefficientsString)
415                 && SI_UNITS.get(normalizedSICoefficientsString).containsKey(SIUnit.class))
416         {
417             return (SIUnit) SI_UNITS.get(normalizedSICoefficientsString).get(SIUnit.class);
418         }
419         SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString, false);
420         return unit;
421     }
422 
423     /** {@inheritDoc} */
424     @Override
425     public final String toString()
426     {
427         return getAbbreviation();
428     }
429 
430     /**
431      * Generate a hashCode for caching.
432      * @return a hashCode that is consistent with the equals() method.
433      */
434     public final int generateHashCode()
435     {
436         final int prime = 31;
437         int result = 1;
438         result = prime * result + ((this.abbreviation == null) ? 0 : this.abbreviation.hashCode());
439         result = prime * result + ((this.abbreviationKey == null) ? 0 : this.abbreviationKey.hashCode());
440         result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
441         result = prime * result + ((this.nameKey == null) ? 0 : this.nameKey.hashCode());
442         return result;
443     }
444 
445     /** {@inheritDoc} */
446     @SuppressWarnings("checkstyle:designforextension")
447     @Override
448     public int hashCode()
449     {
450         return this.cachedHashCode;
451     }
452 
453     /** {@inheritDoc} */
454     @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
455     @Override
456     public boolean equals(final Object obj)
457     {
458         if (this == obj)
459             return true;
460         if (obj == null)
461             return false;
462         if (getClass() != obj.getClass())
463             return false;
464         Unit<?> other = (Unit<?>) obj;
465         if (this.abbreviation == null)
466         {
467             if (other.abbreviation != null)
468                 return false;
469         }
470         else if (!this.abbreviation.equals(other.abbreviation))
471             return false;
472         if (this.abbreviationKey == null)
473         {
474             if (other.abbreviationKey != null)
475                 return false;
476         }
477         else if (!this.abbreviationKey.equals(other.abbreviationKey))
478             return false;
479         if (this.name == null)
480         {
481             if (other.name != null)
482                 return false;
483         }
484         else if (!this.name.equals(other.name))
485             return false;
486         if (this.nameKey == null)
487         {
488             if (other.nameKey != null)
489                 return false;
490         }
491         else if (!this.nameKey.equals(other.nameKey))
492             return false;
493         return true;
494     }
495 
496 }