/* * Copyright (c) 2004, JSR-108 group (http://www.jcp.org/en/jsr/detail?id=108) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - Neither the name of the JSR-108 nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package javax.units; // J2SE direct dependencies import java.io.Serializable; import java.text.ParseException; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.HashMap; /** *
This class represents a unit of physical quantity.
* *It is helpful to think of instances of this class as recording the * history by which they are created. Thus, for example, the string * "g/kg" (which is a dimensionless unit) would result from invoking * the method toString() on a unit that was created by dividing a * gram unit by a kilogram unit. Yet, "kg" divided by "kg" returns * {@link #ONE} and not "kg/kg" due to automatic unit factorization.
* *This class supports the multiplication of offsets units. The result is * usually a unit not convertible to its system unit. Such units may * appear in derivative quantities. For example °C/m is a unit of * gradient, which is common in atmospheric and oceanographic research.
* *Units raised at rational powers are also supported. For example * the cubic root of "liter" is a unit compatible with meter.
* *Instances of this class (and sub-classes) are immutable and unique.
* * @author Steve Emmerson * @author Jean-Marie Dautelle * @author Martin Desruisseaux */ public abstract class Unit implements Serializable { //////////////////////////////////////////////////////////////////////////// // LOOK-UP ACCELERATION (must be initialized first) /** * Holds an empty array of units (immutable). */ private static final Unit[] NONE = new Unit[0]; /** * Holds the unit's unique identifier (for look-up tables and unit's * internal order). */ transient int _id; // Package private. /** * Holds multiply look-up table. */ private transient Unit[] _multiplyLookUp = NONE; /** * Holds divide look-up table. */ private transient Unit[] _divideLookUp = NONE; /** * Holds root look-up table. */ private transient Unit[] _rootLookUp = NONE; /** * Holds pow look-up table. */ private transient Unit[] _powLookUp = NONE; // //////////////////////////////////////////////////////////////////////////// /** * Holds the unit collection. */ private static final Map UNITS = new HashMap(); /** * Holds the symbol collection (symbol-unit mapping). */ private static final Map SYMBOLS = new HashMap(); /** * Holds the key to unit's dimensions. */ static final Map DIMENSIONS = Collections.synchronizedMap(new HashMap()); // Package private. /** * Holds the key to dimension converters (context-sensitive). */ static final Map TO_DIMENSIONS = Collections.synchronizedMap(new HashMap()); // Package private. /** * Holds the dimensionless unitONE
.
*/
public static final Unit ONE = new ProductUnit();
static {
UNITS.put(ONE, ONE);
}
/**
* Holds the unit's symbol or null
if none
* (e.g. {@link ProductUnit}, {@link TransformedUnit}).
*/
final String _symbol; // Package private.
/**
* Holds the hashcode for this unit (calculated once, when the unit
* is added to the collection).
*/
private transient int _hashCode;
/**
* Base constructor.
*
* @param symbol the unit's symbol or null
if none.
*/
Unit(String symbol) { // Package private.
_symbol = symbol;
}
/**
* Indicates if this unit is compatible with the unit specified.
* Units don't need to be equals to be compatible. For example:
* ** @param that the other unit. * @returnRADIAN.equals(ONE) == false
*RADIAN.isCompatible(ONE) == true
. *
true
if both units have the same dimension;
* false
otherwise.
* @see #getDimension
*/
public final boolean isCompatible(Unit that) {
return (this == that) ||
(this.getDimension() == that.getDimension());
}
/**
* Returns the units identifying the dimension of this unit.
* The dimension returned is context-sensitve and based upon the
* current base units' dimensions.
*
* @return the dimensional unit for this unit.
* @see BaseUnit#setDimension
*/
public final Unit getDimension() {
Unit dimension = (Unit) DIMENSIONS.get(this);
if (dimension == null) { // Caches current dimension.
dimension = getCtxDimension();
DIMENSIONS.put(this, dimension);
}
return dimension;
}
/**
* Returns a converter of numeric values from this unit to another unit.
*
* @param that the unit to which to convert the numeric values.
* @return the converter from this unit to that
unit.
* @throws ConversionException if the conveter cannot be constructed
* (e.g. !this.isCompatible(that)
).
*/
public final Converter getConverterTo(Unit that)
throws ConversionException {
if (this == that) {
return Converter.IDENTITY;
} else if (this.isCompatible(that)) { // Same dimensions.
Converter thisToDimension = (Converter) TO_DIMENSIONS.get(this);
if (thisToDimension == null) { // Caches current converter.
thisToDimension = this.getCtxToDimension();
TO_DIMENSIONS.put(this, thisToDimension);
}
Converter thatToDimension = (Converter) TO_DIMENSIONS.get(that);
if (thatToDimension == null) { // Caches current converter.
thatToDimension = that.getCtxToDimension();
TO_DIMENSIONS.put(that, thatToDimension);
}
return thatToDimension.inverse().concatenate(thisToDimension);
} else {
throw new ConversionException(
this + " is not compatible with " + that +
" in current context");
}
}
/**
* Indicates if this unit is a system unit. A system unit
* is either a base unit, an alternate unit or a product of base units
* and alternate units.
*
* @return true
if this unit is a system unit;
* false
otherwise.
* @see #getSystemUnit
*/
public final boolean isSystemUnit() {
return this.getSystemUnit() == this;
}
/**
* Returns the system unit for this unit. The system unit identifies the
* nature of the quantity being measured using this unit.
*
* Note: Having the same system units is not sufficient to ensure * that a converter exists between the two units * (e.g. °C/m and K/m).
* @return the system unit for this unit. * @see #isSystemUnit */ public abstract Unit getSystemUnit(); /** * Returns a unit compatible to this unit except it uses the specified * symbol. The alternate unit can itself be used to form expressions and * symbols for other derived units. For example: *
* RADIAN = ONE.alternate("rad");
* NEWTON = METER.multiply(KILOGRAM).divide(SECOND.pow(2)).alternate("N");
* PASCAL = NEWTON.divide(METER.pow(2)).alternate("Pa");
*
*
* @param symbol the unique symbol for the alternate unit.
* @return a unit compatible with this unit but of different nature.
* @throws IllegalArgumentException if the specified symbol is currently
* associated to a different unit.
* @throws UnsupportedOperationException if the specified unit is not
* a system unit.
* @see #isSystemUnit
*/
public final Unit alternate(String symbol) {
return AlternateUnit.getInstance(symbol, this);
}
/**
* Returns the result of adding an offset to this unit. The returned unit
* is convertible with all units that are convertible with this unit.
*
* @param offset the offset added (expressed in this unit,
* e.g. CELSIUS = KELVIN.add(273.15)
).
* @return this + offset
.
*/
public final Unit add(double offset) {
return TransformedUnit.getInstance(this, new AddConverter(offset));
}
/**
* Returns the result of multiplying this unit by a scale factor. The
* returned unit is convertible with all units that are convertible with
* this unit.
*
* @param scale the scale factor
* (e.g. KILOMETER = METER.multiply(1000)
).
* @return this * scale
*/
public final Unit multiply(double scale) {
return TransformedUnit.getInstance(this, new MultiplyConverter(scale));
}
/**
* Returns the product of this unit with the one specified.
*
* @param that the unit multiplicand.
* @return this * that
*/
public final Unit multiply(Unit that) {
if (that._id >= _multiplyLookUp.length) {
// Resizes.
Unit[] tmp = new Unit[that._id + 1];
System.arraycopy(
_multiplyLookUp, 0, tmp, 0, _multiplyLookUp.length);
_multiplyLookUp = tmp;
}
Unit result = _multiplyLookUp[that._id];
if (result != null) {
return result; // Hit.
} else {
result = ProductUnit.getProductInstance(this, that);
_multiplyLookUp[that._id] = result;
return result;
}
}
/**
* Returns the quotient of this unit with the one specified.
*
* @param that the unit divisor.
* @return this / that
*/
public final Unit divide(Unit that) {
if (that._id >= _divideLookUp.length) {
// Resizes.
Unit[] tmp = new Unit[that._id + 1];
System.arraycopy(_divideLookUp, 0, tmp, 0, _divideLookUp.length);
_divideLookUp = tmp;
}
Unit result = _divideLookUp[that._id];
if (result != null) {
return result; // Hit.
} else {
result = ProductUnit.getQuotientInstance(this, that);
_divideLookUp[that._id] = result;
return result;
}
}
/**
* Returns a unit equals to the given root of this unit.
*
* @param n the root's order.
* @return the result of taking the given root of this unit.
* @throws ArithmeticException if n == 0
.
*/
public final Unit root(int n) {
if (n > 1) {
if (_rootLookUp.length < n-1) {
_rootLookUp = new Unit[n-1]; // Resizes.
}
if (_rootLookUp[n-2] == null) {
_rootLookUp[n-2] = ProductUnit.getRootInstance(this, n);
}
return _rootLookUp[n-2];
} else if (n == 1) {
return this;
} else if (n == 0) {
throw new ArithmeticException("Root's order of zero");
} else { // n < 0
return ONE.divide(this.root(-n));
}
}
/**
* Returns a unit equals to this unit raised to an exponent.
*
* @param n the exponent.
* @return the result of raising this unit to the exponent.
*/
public final Unit pow(int n) {
if (n > 1) {
if (_powLookUp.length < n-1) {
_powLookUp = new Unit[n-1]; // Resizes.
}
if (_powLookUp[n-2] == null) {
_powLookUp[n-2] = ProductUnit.getPowInstance(this, n);
}
return _powLookUp[n-2];
} else if (n == 1) {
return this;
} else if (n == 0) {
return ONE;
} else { // n < 0
return ONE.divide(this.pow(-n));
}
}
/**
* Returns a unit instance that is defined from the specified
* character sequence. If the specified character sequence is a
* combination of units (e.g. {@link ProductUnit}), then the corresponding
* unit is created if not already defined.
* Examples of valid entries (all for meters per second squared) are:
*
*
Note: Unlike {@link #valueOf(CharSequence)}, this method does not * parse the given string (it does not raise an exception either * if the specified symbol is not yet defined).
* * @param symbol the symbol searched for. * @return the unit with the specified symbol ornull
* if such unit cannot be found.
*/
public static Unit searchSymbol(CharSequence symbol) { // Package private.
synchronized (Unit.class) {
return (Unit) SYMBOLS.get(symbol);
}
}
/**
* Returns the current dimension for this unit (context sensitive).
*
* @return the unit's dimension.
*/
abstract Unit getCtxDimension(); // Package private.
/**
* Returns the converter to this unit's current dimension
* (context sensitive).
*
* @return the converter to this unit's dimension.
*/
abstract Converter getCtxToDimension(); // Package private.
//////////////////////
// GENERAL CONTRACT //
//////////////////////
/**
* Returns the standard String
representation of this unit.
*
* @return appendTo(new StringBuffer()).toString()
*/
public final String toString() {
return appendTo(new StringBuffer()).toString();
}
/**
* Appends the text representation of this {@link Unit} to the
* StringBuffer
argument. For better formatting control,
* {@link UnitFormat#format} is recommended.
*
* @param sb the StrinBuffer
to append.
* @return UnitFormat.getStandardInstance().format(this, sb, null)
*
* @see UnitFormat#getStandardInstance
*/
public final StringBuffer appendTo(StringBuffer sb) {
return UnitFormat.getStandardInstance().format(this, sb, null);
}
/**
* Indicates if this unit is equal to the object specified.
* Units are unique and immutable, therefore users might want to use
* ==
to test for equality.
*
* @param that the object to compare for equality.
* @return true
if this unit and the specified object are
* considered equal; false
otherwise.
*/
public abstract boolean equals(Object that);
/**
* Returns a hash code value for this unit.
* Equals object have equal hash codes.
*
* @return this unit hash code value.
* @see #equals
*/
public final int hashCode() {
return _hashCode;
}
/**
* This method returns an {@link Unit} from the collection equals to the
* specified template. If the unit is not found, the specified template is
* added to the collection and being returned. This method is typically used
* by sub-classes to ensure unicity of {@link Unit} instances.
*
* @param template the unit template for comparaison.
* @return a unit from the collection equals to the specified template;
* or the template itself.
* @throws UnsupportedOperationException if the template cannot be added to
* the collection (e.g. symbol already taken by a different unit).
* @see #equals
*/
protected static Unit getInstance(Unit template) {
synchronized (Unit.class) {
// Checks if already exist.
template._hashCode = template.calculateHashCode();
Object obj = UNITS.get(template);
if (obj != null) {
return (Unit) obj;
}
// Registers symbol if any.
if (template._symbol != null) {
obj = SYMBOLS.get(template._symbol);
if (obj != null) {
throw new UnsupportedOperationException("The symbol: " +
template._symbol +
" is currently associated to an instance of " +
obj.getClass());
}
SYMBOLS.put(template._symbol, template);
}
UNITS.put(template, template);
template._id = UNITS.size();
return template;
}
}
/**
* Calculates the hash code of this unit (optimization).
*
* @return this unit's hashcode.
*/
abstract int calculateHashCode(); // Package private.
/**
* Overrides readResolve()
to ensure that deserialization
* maintains unit's unicity.
*
* @return a new unit or an existing unit.
*/
protected Object readResolve() {
return getInstance(this);
}
}