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