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 }