com.hfg.units.Quantity Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.units;
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.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
{
private Double mDoubleValue;
private Integer mIntValue;
private Long mLongValue;
private Unit mUnit;
private Map mScalingFactorMap;
// private static Pattern sIntRegex = Pattern.compile("(-?\\d+)\\s?+([^\\d\\.]\\S+)");
private static Pattern sIntRegex = Pattern.compile("(-?\\d+)\\s?+([^\\d]+)");
private static Pattern sDoubleRegex = Pattern.compile("(-?\\d+\\.\\d+)\\s?+([^\\d]+)");
private static Pattern sScientificRegex = Pattern.compile("(-?\\d+(?:\\.\\d+)?E\\-?\\d+)\\s?+(\\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)
{
Matcher m = sIntRegex.matcher(inValue.trim());
if (m.matches())
{
mIntValue = Integer.parseInt(m.group(1));
mUnit = Unit.valueOf(m.group(2));
}
else
{
m = sDoubleRegex.matcher(inValue.trim());
if (m.matches())
{
mDoubleValue = Double.parseDouble(m.group(1));
mUnit = Unit.valueOf(m.group(2));
}
else
{
m = sScientificRegex.matcher(inValue.trim());
if (m.matches())
{
mDoubleValue = Double.parseDouble(m.group(1));
mUnit = Unit.valueOf(m.group(2));
}
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 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(null);
}
//---------------------------------------------------------------------------
public String toString(String inFmtString)
{
StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(" ");
Double doubleValue = doubleValue();
if (doubleValue != null)
{
if (doubleValue == Math.floor(doubleValue)
&& ! Double.isInfinite(doubleValue))
{
buffer.append(doubleValue.intValue());
}
else
{
buffer.append(StringUtil.isSet(inFmtString) ? String.format(inFmtString, doubleValue) : doubleValue + "");
}
buffer.delimitedAppend(mUnit.computeUnitLabel(mScalingFactorMap));
}
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;
}
//---------------------------------------------------------------------------
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;
}
//---------------------------------------------------------------------------
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;
if (getUnit().equals(inUnit))
{
convertedQuantity = this;
}
else
{
Double doubleValue = doubleValue();
// Put the value in SI base units
if (mUnit.getMeasurementSystem() != null
&& (mUnit.getMeasurementSystem().equals(MeasurementSystem.Metric)
|| mUnit.getMeasurementSystem().equals(MeasurementSystem.SI)))
{
Map scalingFactorMap = new HashMap<>(3);
if (StringUtil.isSet(getUnit().getAbbrev())
|| ! getUnit().hasSubUnits())
{
scalingFactorMap.put(mUnit.getQuantityType(), SI_ScalingFactor.one);
}
else
{
for (SubUnit subUnit : mUnit.getSubUnits())
{
QuantityType quantityType = subUnit.getUnit().getQuantityType();
scalingFactorMap.put(quantityType, SI_ScalingFactor.one);
}
}
doubleValue = mUnit.computeScaledValue(doubleValue, scalingFactorMap);
}
else
{
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)
{
return new Quantity(doubleValue() * inValue, getUnit());
}
//---------------------------------------------------------------------------
public Quantity divideBy(double inValue)
{
return new Quantity(doubleValue() / inValue, getUnit());
}
//---------------------------------------------------------------------------
public Quantity add(Quantity inValue)
{
testQuantityTypeEquivalence(inValue);
Quantity result;
if (inValue != null)
{
Quantity convertedValue = inValue.convertTo(getUnit());
result = new Quantity(doubleValue() + convertedValue.doubleValue(), getUnit());
}
else
{
result = this;
}
return result;
}
//---------------------------------------------------------------------------
public Quantity subtract(Quantity inValue)
{
testQuantityTypeEquivalence(inValue);
Quantity result;
if (inValue != null)
{
Quantity convertedValue = inValue.convertTo(getUnit());
result = new Quantity(doubleValue() - convertedValue.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()
{
return mUnit;
}
//---------------------------------------------------------------------------
public Double doubleValue()
{
Double 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 = 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 = 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 testQuantityTypeEquivalence(Quantity inValue)
{
if (! getUnit().getQuantityType().equals(inValue.getUnit().getQuantityType()))
{
throw new UnitException("Quantity Type mismatch (" + getUnit().getQuantityType() + " vs. " + inValue.getUnit().getQuantityType() + ")!");
}
}
}