// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ================================================================================================= package com.twitter.common.quantity; import com.google.common.base.Preconditions; import com.twitter.common.collections.Pair; /** * Represents a value in a unit system and facilitates unambiguous communication of amounts. * Instances are created via static factory {@code of(...)} methods. * * @param <T> the type of number the amount value is expressed in * @param <U> the type of unit that this amount quantifies * * @author John Sirois */ public abstract class Amount<T extends Number & Comparable<T>, U extends Unit<U>> implements Comparable<Amount<T, U>> { private final Pair<T, U> amount; private Amount(T value, U unit) { Preconditions.checkNotNull(value); Preconditions.checkNotNull(unit); this.amount = Pair.of(value, unit); } public T getValue() { return amount.getFirst(); } public U getUnit() { return amount.getSecond(); } public T as(U unit) { return asUnit(unit); } private T asUnit(Unit<?> unit) { return sameUnits(unit) ? getValue() : scale(getUnit().multiplier() / unit.multiplier()); } @Override public int hashCode() { return amount.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Amount)) { return false; } Amount<?, ?> other = (Amount<?, ?>) obj; return amount.equals(other.amount) || isSameAmount(other); } private boolean isSameAmount(Amount<?, ?> other) { // Equals allows Object - so we have no compile time check that other has the right value type; // ie: make sure they don't have Integer when we have Long. Number value = other.getValue(); if (!getValue().getClass().isInstance(value)) { return false; } Unit<?> unit = other.getUnit(); if (!getUnit().getClass().isInstance(unit)) { return false; } @SuppressWarnings("unchecked") U otherUnit = (U) other.getUnit(); return isSameAmount(other, otherUnit); } private boolean isSameAmount(Amount<?, ?> other, U otherUnit) { // Compare in the more precise unit (the one with the lower multiplier). if (otherUnit.multiplier() > getUnit().multiplier()) { return getValue().equals(other.asUnit(getUnit())); } else { return as(otherUnit).equals(other.getValue()); } } @Override public String toString() { return amount.toString(); } @Override public int compareTo(Amount<T, U> other) { // Compare in the more precise unit (the one with the lower multiplier). if (other.getUnit().multiplier() > getUnit().multiplier()) { return getValue().compareTo(other.as(getUnit())); } else { return as(other.getUnit()).compareTo(other.getValue()); } } private boolean sameUnits(Unit<? extends Unit<?>> unit) { return getUnit().equals(unit); } protected abstract T scale(double multiplier); /** * Creates an amount that uses a {@code double} value. * * @param number the number of units the returned amount should quantify * @param unit the unit the returned amount is expressed in terms of * @param <U> the type of unit that the returned amount quantifies * @return an amount quantifying the given {@code number} of {@code unit}s */ public static <U extends Unit<U>> Amount<Double, U> of(double number, U unit) { return new Amount<Double, U>(number, unit) { @Override protected Double scale(double multiplier) { return getValue() * multiplier; } }; } /** * Creates an amount that uses a {@code float} value. * * @param number the number of units the returned amount should quantify * @param unit the unit the returned amount is expressed in terms of * @param <U> the type of unit that the returned amount quantifies * @return an amount quantifying the given {@code number} of {@code unit}s */ public static <U extends Unit<U>> Amount<Float, U> of(float number, U unit) { return new Amount<Float, U>(number, unit) { @Override protected Float scale(double multiplier) { return (float) (getValue() * multiplier); } }; } /** * Creates an amount that uses a {@code long} value. * * @param number the number of units the returned amount should quantify * @param unit the unit the returned amount is expressed in terms of * @param <U> the type of unit that the returned amount quantifies * @return an amount quantifying the given {@code number} of {@code unit}s */ public static <U extends Unit<U>> Amount<Long, U> of(long number, U unit) { return new Amount<Long, U>(number, unit) { @Override protected Long scale(double multiplier) { return (long) (getValue() * multiplier); } }; } /** * Creates an amount that uses an {@code int} value. * * @param number the number of units the returned amount should quantify * @param unit the unit the returned amount is expressed in terms of * @param <U> the type of unit that the returned amount quantifies * @return an amount quantifying the given {@code number} of {@code unit}s */ public static <U extends Unit<U>> Amount<Integer, U> of(int number, U unit) { return new Amount<Integer, U>(number, unit) { @Override protected Integer scale(double multiplier) { return (int) (getValue() * multiplier); } }; } }