View Javadoc
1   package org.opentrafficsim.core.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.opentrafficsim.core.locale.Localization;
10  import org.opentrafficsim.core.unit.unitsystem.UnitSystem;
11  import org.reflections.Reflections;
12  
13  /**
14   * All units are internally <i>stored</i> relative to a standard unit with conversion factor. This means that e.g., a meter is
15   * stored with conversion factor 1.0, whereas kilometer is stored with a conversion factor 1000.0. This means that if we want to
16   * display a meter as kilometers, we have to <i>divide</i> by the conversion factor.
17   * <p>
18   * Copyright (c) 2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
20   * <p>
21   * @version May 15, 2014 <br>
22   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @param <U> the unit for transformation reasons
24   */
25  public abstract class Unit<U extends Unit<U>> implements Serializable
26  {
27      /** */
28      private static final long serialVersionUID = 20140607;
29  
30      /** the key to the locale file for the long name of the unit. */
31      private final String nameKey;
32  
33      /** the key to the locale file for the abbreviation of the unit. */
34      private final String abbreviationKey;
35  
36      /** the unit system, e.g. SI or Imperial. */
37      private final UnitSystem unitSystem;
38  
39      /** multiply by this number to convert to the standard (e.g., SI) unit. */
40      private final double conversionFactorToStandardUnit;
41  
42      /** SI unit information. */
43      private SICoefficients siCoefficients;
44  
45      /** static map of all defined coefficient strings, to avoid double creation and allow lookup. */
46      private static final Map<String, SICoefficients> SI_COEFFICIENTS = new HashMap<String, SICoefficients>();
47  
48      /** static map of all defined coefficient strings, mapped to the existing units. */
49      private static final Map<String, Map<Class<Unit<?>>, Unit<?>>> SI_UNITS =
50          new HashMap<String, Map<Class<Unit<?>>, Unit<?>>>();
51  
52      /** a static map of all defined units. */
53      private static final Map<String, Set<Unit<?>>> UNITS = new HashMap<String, Set<Unit<?>>>();
54  
55      /** localization information. */
56      private static Localization localization = new Localization("localeunit");
57  
58      /** has this class been initialized? */
59      private static boolean initialized = false;
60  
61      /** force all units to be loaded. */
62      private static void initialize()
63      {
64          Reflections reflections = new Reflections("org.opentrafficsim.core.unit");
65          @SuppressWarnings("rawtypes")
66          Set<Class<? extends Unit>> classes = reflections.getSubTypesOf(Unit.class);
67  
68          for (@SuppressWarnings("rawtypes")
69          Class<? extends Unit> clazz : classes)
70          {
71              try
72              {
73                  Class.forName(clazz.getCanonicalName());
74              }
75              catch (Exception exception)
76              {
77                  // TODO professional logging of errors
78                  // exception.printStackTrace();
79                  System.err.println("Could not load class " + clazz.getCanonicalName());
80              }
81          }
82          initialized = true;
83      }
84  
85      /**
86       * Build a standard unit.
87       * @param nameKey the key to the locale file for the long name of the unit
88       * @param abbreviationKey the key to the locale file for the abbreviation of the unit
89       * @param unitSystem the unit system, e.g. SI or Imperial
90       * @throws UnitException when unit cannot be added to the list of units
91       */
92      /*-
93      public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem) throws UnitException
94      {
95          this.conversionFactorToStandardUnit = 1.0;
96          this.nameKey = nameKey;
97          this.abbreviationKey = abbreviationKey;
98          this.unitSystem = unitSystem;
99          addUnit(this);
100     }
101     */
102 
103     /**
104      * Build a unit with a conversion factor to another unit.
105      * @param nameKey the key to the locale file for the long name of the unit
106      * @param abbreviationKey the key to the locale file for the abbreviation of the unit
107      * @param unitSystem the unit system, e.g. SI or Imperial
108      * @param referenceUnit the unit to convert to
109      * @param conversionFactorToReferenceUnit multiply a value in this unit by the factor to convert to the given reference unit
110      * @throws UnitException when unit cannot be added to the list of units
111      */
112     /*-
113     public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem, final U referenceUnit,
114         final double conversionFactorToReferenceUnit) throws UnitException
115     {
116         // as it can happen that this method is called for the standard unit (when it is still null) we have to catch
117         // the null pointer for the reference unit here.
118         if (referenceUnit == null)
119         {
120             this.conversionFactorToStandardUnit = 1.0;
121         }
122         else
123         {
124             this.conversionFactorToStandardUnit =
125                 referenceUnit.getConversionFactorToStandardUnit() * conversionFactorToReferenceUnit;
126         }
127         this.nameKey = nameKey;
128         this.abbreviationKey = abbreviationKey;
129         this.unitSystem = unitSystem;
130         addUnit(this);
131     }
132     */
133 
134     /**
135      * Build a standard unit.
136      * @param nameKey the key to the locale file for the long name of the unit
137      * @param abbreviationKey the key to the locale file for the abbreviation of the unit
138      * @param unitSystem the unit system, e.g. SI or Imperial
139      * @param safe Boolean; if true, a UnitException is silently ignored; if false a UnitException is an Error
140      */
141     public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem, final boolean safe)
142     {
143         this.conversionFactorToStandardUnit = 1.0;
144         this.nameKey = nameKey;
145         this.abbreviationKey = abbreviationKey;
146         this.unitSystem = unitSystem;
147         try
148         {
149             addUnit(this);
150         }
151         catch (UnitException ue)
152         {
153             if (!safe)
154             {
155                 throw new Error(ue);
156             }
157             // TODO complain wherever we can
158         }
159     }
160 
161     /**
162      * Build a unit with a conversion factor to another unit.
163      * @param nameKey the key to the locale file for the long name of the unit
164      * @param abbreviationKey the key to the locale file for the abbreviation of the unit
165      * @param unitSystem the unit system, e.g. SI or Imperial
166      * @param referenceUnit the unit to convert to
167      * @param conversionFactorToReferenceUnit multiply a value in this unit by the factor to convert to the given reference unit
168      * @param safe Boolean; if true, a UnitException is silently ignored; if false a UnitException is an Error
169      */
170     public Unit(final String nameKey, final String abbreviationKey, final UnitSystem unitSystem, final U referenceUnit,
171         final double conversionFactorToReferenceUnit, final boolean safe)
172     {
173         // as it can happen that this method is called for the standard unit (when it is still null) we have to catch
174         // the null pointer for the reference unit here.
175         if (referenceUnit == null)
176         {
177             this.conversionFactorToStandardUnit = 1.0;
178         }
179         else
180         {
181             this.conversionFactorToStandardUnit =
182                 referenceUnit.getConversionFactorToStandardUnit() * conversionFactorToReferenceUnit;
183         }
184         this.nameKey = nameKey;
185         this.abbreviationKey = abbreviationKey;
186         this.unitSystem = unitSystem;
187         try
188         {
189             addUnit(this);
190         }
191         catch (UnitException ue)
192         {
193             if (!safe)
194             {
195                 throw new Error(ue);
196             }
197             // TODO complain wherever we can
198         }
199     }
200 
201     /**
202      * Add a unit to the overview collection of existing units, and resolve the coefficients.
203      * @param unit the unit to add. It will be stored in a set belonging to the simple class name String, e.g. "ForceUnit".
204      * @throws UnitException when parsing or normalizing the SI coefficient string fails.
205      */
206     private void addUnit(final Unit<U> unit) throws UnitException
207     {
208         if (!UNITS.containsKey(unit.getClass().getSimpleName()))
209         {
210             UNITS.put(unit.getClass().getSimpleName(), new HashSet<Unit<?>>());
211         }
212         UNITS.get(unit.getClass().getSimpleName()).add(unit);
213 
214         // resolve the SI coefficients, and normalize string
215         String siCoefficientsString = SICoefficients.normalize(getSICoefficientsString()).toString();
216         if (SI_COEFFICIENTS.containsKey(siCoefficientsString))
217         {
218             this.siCoefficients = SI_COEFFICIENTS.get(siCoefficientsString);
219         }
220         else
221         {
222             this.siCoefficients = new SICoefficients(SICoefficients.parse(siCoefficientsString));
223             SI_COEFFICIENTS.put(siCoefficientsString, this.siCoefficients);
224         }
225 
226         // add the standard unit
227         Map<Class<Unit<?>>, Unit<?>> unitMap = SI_UNITS.get(siCoefficientsString);
228         if (unitMap == null)
229         {
230             unitMap = new HashMap<Class<Unit<?>>, Unit<?>>();
231             SI_UNITS.put(siCoefficientsString, unitMap);
232         }
233         if (!unitMap.containsKey(unit.getClass()))
234         {
235             @SuppressWarnings("unchecked")
236             Class<Unit<?>> clazz = (Class<Unit<?>>) unit.getClass();
237             if (this.getStandardUnit() == null)
238             {
239                 unitMap.put(clazz, this);
240             }
241             else
242             {
243                 unitMap.put(clazz, this.getStandardUnit());
244             }
245         }
246     }
247 
248     /**
249      * Return a set of defined units for a given unit type.
250      * @param <V> the unit type to use in this method.
251      * @param unitClass the class for which the units are requested, e.g. ForceUnit.class
252      * @return the set of defined units belonging to the provided class. The empty set will be returned in case the unit type
253      *         does not have any units.
254      */
255     @SuppressWarnings("unchecked")
256     public static <V extends Unit<V>> Set<V> getUnits(final Class<V> unitClass)
257     {
258         if (!initialized)
259         {
260             initialize();
261         }
262         Set<V> returnSet = new HashSet<V>();
263         if (UNITS.containsKey(unitClass.getSimpleName()))
264         {
265             for (Unit<?> unit : UNITS.get(unitClass.getSimpleName()))
266             {
267                 returnSet.add((V) unit);
268             }
269         }
270         return returnSet;
271     }
272 
273     /**
274      * Return a copy of the set of all defined units for this unit type.
275      * @return the set of defined units belonging to this Unit class. The empty set will be returned in case the unit type does
276      *         not have any units.
277      */
278     // TODO call static method from the instance method? The two are now too similar.
279     @SuppressWarnings("unchecked")
280     public final Set<Unit<U>> getAllUnitsOfThisType()
281     {
282         if (!initialized)
283         {
284             initialize();
285         }
286         Set<Unit<U>> returnSet = new HashSet<Unit<U>>();
287         if (UNITS.containsKey(this.getClass().getSimpleName()))
288         {
289             for (Unit<?> unit : UNITS.get(this.getClass().getSimpleName()))
290             {
291                 returnSet.add((Unit<U>) unit);
292             }
293         }
294         return returnSet;
295     }
296 
297     /**
298      * @return name, e.g. meters per second
299      */
300     public final String getName()
301     {
302         return localization.getString(this.nameKey);
303     }
304 
305     /**
306      * @return name key, e.g. TimeUnit.MetersPerSecond
307      */
308     public final String getNameKey()
309     {
310         return this.nameKey;
311     }
312 
313     /**
314      * @return abbreviation, e.g., m/s
315      */
316     public final String getAbbreviation()
317     {
318         return localization.getString(this.abbreviationKey);
319     }
320 
321     /**
322      * @return abbreviation key, e.g. TimeUnit.m/s
323      */
324     public final String getAbbreviationKey()
325     {
326         return this.abbreviationKey;
327     }
328 
329     /**
330      * @return conversionFactorToStandardUnit. Multiply by this number to convert to the standard (e.g., SI) unit
331      */
332     public final double getConversionFactorToStandardUnit()
333     {
334         return this.conversionFactorToStandardUnit;
335     }
336 
337     /**
338      * @return unitSystem, e.g. SI or Imperial
339      */
340     public final UnitSystem getUnitSystem()
341     {
342         return this.unitSystem;
343     }
344 
345     /**
346      * @return the SI standard unit for this unit, or the de facto standard unit if SI is not available
347      */
348     public abstract U getStandardUnit();
349 
350     /**
351      * @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
352      *         normalized)
353      */
354     public abstract String getSICoefficientsString();
355 
356     /**
357      * @return the SI coefficients
358      */
359     public final SICoefficients getSICoefficients()
360     {
361         return this.siCoefficients;
362     }
363 
364     /**
365      * @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
366      * @return a set with the Units belonging to this string, or an empty set when it does not exist
367      */
368     public static Set<Unit<?>> lookupUnitWithSICoefficients(final String normalizedSICoefficientsString)
369     {
370         if (!initialized)
371         {
372             initialize();
373         }
374         if (SI_UNITS.containsKey(normalizedSICoefficientsString))
375         {
376             return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
377         }
378         return new HashSet<Unit<?>>();
379     }
380 
381     /**
382      * @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
383      * @return a set of Units belonging to this string, or a set with a new unit when it does not yet exist
384      */
385     // TODO call other static method? The two are now too similar.
386     public static Set<Unit<?>> lookupOrCreateUnitWithSICoefficients(final String normalizedSICoefficientsString)
387     {
388         if (!initialized)
389         {
390             initialize();
391         }
392         if (SI_UNITS.containsKey(normalizedSICoefficientsString))
393         {
394             return new HashSet<Unit<?>>(SI_UNITS.get(normalizedSICoefficientsString).values());
395         }
396         SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString);
397         Set<Unit<?>> unitSet = new HashSet<Unit<?>>();
398         unitSet.add(unit);
399         return unitSet;
400     }
401 
402     /**
403      * @param normalizedSICoefficientsString the normalized string (e.g., kg.m/s2) to look up
404      * @return the Unit belonging to this string, or a new unit when it does not yet exist
405      */
406     public static SIUnit lookupOrCreateSIUnitWithSICoefficients(final String normalizedSICoefficientsString)
407     {
408         if (!initialized)
409         {
410             initialize();
411         }
412         if (SI_UNITS.containsKey(normalizedSICoefficientsString)
413             && SI_UNITS.get(normalizedSICoefficientsString).containsKey(SIUnit.class))
414         {
415             return (SIUnit) SI_UNITS.get(normalizedSICoefficientsString).get(SIUnit.class);
416         }
417         SIUnit unit = new SIUnit("SIUnit." + normalizedSICoefficientsString);
418         return unit;
419     }
420 
421     /** {@inheritDoc} */
422     @Override
423     public final String toString()
424     {
425         return getAbbreviation();
426     }
427 
428 }