All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hfg.units.Quantity Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.units;


import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.hfg.exception.InvalidValueException;
import com.hfg.exception.ProgrammingException;
import com.hfg.util.CompareUtil;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;

//------------------------------------------------------------------------------
/**
 Quantifiable amount. Amount plus units.
 
@author J. Alex Taylor, hairyfatguy.com
*/ //------------------------------------------------------------------------------ // com.hfg XML/HTML Coding Library // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser 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 // // J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com // [email protected] //------------------------------------------------------------------------------ public class Quantity implements Comparable, Cloneable { private Double mDoubleValue; private Integer mIntValue; private Long mLongValue; private Unit mUnit; private Map mScalingFactorMap; private List mSubQuantities; private static final Pattern sIntRegex = Pattern.compile("([\\-+]?\\d+)\\s?+([^\\d\\.]+\\.?)"); private static final Pattern sScientificRegex = Pattern.compile("([\\-+]?(?:\\d+)?(?:\\.\\d+)?(?:E\\-?\\d+)?)\\s?+(.+)"); //########################################################################### // CONSTRUCTORS //########################################################################### //--------------------------------------------------------------------------- /** Convenience constructor that will call Unit.valueOf() on the specified unit string. * @param inValue the numeric amount as a double * @param inUnit the volume unit of the specified amount */ public Quantity(Double inValue, String inUnit) { this(inValue, Unit.valueOf(inUnit)); } //--------------------------------------------------------------------------- /** Convenience constructor that will call Unit.valueOf() on the specified unit string. * @param inValue the numeric amount as a float * @param inUnit the volume unit of the specified amount */ public Quantity(Float inValue, String inUnit) { this(inValue, Unit.valueOf(inUnit)); } //--------------------------------------------------------------------------- /** Convenience constructor that will call Unit.valueOf() on the specified unit string. * @param inValue the numeric amount as an integer * @param inUnit the volume unit of the specified amount */ public Quantity(Integer inValue, String inUnit) { this(inValue, Unit.valueOf(inUnit)); } //--------------------------------------------------------------------------- /** Convenience constructor that will call Unit.valueOf() on the specified unit string. * @param inValue the numeric amount as an integer * @param inUnit the volume unit of the specified amount */ public Quantity(Long inValue, String inUnit) { this(inValue, Unit.valueOf(inUnit)); } //--------------------------------------------------------------------------- /** Convenience constructor. * @param inValue the string value */ public Quantity(String inValue) { if (inValue != null) { String trimmedValue = inValue.trim(); int length = trimmedValue.length(); // To allow for compound quantities we'll chew off a piece at a time int start = 0; while (start < length) { Matcher m = sIntRegex.matcher(trimmedValue); if (m.find(start) && m.start() == start) { if (start > 0 || m.end() < length) { String unitString = m.group(2).trim(); if (unitString.endsWith(" and")) { unitString = unitString.substring(0, unitString.length() - 4); } if (unitString.endsWith(",") || unitString.endsWith(".")) { unitString = unitString.substring(0, unitString.length() - 1); } addSubQuantity(Integer.parseInt(m.group(1)), Unit.valueOf(unitString)); } else { mIntValue = Integer.parseInt(m.group(1)); mUnit = Unit.valueOf(m.group(2)); } start = m.end(); } else { m = sScientificRegex.matcher(trimmedValue); if (m.find(start) && m.start() == start) { if (start > 0 || m.end() < length) { String unitString = m.group(2); if (unitString.endsWith(",")) { unitString = unitString.substring(0, unitString.length() - 1); } addSubQuantity(Double.parseDouble(m.group(1)), Unit.valueOf(unitString)); } else { mDoubleValue = Double.parseDouble(m.group(1)); mUnit = Unit.valueOf(m.group(2)); } start = m.end(); } else { throw new UnitException("Couldn't parse " + StringUtil.singleQuote(inValue) + " into a Quantity!"); } } } } } //--------------------------------------------------------------------------- public Quantity(Double inValue, Unit inUnit) { nullCheckAmount(inValue); mDoubleValue = inValue; mUnit = inUnit; } //--------------------------------------------------------------------------- public Quantity(Float inValue, Unit inUnit) { nullCheckAmount(inValue); mDoubleValue = (inValue != null ? inValue.doubleValue() : null); mUnit = inUnit; } //--------------------------------------------------------------------------- public Quantity(Integer inValue, Unit inUnit) { nullCheckAmount(inValue); mIntValue = inValue; mUnit = inUnit; } //--------------------------------------------------------------------------- public Quantity(Long inValue, Unit inUnit) { nullCheckAmount(inValue); mLongValue = inValue; mUnit = inUnit; } //--------------------------------------------------------------------------- private Quantity(List inSubQuantities) { mSubQuantities = inSubQuantities; } //--------------------------------------------------------------------------- private void nullCheckAmount(Number inValue) { if (null == inValue) { throw new InvalidValueException("A non-null amount must be specified!"); } } //########################################################################### // PUBLIC METHODS //########################################################################### //--------------------------------------------------------------------------- @Override public String toString() { return toString((String)null); } //--------------------------------------------------------------------------- /** * Outputs the Quantity as a numeric value followed by the units where the * numeric portion is formatted according to the specified sprintf string. * @param inFmtString a sprintf format specification * @return the formatted Quantity value */ public String toString(String inFmtString) { StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(" "); if (mSubQuantities != null) { for (Quantity subQuantity : mSubQuantities) { buffer.delimitedAppend(subQuantity.toString(inFmtString)); } } else { Double doubleValue = doubleValue(); if (doubleValue != null) { if (StringUtil.isSet(inFmtString)) { // Apply the specified formatting buffer.append(String.format(inFmtString, doubleValue)); } else if (doubleValue == Math.floor(doubleValue) && !Double.isInfinite(doubleValue)) { // No formatting specified but the value can be represented by an integer buffer.append(doubleValue.intValue()); } else { buffer.append(doubleValue + ""); } boolean isPlural = (doubleValue != 1.0); buffer.delimitedAppend(mUnit.computeUnitLabel(mScalingFactorMap, isPlural)); } } return buffer.toString(); } //--------------------------------------------------------------------------- /** * Outputs the Quantity as a numeric value followed by the units where the * numeric portion is formatted according to the specified DecimalFormat object. * @param inFormat a DecimalFormat format specification * @return the formatted Quantity value */ public String toString(DecimalFormat inFormat) { StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(" "); if (mSubQuantities != null) { for (Quantity subQuantity : mSubQuantities) { buffer.delimitedAppend(subQuantity.toString(inFormat)); } } else { Double doubleValue = doubleValue(); if (doubleValue != null) { if (inFormat != null) { buffer.append(inFormat.format(doubleValue)); } else if (doubleValue == Math.floor(doubleValue) && !Double.isInfinite(doubleValue)) { // No formatting specified but the value can be represented by an integer buffer.append(doubleValue.intValue()); } else { buffer.append(doubleValue + ""); } boolean isPlural = (doubleValue != 1.0); buffer.delimitedAppend(mUnit.computeUnitLabel(mScalingFactorMap, isPlural)); } } return buffer.toString(); } //--------------------------------------------------------------------------- @Override public boolean equals(Object inObj2) { return (inObj2 != null && inObj2 instanceof Quantity && (this == inObj2 || 0 == compareTo(inObj2))); } //--------------------------------------------------------------------------- @Override public int hashCode() { int hashCode = 0; Double doubleValue = doubleValue(); if (doubleValue != null) { hashCode += doubleValue.hashCode(); } if (getUnit() != null) { hashCode += 31 * getUnit().hashCode(); } if (CollectionUtil.hasValues(mScalingFactorMap)) { for (QuantityType quantityType : mScalingFactorMap.keySet()) { hashCode += 31 * quantityType.hashCode() + mScalingFactorMap.get(quantityType).hashCode(); } } return hashCode; } //--------------------------------------------------------------------------- @Override public int compareTo(Object inObj2) { int result = -1; if (inObj2 != null) { if (inObj2 instanceof Quantity) { Quantity quantity2 = (Quantity) inObj2; result = CompareUtil.compare(getUnit(), quantity2.getUnit()); if (0 == result) { result = CompareUtil.compare(doubleValue(), quantity2.doubleValue()); } if (0 == result) { result = CompareUtil.compare(mScalingFactorMap, quantity2.mScalingFactorMap); } } } return result; } //--------------------------------------------------------------------------- @Override public Quantity clone() { Quantity clone; try { clone = (Quantity) super.clone(); } catch (CloneNotSupportedException e) { throw new ProgrammingException(e); } if (mScalingFactorMap != null) { clone.mScalingFactorMap = new HashMap<>(mScalingFactorMap); } if (mSubQuantities != null) { clone.mSubQuantities = new ArrayList<>(mSubQuantities); } return clone; } //--------------------------------------------------------------------------- public void scale(QuantityType inQuantityType, SI_ScalingFactor inScalingFactor) { if (null == mScalingFactorMap) { mScalingFactorMap = new HashMap<>(4); } mScalingFactorMap.put(inQuantityType, inScalingFactor); } //--------------------------------------------------------------------------- public Quantity convertTo(Unit inUnit) { Quantity convertedQuantity = null; if (mSubQuantities != null) { // It's a compound quantity. Convert sub-quantities to the unit of the last // sub-quantity and total them. for (Quantity subquantity : mSubQuantities) { if (null == convertedQuantity) { convertedQuantity = subquantity.convertTo(inUnit); } else { convertedQuantity = convertedQuantity.add(subquantity.convertTo(inUnit)); } } } else { // It's a regular quantity if (getUnit().equals(inUnit)) { // It's already in the desired units convertedQuantity = this; } else { // Our standard of interchange is the base SI unit Double doubleValue = mUnit.computeBaseSIValue(doubleValue()); convertedQuantity = new Quantity(inUnit.computeValueFromBaseSIValue(doubleValue), inUnit); } } return convertedQuantity; } //--------------------------------------------------------------------------- public Quantity invert() { return new Quantity( - doubleValue(), getUnit()); } //--------------------------------------------------------------------------- public Quantity multiplyBy(double inValue) { Quantity newQuantity; if (mSubQuantities != null) { List newSubQuantities = new ArrayList<>(2); for (Quantity subQuantity : mSubQuantities) { newSubQuantities.add(subQuantity.multiplyBy(inValue)); } newQuantity = new Quantity(newSubQuantities); } else { newQuantity = new Quantity(doubleValue() * inValue, getUnit()); } return newQuantity; } //--------------------------------------------------------------------------- public Quantity divideBy(double inValue) { Quantity newQuantity; if (mSubQuantities != null) { List newSubQuantities = new ArrayList<>(2); for (Quantity subQuantity : mSubQuantities) { newSubQuantities.add(subQuantity.divideBy(inValue)); } newQuantity = new Quantity(newSubQuantities); } else { newQuantity = new Quantity(doubleValue() / inValue, getUnit()); } return newQuantity; } //--------------------------------------------------------------------------- public Quantity add(Quantity inValue) { testQuantityTypeEquivalence(inValue); Quantity result; if (inValue != null) { Quantity convertedAdditionalValue = inValue.convertTo(getUnit()); result = new Quantity(doubleValue() + convertedAdditionalValue.doubleValue(), getUnit()); } else { result = this; } return result; } //--------------------------------------------------------------------------- public Quantity subtract(Quantity inValue) { testQuantityTypeEquivalence(inValue); Quantity result; if (inValue != null) { Quantity convertedAdditionalValue = inValue.convertTo(getUnit()); result = new Quantity(doubleValue() - convertedAdditionalValue.doubleValue(), getUnit()); } else { result = this; } return result; } //--------------------------------------------------------------------------- public boolean lessThan(Quantity inValue) { testQuantityTypeEquivalence(inValue); boolean result = false; if (inValue != null) { Quantity convertedValue = inValue.convertTo(getUnit()); result = doubleValue() < convertedValue.doubleValue(); } return result; } //--------------------------------------------------------------------------- public boolean lessThanOrEqualTo(Quantity inValue) { testQuantityTypeEquivalence(inValue); boolean result = false; if (inValue != null) { Quantity convertedValue = inValue.convertTo(getUnit()); result = doubleValue() <= convertedValue.doubleValue(); } return result; } //--------------------------------------------------------------------------- public boolean greaterThan(Quantity inValue) { testQuantityTypeEquivalence(inValue); boolean result = false; if (inValue != null) { Quantity convertedValue = inValue.convertTo(getUnit()); result = doubleValue() > convertedValue.doubleValue(); } return result; } //--------------------------------------------------------------------------- public boolean greaterThanOrEqualTo(Quantity inValue) { testQuantityTypeEquivalence(inValue); boolean result = false; if (inValue != null) { Quantity convertedValue = inValue.convertTo(getUnit()); result = doubleValue() >= convertedValue.doubleValue(); } return result; } //--------------------------------------------------------------------------- public Unit getUnit() { if (null == mUnit && mSubQuantities != null) { mUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); } return mUnit; } //--------------------------------------------------------------------------- public Double doubleValue() { Double value; if (mSubQuantities != null) { // It's a compound quantity. Convert sub-quantities to the unit of the last // sub-quantity and total them. Unit targetUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); double totalDoubleValue = 0; for (Quantity subquantity : mSubQuantities) { totalDoubleValue += subquantity.convertTo(targetUnit).doubleValue(); } value = totalDoubleValue; } else { // It's a regular quantity value = mDoubleValue; if (null == value) { if (mIntValue != null) { value = mIntValue.doubleValue(); } else if (mLongValue != null) { value = mLongValue.doubleValue(); } } // Scale the value? if (value != null && CollectionUtil.hasValues(mScalingFactorMap)) { value = mUnit.computeScaledValue(value, mScalingFactorMap); } } return value; } //--------------------------------------------------------------------------- public Float floatValue() { Double doubleValue = doubleValue(); return (doubleValue != null ? doubleValue.floatValue() : null); } //--------------------------------------------------------------------------- public Integer intValue() { Integer value; if (mSubQuantities != null) { // It's a compound quantity. Convert sub-quantities to the unit of the last // sub-quantity and total them. Unit targetUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); int totalIntValue = 0; for (Quantity subquantity : mSubQuantities) { totalIntValue += subquantity.convertTo(targetUnit).intValue(); } value = totalIntValue; } else { // It's a regular quantity value = mIntValue; if (null == value) { if (mDoubleValue != null) { value = mDoubleValue.intValue(); } else if (mLongValue != null) { value = mLongValue.intValue(); } } // Scale the value? if (value != null && CollectionUtil.hasValues(mScalingFactorMap)) { value = (int) mUnit.computeScaledValue(value, mScalingFactorMap); } } return value; } //--------------------------------------------------------------------------- public Long longValue() { Long value; if (mSubQuantities != null) { // It's a compound quantity. Convert sub-quantities to the unit of the last // sub-quantity and total them. Unit targetUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); long totalLongValue = 0; for (Quantity subquantity : mSubQuantities) { totalLongValue += subquantity.convertTo(targetUnit).longValue(); } value = totalLongValue; } else { // It's a regular quantity value = mLongValue; if (null == value) { if (mDoubleValue != null) { value = mDoubleValue.longValue(); } else if (mIntValue != null) { value = mIntValue.longValue(); } } // Scale the value? if (value != null && CollectionUtil.hasValues(mScalingFactorMap)) { value = (long) mUnit.computeScaledValue(value, mScalingFactorMap); } } return value; } //--------------------------------------------------------------------------- public Quantity autoScale() { return autoScale(SI_ScalingFactor.values()); } //--------------------------------------------------------------------------- public Quantity autoScale(SI_ScalingFactor[] inScalingValues) { Quantity scaledQuantity = null; double doubleValue = doubleValue(); if (doubleValue > 900 || doubleValue < 0.1) { for (int i = 1; i < inScalingValues.length; i++) { if (doubleValue < inScalingValues[i - 1].getScalingFactor() && doubleValue >= inScalingValues[i].getScalingFactor()) { if (StringUtil.isSet(getUnit().getAbbrev()) || ! getUnit().hasSubUnits()) { scaledQuantity = this.convertTo(new Unit(getUnit(), inScalingValues[i])); } else { List subUnits = new ArrayList<>(getUnit().getSubUnits()); subUnits.set(0, new SubUnit(subUnits.get(0).getUnit(), inScalingValues[i], subUnits.get(0).getPow())); scaledQuantity = this.convertTo(new Unit(subUnits)); } break; } } } return (scaledQuantity != null ? scaledQuantity : this); } //--------------------------------------------------------------------------- /** Performs a validation test to ensure that the unit is of the expected quantity type. @param inExpectedQuantityType the quantity type that is expected. @throws InvalidValueException if the unit isn't set or is not of the expected type. */ public void verifyQuantityType(QuantityType inExpectedQuantityType) { if (null == getUnit() || ! getUnit().getQuantityType().equals(inExpectedQuantityType)) { throw new InvalidValueException("The quantity " + StringUtil.singleQuote(toString()) + " isn't specified in " + inExpectedQuantityType + " units!"); } } //########################################################################### // PRIVATE METHODS //########################################################################### //--------------------------------------------------------------------------- private void addSubQuantity(Integer inValue, Unit inUnit) { if (null == mSubQuantities) { mSubQuantities = new ArrayList<>(2); } mSubQuantities.add(new Quantity(inValue, inUnit)); } //--------------------------------------------------------------------------- private void addSubQuantity(Double inValue, Unit inUnit) { if (null == mSubQuantities) { mSubQuantities = new ArrayList<>(2); } mSubQuantities.add(new Quantity(inValue, inUnit)); } //--------------------------------------------------------------------------- private void testQuantityTypeEquivalence(Quantity inValue) { Unit unit1 = getUnit(); if (null == unit1 && mSubQuantities != null) { unit1 = mSubQuantities.get(0).getUnit(); } Unit unit2 = inValue.getUnit(); if (null == unit2 && inValue.mSubQuantities != null) { unit2 = inValue.mSubQuantities.get(0).getUnit(); } if (! unit1.getQuantityType().equals(unit2.getQuantityType())) { throw new UnitException("Quantity Type mismatch (" + unit1.getQuantityType() + " vs. " + unit2.getQuantityType() + ")!"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy