// // BaseUnit.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad; import java.io.Serializable; import java.util.Arrays; import java.util.Vector; /** * A class that represents the base units of a system of units. * * Instances are immutable. * * @author Steven R. Emmerson * * This is part of Steve Emerson's Unit package that has been * incorporated into VisAD. */ public final class BaseUnit extends Unit implements Serializable { private static final long serialVersionUID = 1L; /** * Name of the unit (e.g. "meter"). */ private final String unitName; /** * Quantity of the unit (e.g. "Length"). */ private final String quantityName; /** * Derived unit associated with base unit (for computational efficiency). */ final DerivedUnit derivedUnit; final boolean isDimless; /** * Global database of base units (to prevent multiple base units for the * same quantity). */ private static final Vector<BaseUnit> baseUnits = new Vector<BaseUnit>( 9); /** * Constructs a base unit from the names for the quantity and unit, a the * unit abbreviation, and whether or not the unit is dimensionless. * * @param unitName * Name of the unit (e.g. "meter"). * @param abbreviation * The abbreviation for the unit (e.g. "m"). * @param quantityName * Name of the quantity (e.g. "Length"). * @param isDimless * Whether or not the unit is dimensionless. * @throws UnitException * Name, abbreviation, or quantity name is <code> * null</code> * . */ private BaseUnit(final String unitName, final String abbreviation, final String quantityName, final boolean isDimless) throws UnitException { super(abbreviation); if (unitName == null || abbreviation == null || quantityName == null) { throw new UnitException( "Base unit name, abbreviation, or quantity name is null"); } this.unitName = unitName; this.quantityName = quantityName; baseUnits.addElement(this); derivedUnit = new DerivedUnit(this); this.isDimless = isDimless; } /** * <p> * Indicates if this instance is dimensionless. A unit is dimensionless if * it is a measure of a dimensionless quantity like angle or concentration. * Examples of dimensionless base units include radian, degree, and * steradian. * </p> * * @return True if an only if this unit is dimensionless. */ @Override public boolean isDimensionless() { return isDimless; } @Override public Unit scale(final double amount) throws UnitException { return ScaledUnit.getInstance(amount, this); } @Override public Unit shift(final double offset) throws UnitException { return OffsetUnit.getInstance(offset, this); } @Override public Unit log(final double base) { return derivedUnit.log(base); } /** * Raise a base unit to a power. * * @param power * The power to raise this unit by. * @return The unit resulting from raising this unit to <code>power</code>. * @promise This unit has not been modified. */ @Override public Unit pow(final int power) { return derivedUnit.pow(power); } /** * Raise a unit to a power. * * @param power * The power to raise this unit by. The value must be integral or * reciprocal integral. * @return The unit resulting from raising this unit to <code>power</code>. * @throws IllegalArgumentException * <code>power</code> has a non-integral or non-reciprocal * integral value. * @promise The unit has not been modified. */ @Override public Unit pow(final double power) throws IllegalArgumentException { return derivedUnit.pow(power); } /** * Returns the N-th root of this unit. * * @param root * The root to take (e.g. 2 means square root). May not be zero. * @return The unit corresponding to the <code>root</code>-th root of this * unit. * @promise This unit has not been modified. * @throws IllegalArgumentException * The root value is zero or the resulting unit would have a * non-integral unit dimension. */ @Override public Unit root(final int root) throws IllegalArgumentException { return derivedUnit.root(root); } /** * Return the name of this unit. * * @return The name of this unit (e.g. "meter"). */ public String unitName() { return unitName; } /** * Return the symbol of this unit. This is the same as the identifier. * * @return The symbol of this unit (e.g. "m"). */ public String unitSymbol() { return getIdentifier(); } /** * Return the name of the quantity associated with this unit. * * @return The name this units quantity (e.g. "Length"). */ public String quantityName() { return quantityName; } /** * Create a new base unit from the name of a quantity and the name of a * unit. The unit abbreviation will be the same as the unit name. The unit * will not be dimensionless. * * @param quantityName * The name of the associated quantity (e.g. "Length"). * @param unitName * The name for the unit (e.g. "meter"). * @return A new base unit or the previously created one with the same * names. * @require The arguments are non-null. The quantity name has not been used * before or the unit name is the same as before. * @promise The new quantity and unit has been added to the database. * @throws UnitException * Name, abbreviation, or quantity name is <code> * null</code> * or attempt to redefine the base unit associated with * <code>quantityName</code>. */ public static BaseUnit addBaseUnit(final String quantityName, final String unitName) throws UnitException { return addBaseUnit(quantityName, unitName, unitName); } /** * Create a new base unit from from the name of a quantity, the name of a * unit, and the unit's abbreviation. The unit will not be dimensionless. * * @param quantityName * The name of the associated quantity (e.g. "Length"). * @param unitName * The name for the unit (e.g. "meter"). * @param abbreviation * The abbreviation for the unit (e.g. "m"). * @return A new base unit or the previously created one with the same * names. * @require The arguments are non-null. The quantity name has not been used * before or the unit name is the same as before. * @promise The new quantity and unit has been added to the database. * @throws UnitException * Name, abbreviation, or quantity name is <code> * null</code> * or attempt to redefine the base unit associated with * <code>quantityName</code>. */ public static synchronized BaseUnit addBaseUnit(final String quantityName, final String unitName, final String abbreviation) throws UnitException { return addBaseUnit(quantityName, unitName, abbreviation, false); } /** * Create a new base unit from from the name of a quantity, the name of a * unit, the unit's abbreviation, and whether or not the unit is * dimensionless. * * @param quantityName * The name of the associated quantity (e.g. "Length"). * @param unitName * The name for the unit (e.g. "meter"). * @param abbreviation * The abbreviation for the unit (e.g. "m"). * @param isDimless * Whether or not the unit is dimensionless. * @return A new base unit or the previously created one with the same * names. * @require The arguments are non-null. The quantity name has not been used * before or the unit name is the same as before. * @promise The new quantity and unit has been added to the database. * @throws UnitException * Name, abbreviation, or quantity name is <code> * null</code> * or attempt to redefine the base unit associated with * <code>quantityName</code>. */ public static synchronized BaseUnit addBaseUnit(final String quantityName, final String unitName, final String abbreviation, final boolean isDimless) throws UnitException { final BaseUnit baseUnit = quantityNameToUnit(quantityName); if (baseUnit == null) { return new BaseUnit(unitName, abbreviation, quantityName, isDimless); } if (baseUnit.unitName.equals(unitName) && baseUnit.getIdentifier().equals(abbreviation) && baseUnit.isDimless == isDimless) { return baseUnit; } throw new UnitException("Attempt to redefine quantity \"" + quantityName + "\" base unit from \"" + baseUnit.unitName + "(" + baseUnit.getIdentifier() + ")" + "\" to \"" + unitName + "(" + abbreviation + ")" + "\""); } /** * Find the base unit with the given name. * * @param unitName * The name of the unit (e.g. "meter"). * @return The existing base unit with the given name or <code>null</code> * if no such units exists. * @require The argument is non-null. */ public static synchronized BaseUnit unitNameToUnit(final String unitName) { for (int i = 0; i < baseUnits.size(); ++i) { final BaseUnit baseUnit = baseUnits.elementAt(i); if (baseUnit.unitName.equals(unitName)) { return baseUnit; } } return null; } /** * Find the base unit for the given quantity. * * @param quantityName * The name of the quantity (e.g. "Length"). * @return The existing base unit for the given quantity or * <code>null</code> if no such unit exists. * @require The argument is non-null. */ public static synchronized BaseUnit quantityNameToUnit( final String quantityName) { for (int i = 0; i < baseUnits.size(); ++i) { final BaseUnit baseUnit = baseUnits.elementAt(i); if (baseUnit.quantityName.equals(quantityName)) { return baseUnit; } } return null; } private static void myAssert(final boolean assertion) { if (!assertion) { throw new AssertionError(); } } private static void myAssert(final String have, final String expect) { if (!have.equals(expect)) { throw new AssertionError(have + " != " + expect); } } private static void myAssert(final Unit have, final Unit expect) { if (!have.equals(expect)) { throw new AssertionError(have.toString() + " != " + expect); } } private static void myAssert(final double have, final double expect) { if (have != expect) { throw new AssertionError("" + have + " != " + expect); } } private static void myAssert(final double[] have, final double[] expect) { if (!Arrays.equals(have, expect)) { throw new AssertionError("" + have + " != " + expect); } } /** * Test this class. * * @param args * Arguments (ignored). * @throws UnitException * A problem occurred. */ public static void main(final String[] args) throws UnitException { final BaseUnit meter = BaseUnit.addBaseUnit("Length", "meter", "m"); myAssert(meter, meter); myAssert(meter.isConvertible(meter)); myAssert(meter.toString(), "m"); myAssert(meter.pow(2), meter.multiply(meter)); myAssert(meter.pow(2).sqrt(), meter); final BaseUnit second = BaseUnit.addBaseUnit("Time", "second", "s"); myAssert(!meter.equals(second)); myAssert(!meter.isConvertible(second)); Unit unit = meter.multiply(second); myAssert(unit, second.multiply(meter)); unit = meter.divide(second); myAssert(unit, second.divide(meter).pow(-1)); myAssert(!unit.equals(meter)); myAssert(!unit.equals(second)); myAssert(meter.toThis(5, meter), 5); myAssert(meter.toThat(5, meter), 5); final double[] values = { 1, 2 }; myAssert(meter.toThis(values, meter), values); myAssert(meter.toThat(values, meter), values); System.out.println("Checking exceptions:"); try { meter.toThis(5, second); throw new AssertionError(); } catch (final UnitException e) { System.out.println(e.getMessage()); } try { meter.toThat(5, second); throw new AssertionError(); } catch (final UnitException e) { System.out.println(e.getMessage()); } try { BaseUnit.addBaseUnit("Length", "foot", "ft"); throw new AssertionError(); } catch (final UnitException e) { System.out.println(e.getMessage()); } System.out.println("Done"); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @return The converted values in units of this unit. * @require The units are convertible. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ double[] toThis(final double[] values, final BaseUnit that) throws UnitException { return toThis(values, that, true); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @return The converted values in units of this unit. * @require The units are convertible. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ float[] toThis(final float[] values, final BaseUnit that) throws UnitException { return toThis(values, that, true); } /** * Convert values to this unit from a base unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values in units of this unit. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ double[] toThis(final double[] values, final BaseUnit that, final boolean copy) throws UnitException { if (equals(that)) { final double[] newValues = (copy) ? (double[]) values.clone() : values; return newValues; } throw new UnitException("Attempt to convert from unit \"" + that + "\" to unit \"" + this + "\""); } /** * Convert values to this unit from a base unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values in units of this unit. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ float[] toThis(final float[] values, final BaseUnit that, final boolean copy) throws UnitException { if (equals(that)) { final float[] newValues = (copy) ? (float[]) values.clone() : values; return newValues; } throw new UnitException("Attempt to convert from unit \"" + that + "\" to unit \"" + this + "\""); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @return The converted values in units of this unit. * @require The units are convertible. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThis(final double[] values, final Unit that) throws UnitException { return toThis(values, that, true); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @return The converted values in units of this unit. * @require The units are convertible. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThis(final float[] values, final Unit that) throws UnitException { return toThis(values, that, true); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values in units of this unit. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThis(final double[] values, final Unit that, final boolean copy) throws UnitException { return that.toThat(values, derivedUnit, copy); } /** * Convert values to this unit from another unit. * * @param values * The values to be converted. * @param that * The unit of <code>values</code>. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values in units of this unit. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThis(final float[] values, final Unit that, final boolean copy) throws UnitException { return that.toThat(values, derivedUnit, copy); } /** * Convert values from this unit to a base unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ double[] toThat(final double[] values, final BaseUnit that) throws UnitException { return toThat(values, that, true); } /** * Convert values from this unit to a base unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ float[] toThat(final float[] values, final BaseUnit that) throws UnitException { return toThat(values, that, true); } /** * Convert values from this unit to a base unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ double[] toThat(final double[] values, final BaseUnit that, final boolean copy) throws UnitException { if (equals(that)) { final double[] newValues = (copy) ? (double[]) values.clone() : values; return newValues; } throw new UnitException("Attempt to convert from unit \"" + this + "\" to unit \"" + that + "\""); } /** * Convert values from this unit to a base unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ float[] toThat(final float[] values, final BaseUnit that, final boolean copy) throws UnitException { if (equals(that)) { final float[] newValues = (copy) ? (float[]) values.clone() : values; return newValues; } throw new UnitException("Attempt to convert from unit \"" + this + "\" to unit \"" + that + "\""); } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThat(final double[] values, final Unit that) throws UnitException { return toThat(values, that, true); } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThat(final float[] values, final Unit that) throws UnitException { return toThat(values, that, true); } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public double[] toThat(final double[] values, final Unit that, final boolean copy) throws UnitException { return that.toThis(values, derivedUnit, copy); } /** * Convert values from this unit to another unit. * * @param values * The values to be converted in units of this unit. * @param that * The unit to which to convert the values. * @param copy * if false and <code>that</code> equals this, return * <code>values</code>, else return a new array * @return The converted values. * @require The units are identical. * @promise Neither unit has been modified. * @throws UnitException * The units are not convertible. */ @Override public float[] toThat(final float[] values, final Unit that, final boolean copy) throws UnitException { return that.toThis(values, derivedUnit, copy); } /** * Returns the definition of this unit. The definition of a BaseUnit is the * same as the BaseUnit's identifier. * * @return The definition of this unit. Won't be <code>null * </code> * but may be empty. */ @Override public String getDefinition() { return getIdentifier(); } /** * Clones this unit, changing the identifier. This method always throws an * exception because base units may not be cloned. * * @param identifier * The name or abbreviation for the cloned unit. May be * <code>null</code> or empty. * @return A unit equal this this instance but with the given identifier. * @throws UnitException * Base units may not be cloned. Always thrown. */ @Override protected Unit protectedClone(final String identifier) throws UnitException { throw new UnitException("Base units may not be cloned"); } /** * Indicates whether or not this instance equals a unit. * * @param unit * A unit. * @return <code>true</code> if and only if this instance is equal to the * unit. */ @Override public boolean equals(final Unit unit) { if (this == unit) { return true; } if (!(unit instanceof BaseUnit)) { return derivedUnit.equals(unit); } final BaseUnit that = (BaseUnit) unit; return unitName.equals(that.unitName) && quantityName.equals(that.quantityName) && isDimless == that.isDimless; } /** * Returns the hash code of this instance. {@link Object#hashCode()} should * be overridden whenever {@link Object#equals(Object)} is. * * @return The hash code of this instance (includes the values). */ @Override public int hashCode() { if (hashCode == 0) { hashCode = unitName.hashCode() ^ quantityName.hashCode() ^ Boolean.valueOf(isDimless).hashCode(); } return hashCode; } /** * Multiply this unit by another unit. * * @param that * The unit with which to multiply this unit. * @return The product of the two units. * @promise Neither unit has been modified. * @throws UnitException * Meaningless operation. */ @Override public Unit multiply(final Unit that) throws UnitException { return derivedUnit.multiply(that); } /** * Divide this unit by another unit. * * @param that * The unit to divide into this unit. * @return The quotient of the two units. * @promise Neither unit has been modified. * @throws UnitException * Meaningless operation. */ @Override public Unit divide(final Unit that) throws UnitException { return derivedUnit.divide(that); } /** * Divide this unit into another unit. * * @param that * The unit to divided this unit. * @return The quotient of the two units. * @promise Neither unit has been modified. * @throws UnitException * Meaningless operation. */ @Override protected Unit divideInto(final Unit that) throws UnitException { return derivedUnit.divideInto(that); } /** * Indicate whether this unit is convertible with another unit. If one unit * is convertible with another, then the <code>toThis(...)</code>/ and * <code>toThat(...)</code> methods will not throw a UnitException. Unit A * is convertible with unit B if and only if unit B is convertible with unit * A; hence, calling-order is irrelevant. * * @param unit * The other unit. * @return True if and only if this unit is convertible with the other unit. */ @Override public boolean isConvertible(final Unit unit) { return unit == null ? false : derivedUnit.isConvertible(unit); } @Override public DerivedUnit getDerivedUnit() { return derivedUnit; } }