//
// 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.: 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
     *            null 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
     * null.
     * 
     * @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
     *            null 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);
    }

    /**
     * 

* 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". *

* * @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 true 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 * null 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 root-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 values. * @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 values. * @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 values. * @param copy * if false and that equals this, return * values, 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 values. * @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 that equals this, return a * values, 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 that equals this, return a * values, 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 toThis(...)/ and * toThat(...) 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 true 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(); } }

Ads help maintain this website.