001    // =================================================================================================
002    // Copyright 2011 Twitter, Inc.
003    // -------------------------------------------------------------------------------------------------
004    // Licensed under the Apache License, Version 2.0 (the "License");
005    // you may not use this work except in compliance with the License.
006    // You may obtain a copy of the License in the LICENSE file, or at:
007    //
008    //  http://www.apache.org/licenses/LICENSE-2.0
009    //
010    // Unless required by applicable law or agreed to in writing, software
011    // distributed under the License is distributed on an "AS IS" BASIS,
012    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013    // See the License for the specific language governing permissions and
014    // limitations under the License.
015    // =================================================================================================
016    
017    package com.twitter.common.quantity;
018    
019    import com.google.common.base.Preconditions;
020    
021    import com.twitter.common.collections.Pair;
022    
023    /**
024     * Represents a value in a unit system and facilitates unambiguous communication of amounts.
025     * Instances are created via static factory {@code of(...)} methods.
026     *
027     * @param <T> the type of number the amount value is expressed in
028     * @param <U> the type of unit that this amount quantifies
029     *
030     * @author John Sirois
031     */
032    public abstract class Amount<T extends Number & Comparable<T>, U extends Unit<U>>
033        implements Comparable<Amount<T, U>> {
034    
035      private final Pair<T, U> amount;
036    
037      private Amount(T value, U unit) {
038        Preconditions.checkNotNull(value);
039        Preconditions.checkNotNull(unit);
040    
041        this.amount = Pair.of(value, unit);
042      }
043    
044      public T getValue() {
045        return amount.getFirst();
046      }
047    
048      public U getUnit() {
049        return amount.getSecond();
050      }
051    
052      public T as(U unit) {
053        return asUnit(unit);
054      }
055    
056      private T asUnit(Unit<?> unit) {
057        return sameUnits(unit) ? getValue() : scale(getUnit().multiplier() / unit.multiplier());
058      }
059    
060      @Override
061      public int hashCode() {
062        return amount.hashCode();
063      }
064    
065      @Override
066      public boolean equals(Object obj) {
067        if (this == obj) {
068          return true;
069        }
070        if (!(obj instanceof Amount)) {
071          return false;
072        }
073    
074        Amount<?, ?> other = (Amount<?, ?>) obj;
075        return amount.equals(other.amount) || isSameAmount(other);
076      }
077    
078      private boolean isSameAmount(Amount<?, ?> other) {
079        // Equals allows Object - so we have no compile time check that other has the right value type;
080        // ie: make sure they don't have Integer when we have Long.
081        Number value = other.getValue();
082        if (!getValue().getClass().isInstance(value)) {
083          return false;
084        }
085    
086        Unit<?> unit = other.getUnit();
087        if (!getUnit().getClass().isInstance(unit)) {
088          return false;
089        }
090    
091        @SuppressWarnings("unchecked")
092        U otherUnit = (U) other.getUnit();
093        return isSameAmount(other, otherUnit);
094      }
095    
096      private boolean isSameAmount(Amount<?, ?> other, U otherUnit) {
097        // Compare in the more precise unit (the one with the lower multiplier).
098        if (otherUnit.multiplier() > getUnit().multiplier()) {
099          return getValue().equals(other.asUnit(getUnit()));
100        } else {
101          return as(otherUnit).equals(other.getValue());
102        }
103      }
104    
105      @Override
106      public String toString() {
107        return amount.toString();
108      }
109    
110      @Override
111      public int compareTo(Amount<T, U> other) {
112        // Compare in the more precise unit (the one with the lower multiplier).
113        if (other.getUnit().multiplier() > getUnit().multiplier()) {
114          return getValue().compareTo(other.as(getUnit()));
115        } else {
116          return as(other.getUnit()).compareTo(other.getValue());
117        }
118      }
119    
120      private boolean sameUnits(Unit<? extends Unit<?>> unit) {
121        return getUnit().equals(unit);
122      }
123    
124      protected abstract T scale(double multiplier);
125    
126      /**
127       * Creates an amount that uses a {@code double} value.
128       *
129       * @param number the number of units the returned amount should quantify
130       * @param unit the unit the returned amount is expressed in terms of
131       * @param <U> the type of unit that the returned amount quantifies
132       * @return an amount quantifying the given {@code number} of {@code unit}s
133       */
134      public static <U extends Unit<U>> Amount<Double, U> of(double number, U unit) {
135        return new Amount<Double, U>(number, unit) {
136          @Override protected Double scale(double multiplier) {
137            return getValue() * multiplier;
138          }
139        };
140      }
141    
142      /**
143       * Creates an amount that uses a {@code float} value.
144       *
145       * @param number the number of units the returned amount should quantify
146       * @param unit the unit the returned amount is expressed in terms of
147       * @param <U> the type of unit that the returned amount quantifies
148       * @return an amount quantifying the given {@code number} of {@code unit}s
149       */
150      public static <U extends Unit<U>> Amount<Float, U> of(float number, U unit) {
151        return new Amount<Float, U>(number, unit) {
152          @Override protected Float scale(double multiplier) {
153            return (float) (getValue() * multiplier);
154          }
155        };
156      }
157    
158      /**
159       * Creates an amount that uses a {@code long} value.
160       *
161       * @param number the number of units the returned amount should quantify
162       * @param unit the unit the returned amount is expressed in terms of
163       * @param <U> the type of unit that the returned amount quantifies
164       * @return an amount quantifying the given {@code number} of {@code unit}s
165       */
166      public static <U extends Unit<U>> Amount<Long, U> of(long number, U unit) {
167        return new Amount<Long, U>(number, unit) {
168          @Override protected Long scale(double multiplier) {
169            return (long) (getValue() * multiplier);
170          }
171        };
172      }
173    
174      /**
175       * Creates an amount that uses an {@code int} value.
176       *
177       * @param number the number of units the returned amount should quantify
178       * @param unit the unit the returned amount is expressed in terms of
179       * @param <U> the type of unit that the returned amount quantifies
180       * @return an amount quantifying the given {@code number} of {@code unit}s
181       */
182      public static <U extends Unit<U>> Amount<Integer, U> of(int number, U unit) {
183        return new Amount<Integer, U>(number, unit) {
184          @Override protected Integer scale(double multiplier) {
185            return (int) (getValue() * multiplier);
186          }
187        };
188      }
189    }