001/*
002 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences.
003 * Copyright (C) 2006 - JScience (http://jscience.org/)
004 * All rights reserved.
005 * 
006 * Permission to use, copy, modify, and distribute this software is
007 * freely granted, provided that this notice is preserved.
008 */
009package javax.measure.converter;
010
011import java.io.Serializable;
012
013/**
014 * <p> This class represents a converter of numeric values.</p>
015 * 
016 * <p> It is not required for sub-classes to be immutable
017 *     (e.g. currency converter).</p>
018 *     
019 * <p> Sub-classes must ensure unicity of the {@link #IDENTITY identity} 
020 *     converter. In other words, if the result of an operation is equivalent
021 *     to the identity converter, then the unique {@link #IDENTITY} instance 
022 *     should be returned.</p>
023 *
024 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
025 * @version 3.1, April 22, 2006
026 */
027public abstract class UnitConverter implements Serializable {
028
029    /**
030     * Holds the identity converter (unique). This converter does nothing
031     * (<code>ONE.convert(x) == x</code>).
032     */
033    public static final UnitConverter IDENTITY = new Identity();
034
035    /**
036     * Default constructor.
037     */
038    protected UnitConverter() {
039    }
040
041    /**
042     * Returns the inverse of this converter. If <code>x</code> is a valid
043     * value, then <code>x == inverse().convert(convert(x))</code> to within
044     * the accuracy of computer arithmetic.
045     *
046     * @return the inverse of this converter.
047     */
048    public abstract UnitConverter inverse();
049
050    /**
051     * Converts a double value.
052     *
053     * @param  x the numeric value to convert.
054     * @return the converted numeric value.
055     * @throws ConversionException if an error occurs during conversion.
056     */
057    public abstract double convert(double x) throws ConversionException;
058
059    /**
060     * Indicates if this converter is linear. A converter is linear if
061     * <code>convert(u + v) == convert(u) + convert(v)</code> and
062     * <code>convert(r * u) == r * convert(u)</code>.
063     * For linear converters the following property always hold:[code]
064     *     y1 = c1.convert(x1);
065     *     y2 = c2.convert(x2); 
066     * then y1*y2 = c1.concatenate(c2).convert(x1*x2)[/code]
067     *
068     * @return <code>true</code> if this converter is linear;
069     *         <code>false</code> otherwise.
070     */
071    public abstract boolean isLinear();
072
073    /**
074     * Indicates whether this converter is considered the same as the  
075     * converter specified. To be considered equal this converter 
076     * concatenated with the one specified must returns the {@link #IDENTITY}.
077     *
078     * @param  cvtr the converter with which to compare.
079     * @return <code>true</code> if the specified object is a converter 
080     *         considered equals to this converter;<code>false</code> otherwise.
081     */
082    public boolean equals(Object cvtr) {
083        if (!(cvtr instanceof UnitConverter)) return false;
084        return this.concatenate(((UnitConverter)cvtr).inverse()) == IDENTITY;        
085    }
086
087    /**
088     * Returns a hash code value for this converter. Equals object have equal
089     * hash codes.
090     *
091     * @return this converter hash code value.
092     * @see    #equals
093     */
094    public int hashCode() {
095        return Float.floatToIntBits((float)convert(1.0));
096    }
097
098    /**
099     * Concatenates this converter with another converter. The resulting
100     * converter is equivalent to first converting by the specified converter,
101     * and then converting by this converter.
102     * 
103     * <p>Note: Implementations must ensure that the {@link #IDENTITY} instance
104     *          is returned if the resulting converter is an identity 
105     *          converter.</p> 
106     * 
107     * @param  converter the other converter.
108     * @return the concatenation of this converter with the other converter.
109     */
110    public UnitConverter concatenate(UnitConverter converter) {
111        return (converter == IDENTITY) ? this : new Compound(converter, this);
112    }
113
114    /**
115     * This inner class represents the identity converter (singleton).
116     */
117    private static final class Identity extends UnitConverter {
118
119        @Override
120        public UnitConverter inverse() {
121            return this;
122        }
123
124        @Override
125        public double convert(double x) {
126            return x;
127        }
128
129        @Override
130        public boolean isLinear() {
131            return true;
132        }
133
134        @Override
135        public UnitConverter concatenate(UnitConverter converter) {
136            return converter;
137        }
138
139        private static final long serialVersionUID = 1L;
140
141    }
142
143    /**
144     * This inner class represents a compound converter.
145     */
146    private static final class Compound extends UnitConverter {
147
148        /**
149         * Holds the first converter.
150         */
151        private final UnitConverter _first;
152
153        /**
154         * Holds the second converter.
155         */
156        private final UnitConverter _second;
157
158        /**
159         * Creates a compound converter resulting from the combined
160         * transformation of the specified converters.
161         *
162         * @param  first the first converter.
163         * @param  second the second converter.
164         */
165        private Compound(UnitConverter first, UnitConverter second) {
166            _first = first;
167            _second = second;
168        }
169
170        @Override
171        public UnitConverter inverse() {
172            return new Compound(_second.inverse(), _first.inverse());
173        }
174
175        @Override
176        public double convert(double x) {
177            return _second.convert(_first.convert(x));
178        }
179
180        @Override
181        public boolean isLinear() {
182            return _first.isLinear() && _second.isLinear();
183        }
184
185        private static final long serialVersionUID = 1L;
186
187    }
188}