1 | /*
|
---|
2 | * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences.
|
---|
3 | * Copyright (C) 2006 - JScience (http://jscience.org/)
|
---|
4 | * All rights reserved.
|
---|
5 | *
|
---|
6 | * Permission to use, copy, modify, and distribute this software is
|
---|
7 | * freely granted, provided that this notice is preserved.
|
---|
8 | */
|
---|
9 | package javax.measure.unit;
|
---|
10 |
|
---|
11 | import java.io.Serializable;
|
---|
12 | import java.util.HashMap;
|
---|
13 |
|
---|
14 | import javax.measure.converter.ConversionException;
|
---|
15 | import javax.measure.converter.MultiplyConverter;
|
---|
16 | import javax.measure.converter.RationalConverter;
|
---|
17 | import javax.measure.converter.UnitConverter;
|
---|
18 | import javax.measure.quantity.Dimensionless;
|
---|
19 | import javax.measure.quantity.Quantity;
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * <p> This class represents a determinate {@link javax.measure.quantity.Quantity
|
---|
23 | * quantity} (as of length, time, heat, or value) adopted as a standard
|
---|
24 | * of measurement.</p>
|
---|
25 | *
|
---|
26 | * <p> It is helpful to think of instances of this class as recording the
|
---|
27 | * history by which they are created. Thus, for example, the string
|
---|
28 | * "g/kg" (which is a dimensionless unit) would result from invoking
|
---|
29 | * the method toString() on a unit that was created by dividing a
|
---|
30 | * gram unit by a kilogram unit. Yet, "kg" divided by "kg" returns
|
---|
31 | * {@link #ONE} and not "kg/kg" due to automatic unit factorization.</p>
|
---|
32 | *
|
---|
33 | * <p> This class supports the multiplication of offsets units. The result is
|
---|
34 | * usually a unit not convertible to its {@link #getStandardUnit standard unit}.
|
---|
35 | * Such units may appear in derivative quantities. For example °C/m is an
|
---|
36 | * unit of gradient, which is common in atmospheric and oceanographic
|
---|
37 | * research.</p>
|
---|
38 | *
|
---|
39 | * <p> Units raised at rational powers are also supported. For example
|
---|
40 | * the cubic root of "liter" is a unit compatible with meter.</p>
|
---|
41 | *
|
---|
42 | * <p> Instances of this class are immutable.</p>
|
---|
43 | *
|
---|
44 | * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
|
---|
45 | * @author <a href="mailto:steve@unidata.ucar.edu">Steve Emmerson</a>
|
---|
46 | * @author Martin Desruisseaux
|
---|
47 | * @version 3.2, August 28, 2006
|
---|
48 | * @see <a href="http://en.wikipedia.org/wiki/Units_of_measurement">
|
---|
49 | * Wikipedia: Units of measurement</a>
|
---|
50 | */
|
---|
51 | @SuppressWarnings("serial")
|
---|
52 | public abstract class Unit<Q extends Quantity> implements Serializable {
|
---|
53 |
|
---|
54 | /**
|
---|
55 | * Holds the dimensionless unit <code>ONE</code>.
|
---|
56 | */
|
---|
57 | public static final Unit<Dimensionless> ONE = new ProductUnit<Dimensionless>();
|
---|
58 |
|
---|
59 | /**
|
---|
60 | * Holds the unique symbols collection (base unit or alternate units).
|
---|
61 | */
|
---|
62 | static final HashMap<String, Unit<?>> SYMBOL_TO_UNIT = new HashMap<String, Unit<?>>();
|
---|
63 |
|
---|
64 | /**
|
---|
65 | * Default constructor.
|
---|
66 | */
|
---|
67 | protected Unit() {
|
---|
68 | }
|
---|
69 |
|
---|
70 | //////////////////////////////////////////////////////
|
---|
71 | // Contract methods (for sub-classes to implement). //
|
---|
72 | //////////////////////////////////////////////////////
|
---|
73 |
|
---|
74 | /**
|
---|
75 | * Returns the {@link BaseUnit base unit}, {@link AlternateUnit alternate
|
---|
76 | * unit} or product of base units and alternate units this unit is derived
|
---|
77 | * from. The standard unit identifies the "type" of
|
---|
78 | * {@link javax.measure.quantity.Quantity quantity} for which this unit is employed.
|
---|
79 | * For example:[code]
|
---|
80 | * boolean isAngularVelocity(Unit<?> u) {
|
---|
81 | * return u.getStandardUnit().equals(RADIAN.divide(SECOND));
|
---|
82 | * }
|
---|
83 | * assert(REVOLUTION.divide(MINUTE).isAngularVelocity());
|
---|
84 | * [/code]
|
---|
85 | *
|
---|
86 | * <p><i> Note: Having the same system unit is not sufficient to ensure
|
---|
87 | * that a converter exists between the two units
|
---|
88 | * (e.g. °C/m and K/m).</i></p>
|
---|
89 | * @return the system unit this unit is derived from.
|
---|
90 | */
|
---|
91 | public abstract Unit<? super Q> getStandardUnit();
|
---|
92 |
|
---|
93 |
|
---|
94 | /**
|
---|
95 | * Returns the converter from this unit to its system unit.
|
---|
96 | *
|
---|
97 | * @return <code>this.getConverterTo(this.getSystemUnit())</code>
|
---|
98 | */
|
---|
99 | public abstract UnitConverter toStandardUnit();
|
---|
100 |
|
---|
101 | /**
|
---|
102 | * Returns the hash code for this unit.
|
---|
103 | *
|
---|
104 | * @return this unit hashcode value.
|
---|
105 | */
|
---|
106 | public abstract int hashCode();
|
---|
107 |
|
---|
108 | /**
|
---|
109 | * Indicates if the specified unit can be considered equals to
|
---|
110 | * the one specified.
|
---|
111 | *
|
---|
112 | * @param that the object to compare to.
|
---|
113 | * @return <code>true</code> if this unit is considered equal to
|
---|
114 | * that unit; <code>false</code> otherwise.
|
---|
115 | */
|
---|
116 | public abstract boolean equals(Object that);
|
---|
117 |
|
---|
118 | /**
|
---|
119 | * Indicates if this unit is a standard unit (base units and
|
---|
120 | * alternate units are standard units). The standard unit identifies
|
---|
121 | * the "type" of {@link javax.measure.quantity.Quantity quantity} for
|
---|
122 | * which the unit is employed.
|
---|
123 | *
|
---|
124 | * @return <code>getStandardUnit().equals(this)</code>
|
---|
125 | */
|
---|
126 | public boolean isStandardUnit() {
|
---|
127 | return getStandardUnit().equals(this);
|
---|
128 | }
|
---|
129 |
|
---|
130 | /**
|
---|
131 | * Indicates if this unit is compatible with the unit specified.
|
---|
132 | * Units don't need to be equals to be compatible. For example:[code]
|
---|
133 | * RADIAN.equals(ONE) == false
|
---|
134 | * RADIAN.isCompatible(ONE) == true
|
---|
135 | * [/code]
|
---|
136 | * @param that the other unit.
|
---|
137 | * @return <code>this.getDimension().equals(that.getDimension())</code>
|
---|
138 | * @see #getDimension()
|
---|
139 | */
|
---|
140 | public final boolean isCompatible(Unit<?> that) {
|
---|
141 | return (this == that)
|
---|
142 | || this.getStandardUnit().equals(that.getStandardUnit())
|
---|
143 | || this.getDimension().equals(that.getDimension());
|
---|
144 | }
|
---|
145 |
|
---|
146 | /**
|
---|
147 | * Casts this unit to a parameterized unit of specified nature or
|
---|
148 | * throw a <code>ClassCastException</code> if the dimension of the
|
---|
149 | * specified quantity and this unit's dimension do not match.
|
---|
150 | * For example:[code]
|
---|
151 | * Unit<Length> LIGHT_YEAR = NonSI.C.times(NonSI.YEAR).asType(Length.class);
|
---|
152 | * [/code]
|
---|
153 | *
|
---|
154 | * @param type the quantity class identifying the nature of the unit.
|
---|
155 | * @return this unit parameterized with the specified type.
|
---|
156 | * @throws ClassCastException if the dimension of this unit is different
|
---|
157 | * from the specified quantity dimension.
|
---|
158 | * @throws UnsupportedOperationException if the specified quantity class
|
---|
159 | * does not have a public static field named "UNIT" holding the
|
---|
160 | * standard unit for the quantity.
|
---|
161 | */
|
---|
162 | @SuppressWarnings("unchecked")
|
---|
163 | public final <T extends Quantity> Unit<T> asType(Class<T> type) throws ClassCastException {
|
---|
164 | Dimension dim1 = this.getDimension();
|
---|
165 | Unit<T> u = null;
|
---|
166 | try {
|
---|
167 | u = (Unit<T>)type.getField("UNIT").get(null);
|
---|
168 | } catch (Exception e) {
|
---|
169 | throw new Error(e);
|
---|
170 | }
|
---|
171 | Dimension dim2 = u.getDimension();
|
---|
172 | if (!dim1.equals(dim2))
|
---|
173 | throw new ClassCastException();
|
---|
174 | return (Unit<T>)this;
|
---|
175 | }
|
---|
176 |
|
---|
177 | /**
|
---|
178 | * Returns the dimension of this unit (depends upon the current
|
---|
179 | * dimensional {@link Dimension.Model model}).
|
---|
180 | *
|
---|
181 | * @return the dimension of this unit for the current model.
|
---|
182 | */
|
---|
183 | public final Dimension getDimension() {
|
---|
184 | Unit<?> systemUnit = this.getStandardUnit();
|
---|
185 | if (systemUnit instanceof BaseUnit)
|
---|
186 | return Dimension.getModel().getDimension((BaseUnit<?>) systemUnit);
|
---|
187 | if (systemUnit instanceof AlternateUnit)
|
---|
188 | return ((AlternateUnit<?>) systemUnit).getParent().getDimension();
|
---|
189 | // Product of units.
|
---|
190 | ProductUnit<?> productUnit = (ProductUnit<?>) systemUnit;
|
---|
191 | Dimension dimension = Dimension.NONE;
|
---|
192 | for (int i = 0; i < productUnit.getUnitCount(); i++) {
|
---|
193 | Unit<?> unit = productUnit.getUnit(i);
|
---|
194 | Dimension d = unit.getDimension().pow(productUnit.getUnitPow(i))
|
---|
195 | .root(productUnit.getUnitRoot(i));
|
---|
196 | dimension = dimension.times(d);
|
---|
197 | }
|
---|
198 | return dimension;
|
---|
199 | }
|
---|
200 |
|
---|
201 | /**
|
---|
202 | * Returns a converter of numeric values from this unit to another unit.
|
---|
203 | *
|
---|
204 | * @param that the unit to which to convert the numeric values.
|
---|
205 | * @return the converter from this unit to <code>that</code> unit.
|
---|
206 | * @throws ConversionException if the conveter cannot be constructed
|
---|
207 | * (e.g. <code>!this.isCompatible(that)</code>).
|
---|
208 | */
|
---|
209 | public final UnitConverter getConverterTo(Unit<?> that)
|
---|
210 | throws ConversionException {
|
---|
211 | if (this.equals(that))
|
---|
212 | return UnitConverter.IDENTITY;
|
---|
213 | Unit<?> thisSystemUnit = this.getStandardUnit();
|
---|
214 | Unit<?> thatSystemUnit = that.getStandardUnit();
|
---|
215 | if (thisSystemUnit.equals(thatSystemUnit))
|
---|
216 | return that.toStandardUnit().inverse().concatenate(
|
---|
217 | this.toStandardUnit());
|
---|
218 | // Use dimensional transforms.
|
---|
219 | if (!thisSystemUnit.getDimension()
|
---|
220 | .equals(thatSystemUnit.getDimension()))
|
---|
221 | throw new ConversionException(this + " is not compatible with "
|
---|
222 | + that);
|
---|
223 | // Transform between SystemUnit and BaseUnits is Identity.
|
---|
224 | UnitConverter thisTransform = this.toStandardUnit().concatenate(
|
---|
225 | transformOf(this.getBaseUnits()));
|
---|
226 | UnitConverter thatTransform = that.toStandardUnit().concatenate(
|
---|
227 | transformOf(that.getBaseUnits()));
|
---|
228 | return thatTransform.inverse().concatenate(thisTransform);
|
---|
229 | }
|
---|
230 |
|
---|
231 | private Unit<?> getBaseUnits() {
|
---|
232 | Unit<?> systemUnit = this.getStandardUnit();
|
---|
233 | if (systemUnit instanceof BaseUnit) return systemUnit;
|
---|
234 | if (systemUnit instanceof AlternateUnit)
|
---|
235 | return ((AlternateUnit<?>)systemUnit).getParent().getBaseUnits();
|
---|
236 | if (systemUnit instanceof ProductUnit) {
|
---|
237 | ProductUnit<?> productUnit = (ProductUnit<?>)systemUnit;
|
---|
238 | Unit<?> baseUnits = ONE;
|
---|
239 | for (int i = 0; i < productUnit.getUnitCount(); i++) {
|
---|
240 | Unit<?> unit = productUnit.getUnit(i).getBaseUnits();
|
---|
241 | unit = unit.pow(productUnit.getUnitPow(i));
|
---|
242 | unit = unit.root(productUnit.getUnitRoot(i));
|
---|
243 | baseUnits = baseUnits.times(unit);
|
---|
244 | }
|
---|
245 | return baseUnits;
|
---|
246 | } else {
|
---|
247 | throw new InternalError(
|
---|
248 | "System Unit cannot be an instance of " + this.getClass());
|
---|
249 | }
|
---|
250 | }
|
---|
251 | private static UnitConverter transformOf(Unit<?> baseUnits) {
|
---|
252 | if (baseUnits instanceof BaseUnit)
|
---|
253 | return Dimension.getModel().getTransform((BaseUnit<?>) baseUnits);
|
---|
254 | // Product of units.
|
---|
255 | ProductUnit<?> productUnit = (ProductUnit<?>) baseUnits;
|
---|
256 | UnitConverter converter = UnitConverter.IDENTITY;
|
---|
257 | for (int i = 0; i < productUnit.getUnitCount(); i++) {
|
---|
258 | Unit<?> unit = productUnit.getUnit(i);
|
---|
259 | UnitConverter cvtr = transformOf(unit);
|
---|
260 | if (!cvtr.isLinear())
|
---|
261 | throw new ConversionException(baseUnits
|
---|
262 | + " is non-linear, cannot convert");
|
---|
263 | if (productUnit.getUnitRoot(i) != 1)
|
---|
264 | throw new ConversionException(productUnit
|
---|
265 | + " holds a base unit with fractional exponent");
|
---|
266 | int pow = productUnit.getUnitPow(i);
|
---|
267 | if (pow < 0) { // Negative power.
|
---|
268 | pow = -pow;
|
---|
269 | cvtr = cvtr.inverse();
|
---|
270 | }
|
---|
271 | for (int j = 0; j < pow; j++) {
|
---|
272 | converter = converter.concatenate(cvtr);
|
---|
273 | }
|
---|
274 | }
|
---|
275 | return converter;
|
---|
276 | }
|
---|
277 |
|
---|
278 | /**
|
---|
279 | * Returns the unit derived from this unit using the specified converter.
|
---|
280 | * The converter does not need to be linear. For example:[code]
|
---|
281 | * Unit<Dimensionless> DECIBEL = Unit.ONE.transform(
|
---|
282 | * new LogConverter(10).inverse().concatenate(
|
---|
283 | * new RationalConverter(1, 10)));[/code]
|
---|
284 | *
|
---|
285 | * @param operation the converter from the transformed unit to this unit.
|
---|
286 | * @return the unit after the specified transformation.
|
---|
287 | */
|
---|
288 | public final Unit<Q> transform(UnitConverter operation) {
|
---|
289 | if (this instanceof TransformedUnit) {
|
---|
290 | TransformedUnit<Q> tf = (TransformedUnit<Q>) this;
|
---|
291 | Unit<Q> parent = tf.getParentUnit();
|
---|
292 | UnitConverter toParent = tf.toParentUnit().concatenate(operation);
|
---|
293 | if (toParent == UnitConverter.IDENTITY)
|
---|
294 | return parent;
|
---|
295 | return new TransformedUnit<Q>(parent, toParent);
|
---|
296 | }
|
---|
297 | if (operation == UnitConverter.IDENTITY)
|
---|
298 | return this;
|
---|
299 | return new TransformedUnit<Q>(this, operation);
|
---|
300 | }
|
---|
301 |
|
---|
302 | /**
|
---|
303 | * Returns the result of multiplying this unit by an exact factor.
|
---|
304 | *
|
---|
305 | * @param factor the exact scale factor
|
---|
306 | * (e.g. <code>KILOMETER = METER.times(1000)</code>).
|
---|
307 | * @return <code>this.transform(new RationalConverter(factor, 1))</code>
|
---|
308 | */
|
---|
309 | public final Unit<Q> times(long factor) {
|
---|
310 | return transform(new RationalConverter(factor, 1));
|
---|
311 | }
|
---|
312 |
|
---|
313 | /**
|
---|
314 | * Returns the result of multiplying this unit by a an approximate factor
|
---|
315 | *
|
---|
316 | * @param factor the approximate factor (e.g.
|
---|
317 | * <code>ELECTRON_MASS = KILOGRAM.times(9.10938188e-31)</code>).
|
---|
318 | * @return <code>this.transform(new MultiplyConverter(factor))</code>
|
---|
319 | */
|
---|
320 | public final Unit<Q> times(double factor) {
|
---|
321 | return transform(new MultiplyConverter(factor));
|
---|
322 | }
|
---|
323 |
|
---|
324 | /**
|
---|
325 | * Returns the product of this unit with the one specified.
|
---|
326 | *
|
---|
327 | * @param that the unit multiplicand.
|
---|
328 | * @return <code>this * that</code>
|
---|
329 | */
|
---|
330 | public final Unit<? extends Quantity> times(Unit<?> that) {
|
---|
331 | return ProductUnit.getProductInstance(this, that);
|
---|
332 | }
|
---|
333 |
|
---|
334 | /**
|
---|
335 | * Returns the inverse of this unit.
|
---|
336 | *
|
---|
337 | * @return <code>1 / this</code>
|
---|
338 | */
|
---|
339 | public final Unit<? extends Quantity> inverse() {
|
---|
340 | return ProductUnit.getQuotientInstance(ONE, this);
|
---|
341 | }
|
---|
342 |
|
---|
343 | /**
|
---|
344 | * Returns the result of dividing this unit by an exact divisor.
|
---|
345 | *
|
---|
346 | * @param divisor the exact divisor.
|
---|
347 | * (e.g. <code>QUART = GALLON_LIQUID_US.divide(4)</code>).
|
---|
348 | * @return <code>this.transform(new RationalConverter(1 , divisor))</code>
|
---|
349 | */
|
---|
350 | public final Unit<Q> divide(long divisor) {
|
---|
351 | return transform(new RationalConverter(1, divisor));
|
---|
352 | }
|
---|
353 |
|
---|
354 | /**
|
---|
355 | * Returns the quotient of this unit with the one specified.
|
---|
356 | *
|
---|
357 | * @param that the unit divisor.
|
---|
358 | * @return <code>this / that</code>
|
---|
359 | */
|
---|
360 | public final Unit<? extends Quantity> divide(Unit<?> that) {
|
---|
361 | return this.times(that.inverse());
|
---|
362 | }
|
---|
363 |
|
---|
364 | /**
|
---|
365 | * Returns a unit equals to the given root of this unit.
|
---|
366 | *
|
---|
367 | * @param n the root's order.
|
---|
368 | * @return the result of taking the given root of this unit.
|
---|
369 | * @throws ArithmeticException if <code>n == 0</code>.
|
---|
370 | */
|
---|
371 | public final Unit<? extends Quantity> root(int n) {
|
---|
372 | if (n > 0) {
|
---|
373 | return ProductUnit.getRootInstance(this, n);
|
---|
374 | } else if (n == 0) {
|
---|
375 | throw new ArithmeticException("Root's order of zero");
|
---|
376 | } else { // n < 0
|
---|
377 | return ONE.divide(this.root(-n));
|
---|
378 | }
|
---|
379 | }
|
---|
380 |
|
---|
381 | /**
|
---|
382 | * Returns a unit equals to this unit raised to an exponent.
|
---|
383 | *
|
---|
384 | * @param n the exponent.
|
---|
385 | * @return the result of raising this unit to the exponent.
|
---|
386 | */
|
---|
387 | public final Unit<? extends Quantity> pow(int n) {
|
---|
388 | if (n > 0) {
|
---|
389 | return this.times(this.pow(n - 1));
|
---|
390 | } else if (n == 0) {
|
---|
391 | return ONE;
|
---|
392 | } else { // n < 0
|
---|
393 | return ONE.divide(this.pow(-n));
|
---|
394 | }
|
---|
395 | }
|
---|
396 | }
|
---|