// // OffsetUnit.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.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; /** * A class that represents a scaled unit with an offset. * * Instances are immutable. * * @author Steven R. Emmerson * * This is part of Steve Emerson's Unit package that has been * incorporated into VisAD. * * Instances are immutable. */ public final class OffsetUnit extends Unit implements Serializable { private static final long serialVersionUID = 1L; /** * The associated (unoffset) underlying unit. */ final Unit underUnit; /** * The offset for this unit (e.g. 273.15 for the celsius unit when the * kelvin unit is associated scaled unit). */ final double offset; /** * The date formatter. * * @serial */ private static SimpleDateFormat dateFormat; /** * The arbitrary time origin. * * @serial */ private static double offsetUnitOrigin; /** * The millisecond unit. */ private static Unit millisecond; static { try { dateFormat = (SimpleDateFormat) DateFormat.getDateInstance( DateFormat.SHORT, Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); dateFormat.applyPattern("yyyy-MM-dd HH:mm:ss.SSS 'UTC'"); final Calendar calendar = new GregorianCalendar(TimeZone .getTimeZone("UTC")); calendar.clear(); calendar.set(2001, 0, 1, 0, 0, 0); // data.netcdf.units.UnitParser offsetUnitOrigin = calendar.getTime().getTime(); millisecond = SI.second.scale(0.001).clone("ms"); } catch (final Exception e) { System.err .println("OffsetUnit.<clinit>: Couldn't initialize class: " + e); System.exit(1); } } /** * Construct an offset, dimensionless unit. The identifier will be empty. * * @param offset * The amount of offset. */ public OffsetUnit(final double offset) { this(offset, ""); } /** * Construct an offset, dimensionless unit with an identifier. * * @param offset * The amount of offset. * @param identifier * The name or abbreviation for the cloned unit. May be * <code>null</code> or empty. * */ public OffsetUnit(final double offset, final String identifier) { super(identifier); this.offset = offset; underUnit = new ScaledUnit(1.0); } /** * Construct an offset unit from another unit. The identifier will be that * of the base unit if the offset is zero; otherwise, the identifier will be * <code>null</code>. * * @param offset * The amount of offset. * @param unit * The other unit. */ public OffsetUnit(final double offset, final Unit unit) { this(offset, unit, null); } /** * Construct an offset unit from another unit and an identifier. * * @param offset * The amount of offset. * @param unit * The other unit. * @param identifier * The name or abbreviation for the cloned unit. May be * <code>null</code> or empty. */ public OffsetUnit(final double offset, final Unit unit, final String identifier) { super(identifier != null ? identifier : offset == 0 ? unit.getIdentifier() : null); this.offset = offset; underUnit = unit; } /** * Returns an instance based on an offset and an underlying unit. * * @param offset * The offset. * @param unit * The underlying unit. * @return An instance corresponding to the input. */ static Unit getInstance(final double offset, final Unit unit) { return offset == 0 ? unit : new OffsetUnit(offset, unit); } /** * <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 units include radian, degree, steradian, and * "g/kg". * </p> * * @return True if an only if this unit is dimensionless. */ @Override public boolean isDimensionless() { return underUnit.isDimensionless(); } /** * Indicates if this instance is a unit of time. * * @return <code>true</code> if and only if this instance is a unit of time. */ protected boolean isTime() { return SI.second.isConvertible(underUnit); } /** * Clones this unit, changing the identifier. * * @param identifier * The name or abbreviation for the cloned unit. May be * <code>null</code> or empty. * @return A unit equal to this unit but with the given identifier. */ @Override protected Unit protectedClone(final String identifier) { return new OffsetUnit(0, this, identifier); } @Override public Unit scale(final double amount) throws UnitException { return OffsetUnit.getInstance(offset / amount, underUnit.scale(amount)); } @Override public Unit shift(final double offset) throws UnitException { return OffsetUnit.getInstance(offset + this.offset, underUnit); } @Override public Unit log(final double base) throws UnitException { return LogarithmicUnit.getInstance(base, this); } /** * Raises an offset unit to a power. Raising an offset unit to a power is * equivalent to first stripping-off the offset amount and then raising the * resulting non-offset unit to the power. * * @param power * The power to raise this unit by. * @return A corresponding unit. * @throws UnitException * if the underlying unit can't be raised to the given power. */ @Override public Unit pow(final int power) throws UnitException { return underUnit.pow(power); } /** * Raises an offset unit to a power. Raising an offset unit to a power is * equivalent to first stripping-off the offset amount and then raising the * resulting non-offset unit to the power. * * @param power * The power to raise this unit by. * @return A corresponding unit. * @throws UnitException * if the underlying unit can't be raised to the given power. */ @Override public Unit pow(final double power) throws UnitException { return underUnit.pow(power); } /** * Returns the N-th root of this unit. Taking the root of an offset unit is * equivalent to first stripping-off the offset amount and then taking the * root of the resulting non-offset 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. * @throws IllegalArgumentException * The root value is zero or the resulting unit would have a * non-integral unit dimension. * @throws UnitException * if the underlying unit can't have the given root taken. */ @Override public Unit root(final int root) throws IllegalArgumentException, UnitException { return underUnit.root(root); } /** * Return the definition of this unit. * * @return The definition of this unit (e.g. "K @ 273.15" for degree * celsius). */ @Override public String getDefinition() { String definition; String scaledString = underUnit.toString(); if (scaledString.indexOf(' ') != -1) { scaledString = "(" + scaledString + ")"; } if (!isTime()) { definition = scaledString + " @ " + offset; } else { try { definition = scaledString + " since " + dateFormat .format(new Date((long) (millisecond.toThis( offset, underUnit) + offsetUnitOrigin))); } catch (final UnitException e) { definition = e.toString(); } // can't happen } return definition; } /** * Multiply an offset unit by another unit. * * @param that * The unit with which to multiply this unit. * @return A unit equal to this instance multiplied by the given unit. * @exception UnitException * Can't multiply units. */ @Override public Unit multiply(final Unit that) throws UnitException { return that.multiply(underUnit); } /** * Divide an offset unit by another unit. * * @param that * The unit to divide into this unit. * @return A unit equal to this instance divided by the given unit. * @exception UnitException * Can't divide units. */ @Override public Unit divide(final Unit that) throws UnitException { return that.divideInto(underUnit); } /** * Divide an offset unit into another unit. * * @param that * The unit to be divide by 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 that.divide(underUnit); } /** * 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 convertible. * 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 { double[] newValues; if (equals(that) || that instanceof PromiscuousUnit) { newValues = (copy) ? (double[]) values.clone() : values; } else { newValues = that.toThat(values, underUnit, copy); for (int i = 0; i < newValues.length; ++i) { if (newValues[i] == newValues[i]) { newValues[i] -= offset; } } } return newValues; } /** * 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, convert values in place. * @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, final boolean copy) throws UnitException { float[] newValues; if (equals(that) || that instanceof PromiscuousUnit) { newValues = (copy) ? (float[]) values.clone() : values; } else { newValues = that.toThat(values, underUnit, copy); for (int i = 0; i < newValues.length; ++i) { if (newValues[i] == newValues[i]) { newValues[i] -= offset; } } } return newValues; } /** * 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 convertible. * 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 convertible. * 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 a * <code>values</code>, else return a new array * @return The converted values. * require: The units are convertible. * 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 { double[] newValues = (copy) ? (double[]) values.clone() : values; if (!(equals(that) || that instanceof PromiscuousUnit)) { for (int i = 0; i < newValues.length; ++i) { if (newValues[i] == newValues[i]) { newValues[i] += offset; } } newValues = that.toThis(newValues, underUnit, copy); } return newValues; } /** * 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 a * <code>values</code>, else return a new array * @return The converted values. * require: The units are convertible. * 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 { float[] newValues = (copy) ? (float[]) values.clone() : values; if (!(equals(that) || that instanceof PromiscuousUnit)) { for (int i = 0; i < newValues.length; ++i) { if (newValues[i] == newValues[i]) { newValues[i] += offset; } } newValues = that.toThis(newValues, underUnit, copy); } return newValues; } /** * Gets the absolute unit of this unit. An interval in the underlying * physical quantity has the same numeric value in an absolute unit of a * unit as in the unit itself -- but an absolute unit is always referenced * to the physical origin of the underlying physical quantity. For example, * the absolute unit corresponding to degrees celsius is degrees kelvin -- * and calling this method on a degrees celsius unit obtains a degrees * kelvin unit. * * @return The absolute unit corresponding to this unit. */ @Override public Unit getAbsoluteUnit() { return underUnit.getAbsoluteUnit(); } /** * 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 underUnit.isConvertible(unit); } private static void myAssert(final boolean assertion) { if (!assertion) { throw new AssertionError(); } } 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). * @exception UnitException * A problem occurred. */ public static void main(final String[] args) throws UnitException { final BaseUnit degK = SI.kelvin; final Unit degC = new OffsetUnit(273.15, degK); final ScaledUnit degR = new ScaledUnit(1 / 1.8, degK); final Unit degF = new OffsetUnit(459.67, degR); myAssert(degC, degC); myAssert(!degC.equals(degK)); myAssert(!degC.equals(degF)); myAssert(degC.isConvertible(degC)); myAssert(degC.isConvertible(degK)); myAssert(degC.isConvertible(degF)); myAssert(degF, degF); myAssert(degF.toThis(0, degC), degC.toThat(0, degF)); myAssert(degC.toThis(32, degF), degF.toThat(32, degC)); myAssert(degC.toThis(degF.toThis(0, degC), degF), 0); myAssert(degC.toThat(degF.toThat(32, degC), degF), 32); double[] values = { 0, 100 }; myAssert(degF.toThis(values, degC), degC.toThat(values, degF)); myAssert(degC.toThis(degF.toThis(values, degC), degF), values); values = new double[] { 32, 212 }; myAssert(degC.toThis(values, degF), degF.toThat(values, degC)); myAssert(degF.pow(2), degR.pow(2)); myAssert(degF.multiply(degC), degC.multiply(degF)); myAssert(degF.multiply(degC), degR.multiply(degK)); myAssert(degF.divide(degC), degR.divide(degK)); myAssert(degC.divide(degF), degK.divide(degR)); System.out.println("Done"); } /** * Indicates if this instance equals a unit. * * @param unit * The unit. * @return <code>true</code> if and only if this instance equals the unit. */ @Override public boolean equals(final Unit unit) { if (this == unit) { return true; } if (offset == 0) return underUnit.equals(unit); if (!(unit instanceof OffsetUnit)) { return false; } final OffsetUnit that = (OffsetUnit) unit; return offset == that.offset && underUnit.equals(that.underUnit); } /** * 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 = offset == 0 ? underUnit.hashCode() : underUnit.hashCode() ^ Double.valueOf(offset).hashCode(); } return hashCode; } @Override public DerivedUnit getDerivedUnit() { return underUnit.getDerivedUnit(); } }