com.gnapse.metric.Quantity.java Source code

Java tutorial

Introduction

Here is the source code for com.gnapse.metric.Quantity.java

Source

/*
 * Copyright (C) 2012 Gnapse.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License 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.gnapse.metric;

import static com.google.common.base.Preconditions.checkNotNull;

import com.gnapse.common.math.BigFraction;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;

import java.util.Arrays;
import java.util.Iterator;

/**
 * Represents a quantity of a {@linkplain Property property}, consisting of a scalar numeric value
 * and the {@linkplain Unit unit} in which it is expressed.
 *
 * @author Ernesto Garca
 */
public final class Quantity implements Comparable<Quantity> {

    /**
     * The numeric value represented by this quantity.
     */
    private final BigFraction value;

    /**
     * The unit in which this quantity is expressed.
     */
    private final Unit unit;

    /**
     * Creates a quantity with a value of 0 and measured in the given unit.
     */
    public static Quantity zero(Unit unit) {
        return new Quantity(BigFraction.ZERO, unit);
    }

    /**
     * Sums all the given quantities and returns the result as a quantity expressed in the specified
     * unit.
     *
     * @param resultUnits the unit in which the resulting quantity shall be expressed
     * @param quantities the quantities to sum
     * @return a quantity consisting of the sum of all the given quantities, and expressed in the
     *     specified results unit
     * @throws IncompatibleUnitsException if any of the quantities to sum represents a value of a
     *     property different than the property in which the result shall be expressed
     */
    public static Quantity sum(Unit resultUnits, Quantity... quantities) throws MetricException {
        checkAdditiveQuantities(Arrays.asList(quantities));

        if (quantities.length == 0) {
            return zero(resultUnits);
        }

        if (quantities.length == 1) {
            return quantities[0].convertTo(resultUnits);
        }

        BigFraction result = quantities[0].convertTo(resultUnits).getValue();
        for (int i = 1; i < quantities.length; ++i) {
            Quantity q = quantities[i].convertTo(resultUnits);
            result = result.add(q.getValue());
        }
        return new Quantity(result, resultUnits);
    }

    /**
     * Sums all the given quantities and returns the result as a quantity expressed in the specified
     * unit.
     *
     * @param resultUnits the unit in which the resulting quantity shall be expressed
     * @param quantities the quantities to sum
     * @return a quantity consisting of the sum of all the given quantities, and expressed in the
     *     specified results unit
     * @throws IncompatibleUnitsException if any of the quantities to sum represents a value of a
     *     property different than the property in which the result shall be expressed
     */
    public static Quantity sum(Unit resultUnits, Iterable<Quantity> quantities) throws MetricException {
        checkAdditiveQuantities(quantities);

        final Iterator<Quantity> it = quantities.iterator();
        if (!it.hasNext()) {
            return zero(resultUnits);
        }

        Quantity first = it.next().convertTo(resultUnits);
        if (!it.hasNext()) {
            return first;
        }

        BigFraction result = first.getValue();
        while (it.hasNext()) {
            Quantity q = it.next().convertTo(resultUnits);
            result = result.add(q.getValue());
        }
        return new Quantity(result, resultUnits);
    }

    /**
     * Creates a new quantity representing the given numeric value expressed in the specified unit.
     */
    public Quantity(BigFraction value, Unit unit) {
        this.value = checkNotNull(value);
        this.unit = checkNotNull(unit);
    }

    /**
     * The value of this quantity.
     */
    public BigFraction getValue() {
        return value;
    }

    /**
     * The unit in which this quantity is expressed.
     */
    public Unit getUnit() {
        return unit;
    }

    /**
     * The property that this quantity measures.
     */
    public Property getProperty() {
        return unit.getProperty();
    }

    /**
     * The universe in which this quantity is measured.
     */
    public Universe getUniverse() {
        return unit.getUniverse();
    }

    @Override
    public String toString() {
        final String valueStr = unit.getUniverse().getNumberFormatter().apply(value.toBigDecimal());
        String unitName = unit.toStringForValue(value);
        return String.format("%s %s", valueStr, unitName);
    }

    /**
     * Determines if this quantity is equal to the given object.
     *
     * <p>A different quantity with the same numeric value expressed in the same unit as this one is
     * obviously equal to this quantity.  Moreover, if the given object is a quantity of the same
     * property, and when converted to the unit in which this quantity is expressed, the values are
     * the same, then both quantities are equal.  If the given unit is a quantity of a different
     * property, then {@code false} is returned.</p>
     *
     * <p>For example, {@code 10 cm} equals {@code 100 mm} and {@code 0 celcius} equals
     * {@code 32 fahrenheit}.  On the other hand {@code 10 m} is not equal to {@code 10 yards},
     * {@code 1 meter} is not equal to {@code 1 kelvin}.</p>
     *
     * @param o the object to test for equality
     * @return {@code true} if the given object is a quantity that expresses the same magnitude that
     *     is represented by this quantity; {@code false} otherwise.
     */
    @Override
    public boolean equals(Object o) {
        if (o == null || !(o instanceof Quantity)) {
            return false;
        }
        if (o == this) {
            return true;
        }
        Quantity q = (Quantity) o;
        if (this.unit.equals(q.unit) && this.value.equals(q.value)) {
            return true;
        }

        try {
            BigFraction v = q.convertTo(this.unit).getValue();
            return v.equals(this.value);
        } catch (MetricException e) {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(unit, value);
    }

    /**
     * Converts this quantity to be expressed in the other unit given.
     *
     * @param otherUnit the unit to which this quantity will be conversed
     * @return a quantity representing the same amount of this one, but expressed in the other unit
     *     given.
     * @throws IllegalArgumentException if the given unit and the unit of this quantity are from
     *     different properties.
     */
    public Quantity convertTo(Unit otherUnit) throws MetricException {
        if (otherUnit == this.unit) {
            return this;
        }
        BigFraction newValue = unit.convertTo(otherUnit, value);
        return new Quantity(newValue, otherUnit);
    }

    /**
     * Compares this quantity with the specified quantity for order.  Returns a negative integer,
     * zero, or a positive integer as this quantity is less than, equal to, or greater than the
     * specified quantity.
     *
     * @param otherQuantity the quantity to compare to
     * @return a negative integer, zero, or a positive integer as this quantity is less than, equal
     *     to, or greater than the specified quantity.
     * @throws RuntimeException if the specified quantity expresses a value of another property,
     *     thus being incompatible to be compared to this quantity.  In this case the {@code
     *     RuntimeException} thrown refers to the original {@link MetricException} as its {@link
     *     RuntimeException#getCause() cause}.
     */
    @Override
    public int compareTo(Quantity otherQuantity) {
        try {
            return value.compareTo(otherQuantity.convertTo(this.unit).getValue());
        } catch (MetricException ex) {
            throw Throwables.propagate(ex);
        }
    }

    private static void checkAdditiveQuantities(Iterable<Quantity> quantities) throws MetricException {
        final int size = Iterables.size(quantities);
        if (size > 1) {
            final boolean hasOffset = Iterables.any(quantities, new Predicate<Quantity>() {
                @Override
                public boolean apply(Quantity q) {
                    return q.getUnit().hasOffset();
                }
            });
            if (hasOffset) {
                throw MetricException.withMessage("Cannot sum non-absolute quantities");
            }
        }
    }

}