com.hfg.units.Unit 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.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.regex.Pattern;
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;
import com.hfg.util.collection.OrderedSet;
// NOTE: Add new unit classes to the /META-DATA/services/com.hfg.units.Unit file
// for them to be properly discoverable!!!
//------------------------------------------------------------------------------
/**
Container for 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 Unit implements Comparable
{
private static MathContext sDefaultMathContext = new MathContext(16, RoundingMode.HALF_UP);
private static Set sInstances = new OrderedSet<>(50);
private static Map sLookupMap = new HashMap<>();
private static boolean sInitialized = false;
private MeasurementSystem mMeasurementSystem;
private String mName;
private Set mAlternateNames;
private String mPlural;
private String mAbbrev;
private String mPluralAbbrev;
private QuantityType mQuantityType;
private Integer mPow;
private BaseSIUnitConverter mConversionToBaseSIUnit;
private List mSubUnits;
private MathContext mMathContext;
//---------------------------------------------------------------------------
protected Unit(QuantityType inQuantityType, String inName, String inAbbrev, Integer inPow)
{
this(null, inQuantityType, inName, inAbbrev, inPow, null);
}
//---------------------------------------------------------------------------
protected Unit(MeasurementSystem inSystem, QuantityType inQuantityType, String inName, String inAbbrev, Integer inPow)
{
this(inSystem, inQuantityType, inName, inAbbrev, inPow, null);
}
//---------------------------------------------------------------------------
protected Unit(QuantityType inQuantityType, String inName, String inAbbrev, Integer inPow, BaseSIUnitConverter inConversionToBaseSIUnit)
{
this(null, inQuantityType, inName, inAbbrev, inPow, inConversionToBaseSIUnit);
}
//---------------------------------------------------------------------------
protected Unit(MeasurementSystem inSystem, QuantityType inQuantityType, String inName, String inAbbrev, Integer inPow, BaseSIUnitConverter inConversionToBaseSIUnit)
{
mMeasurementSystem = inSystem;
mQuantityType = inQuantityType;
mName = inName;
mAbbrev = inAbbrev;
mPow = inPow;
mConversionToBaseSIUnit = inConversionToBaseSIUnit;
addToMaps(this);
}
//---------------------------------------------------------------------------
protected Unit(MeasurementSystem inSystem, QuantityType inQuantityType, String inName, String inAbbrev, Integer inPow, BaseSIUnitConverter inConversionToBaseSIUnit, SubUnit[] inSubUnits)
{
mMeasurementSystem = inSystem;
mQuantityType = inQuantityType;
mName = inName;
mAbbrev = inAbbrev;
mPow = inPow;
mConversionToBaseSIUnit = inConversionToBaseSIUnit;
if (inSubUnits != null
&& inSubUnits.length > 0)
{
mSubUnits = new ArrayList<>(Arrays.asList(inSubUnits));
}
addToMaps(this);
}
//---------------------------------------------------------------------------
protected Unit(MeasurementSystem inSystem, QuantityType inQuantityType, SubUnit inSubUnit)
{
mMeasurementSystem = inSystem;
mQuantityType = inQuantityType;
mSubUnits = new ArrayList<>(1);
mSubUnits.add(inSubUnit);
addToMaps(this);
}
//---------------------------------------------------------------------------
protected Unit(Unit inSubUnit, SI_ScalingFactor inScalingFactor)
{
this(new SubUnit(inSubUnit, inScalingFactor));
}
//---------------------------------------------------------------------------
protected Unit(SubUnit... inSubUnits)
{
if (null == inSubUnits
|| 0 == inSubUnits.length)
{
throw new RuntimeException("No SubUnits specified!");
}
initFromSubUnits(Arrays.asList(inSubUnits));
}
//---------------------------------------------------------------------------
protected Unit(List inSubUnits)
{
initFromSubUnits(inSubUnits);
}
//---------------------------------------------------------------------------
protected Unit(MeasurementSystem inSystem, QuantityType inQuantityType, String inName, String inAbbrev, List inSubUnits)
{
mMeasurementSystem = inSystem;
mQuantityType = inQuantityType;
mName = inName;
mAbbrev = inAbbrev;
mSubUnits = inSubUnits;
addToMaps(this);
}
//---------------------------------------------------------------------------
protected Unit(MeasurementSystem inSystem, QuantityType inQuantityType, String inName, String inAbbrev, SubUnit... inSubUnits)
{
mMeasurementSystem = inSystem;
mQuantityType = inQuantityType;
mName = inName;
mAbbrev = inAbbrev;
if (inSubUnits != null
&& inSubUnits.length > 0)
{
mSubUnits = new ArrayList<>(Arrays.asList(inSubUnits));
}
addToMaps(this);
}
//---------------------------------------------------------------------------
private void initFromSubUnits(List inSubUnits)
{
mSubUnits = inSubUnits;
mMeasurementSystem = mSubUnits.get(0).getUnit().getMeasurementSystem();
// Add to the lookup map but not to the list of instances since these are not "official"
String name = name();
if (! sLookupMap.containsKey(name) // Don't override an existing mapping
&& (getSubUnits().size() > 1
|| null == getSubUnits().get(0).getScalingFactor())) // Don't add scaled simple base units
{
sLookupMap.put(name, this);
}
}
//---------------------------------------------------------------------------
private void addToMaps(Unit inValue)
{
sInstances.add(inValue);
sLookupMap.put(inValue.name(), inValue);
}
//###########################################################################
// PUBLIC METHODS
//###########################################################################
//---------------------------------------------------------------------------
/**
Sets the default MathContext to specify precision and rounding during operations.
* @param inValue the MathContext to use
*/
public static void setDefaultMathContext(MathContext inValue)
{
sDefaultMathContext = inValue;
}
//---------------------------------------------------------------------------
/**
Sets the MathContext to specify precision and rounding during operations with this Unit.
* @param inValue the MathContext to use
*/
public Unit setMathContext(MathContext inValue)
{
mMathContext = inValue;
return this;
}
//---------------------------------------------------------------------------
public MathContext getMathContext()
{
return mMathContext != null ? mMathContext : sDefaultMathContext;
}
//---------------------------------------------------------------------------
public static Collection extends Unit> values()
{
ensureExtendingClassesAreInitialized();
return Collections.unmodifiableCollection(sInstances);
}
//---------------------------------------------------------------------------
public static Unit valueOf(SubUnit inSubUnit)
{
List subUnits = new ArrayList<>(1);
subUnits.add(inSubUnit);
return valueOf(subUnits);
}
//---------------------------------------------------------------------------
public static Unit valueOf(List inSubUnits)
{
Unit unit = null;
if (CollectionUtil.hasValues(inSubUnits))
{
for (Unit existingUnit : sInstances)
{
if (0 == existingUnit.compareTo(inSubUnits))
{
unit = existingUnit;
break;
}
}
if (null == unit)
{
unit = new Unit(inSubUnits);
}
}
return unit;
}
//---------------------------------------------------------------------------
public static Unit valueOf(String inString)
{
ensureExtendingClassesAreInitialized();
Unit unit = null;
if (StringUtil.isSet(inString))
{
String string = inString.trim();
List subUnits = new ArrayList<>(5);
if (string.contains("/"))
{
String[] pieces = string.split("/");
if (pieces.length > 2)
{
throw new UnitParseException("Multiple '/' operators in a unit string is ambiguous!");
}
// Special nasty case: '% v/v'
if (string.equalsIgnoreCase(ConcentrationUnit.pct_by_volume.getAbbrev())
|| ConcentrationUnit.pct_by_volume.getAlternateNames().contains(string))
{
subUnits.addAll(ConcentrationUnit.pct_by_volume.getSubUnits());
} // Special nasty case: '% w/w'
else if (string.equalsIgnoreCase(ConcentrationUnit.pct_by_weight.getAbbrev())
|| ConcentrationUnit.pct_by_weight.getAlternateNames().contains(string))
{
subUnits.addAll(ConcentrationUnit.pct_by_weight.getSubUnits());
}
else
{
// Numerator
subUnits.addAll(parseNumeratorBlockOfUnits(pieces[0].trim()));
// Denominator
subUnits.addAll(parseDenominatorBlockOfUnits(pieces[1].trim()));
}
}
else
{
subUnits.addAll(parseNumeratorBlockOfUnits(string));
}
// Does the configuration of subunits match a defined unit?
for (Unit stdUnit : sInstances)
{
if (stdUnit.hasSubUnits()
&& 0 == CompareUtil.compare(stdUnit.getSubUnits(), subUnits))
{
unit = stdUnit;
break;
}
}
if (null == unit
&& 1 == subUnits.size())
{
SubUnit subUnit = subUnits.get(0);
for (Unit stdUnit : sInstances)
{
if (stdUnit.equals(subUnit.getUnit())
&& null == subUnit.getPow()
&& null == subUnit.getScalingFactor())
{
unit = stdUnit;
break;
}
}
}
if (null == unit)
{
// If all the subunits are of the same type, use that type as the constructor.
Set quantityTypes = new HashSet<>(2);
for (SubUnit subUnit : subUnits)
{
quantityTypes.add(subUnit.getUnit().getQuantityType());
}
if (1 == quantityTypes.size())
{
QuantityType quantityType = quantityTypes.iterator().next();
if (quantityType.equals(QuantityType.TIME))
{
unit = new TimeUnit(subUnits);
}
else if (quantityType.equals(QuantityType.LENGTH))
{
unit = new LengthUnit(subUnits);
}
else if (quantityType.equals(QuantityType.MASS))
{
unit = new MassUnit(subUnits);
}
else if (quantityType.equals(QuantityType.VOLUME))
{
unit = new VolumeUnit(subUnits);
}
else if (quantityType.equals(QuantityType.CONCENTRATION))
{
unit = new ConcentrationUnit(subUnits);
}
else if (quantityType.equals(QuantityType.AREA))
{
unit = new AreaUnit(subUnits);
}
else if (quantityType.equals(QuantityType.BIOLOGICAL_POTENCY))
{
unit = new BiologicalPotencyUnit(subUnits);
}
else if (quantityType.equals(QuantityType.CATALYTIC_ACTIVITY))
{
unit = new CatalyticActivityUnit(subUnits);
}
else if (quantityType.equals(QuantityType.ELECTRIC_CURRENT))
{
unit = new ElectricCurrentUnit(subUnits);
}
}
// As a last resort just create a Unit from the SubUnits.
if (null == unit)
{
unit = new Unit(subUnits);
}
}
}
return unit;
}
//---------------------------------------------------------------------------
@Override
public String toString()
{
StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(" ");
if (StringUtil.isSet(getAbbrev()))
{
buffer.append(getAbbrev());
if (getPow() != null)
{
buffer.append(StringUtil.toSuperscript(getPow()));
}
}
else if (hasSubUnits())
{
for (SubUnit subUnit : getSubUnits())
{
if (subUnit.getPow() != null
&& subUnit.getPow().equals(-1)
&& 2 == getSubUnits().size()
&& 1 == getSubUnits().indexOf(subUnit)
&& (null == getSubUnits().get(0).getPow()
|| getSubUnits().get(0).getPow() > 0))
{
buffer.append("/");
if (subUnit.getScalingFactor() != null)
{
buffer.append(subUnit.getScalingFactor().getSymbol());
}
buffer.append(subUnit.toStringMinusPow());
}
else
{
buffer.delimitedAppend(subUnit);
}
}
}
else
{
throw new ProgrammingException("Unit with no abbreviation or SubUnits!");
}
return buffer.toString();
}
//---------------------------------------------------------------------------
@Override
public boolean equals(Object inObj2)
{
return (inObj2 != null
&& inObj2 instanceof Unit
&& (this == inObj2
|| 0 == compareTo(inObj2)));
}
//---------------------------------------------------------------------------
@Override
public int hashCode()
{
int hashCode = 0;
if (getMeasurementSystem() != null)
{
hashCode += 31 * getMeasurementSystem().hashCode();
}
if (getQuantityType() != null)
{
hashCode += 31 * getQuantityType().hashCode();
}
if (getPow() != null)
{
hashCode += 31 * getPow().hashCode();
}
if (hasSubUnits())
{
for (SubUnit subUnit : getSubUnits())
{
hashCode += 31 * subUnit.hashCode();
}
}
else
{
if (name() != null)
{
hashCode += 31 * name().hashCode();
}
}
return hashCode;
}
//---------------------------------------------------------------------------
public int compareTo(Object inObj2)
{
int result = -1;
if (inObj2 != null)
{
if (inObj2 instanceof Unit)
{
Unit unit2 = (Unit) inObj2;
result = CompareUtil.compare(getMeasurementSystem(), unit2.getMeasurementSystem());
/*
if (0 == result)
{
result = CompareUtil.compare(name(), unit2.name());
}
*/
if (0 == result)
{
result = CompareUtil.compare(getQuantityType(), unit2.getQuantityType());
}
if (0 == result)
{
result = CompareUtil.compare(getPow(), unit2.getPow());
}
if (0 == result)
{
if (hasSubUnits())
{
result = CompareUtil.compare(getSubUnits(), unit2.getSubUnits());
}
else
{
result = CompareUtil.compare(name(), unit2.name());
}
}
}
}
return result;
}
//---------------------------------------------------------------------------
public int compareTo(List inSubUnits)
{
int result = -1;
if (inSubUnits != null)
{
if (1 == inSubUnits.size())
{
// Does this unit equal the specified subunit?
SubUnit subUnit = inSubUnits.get(0);
result = CompareUtil.compare(this, subUnit.getUnit());
if (0 == result)
{
result = CompareUtil.compare(getPow(), subUnit.getPow());
}
if (0 == result
&& subUnit.getScalingFactor() != null)
{
result = -1;
}
}
if (result != 0
&& hasSubUnits())
{
result = CompareUtil.compare(getSubUnits(), inSubUnits);
if (0 == result)
{
if (getPow() != null)
{
result = 1;
}
}
}
}
return result;
}
//---------------------------------------------------------------------------
public String name()
{
if (null == mName
&& CollectionUtil.hasValues(mSubUnits))
{
StringBuilderPlus name = new StringBuilderPlus().setDelimiter(" ");
for (SubUnit subUnit : mSubUnits)
{
String subUnitName = subUnit.getUnit().name();
if (null == subUnitName)
{
name = null;
break;
}
if (subUnit.getScalingFactor() != null)
{
subUnitName = subUnit.getScalingFactor().getPrefix() + subUnitName;
}
if (subUnit.getPow() != null)
{
subUnitName += StringUtil.toSuperscript(subUnit.getPow());
}
name.delimitedAppend(subUnitName);
}
if (StringUtil.isSet(name))
{
mName = name.toString();
}
}
return mName;
}
//---------------------------------------------------------------------------
public String getAbbrev()
{
if (null == mAbbrev
&& CollectionUtil.hasValues(mSubUnits))
{
StringBuilderPlus abbrev = new StringBuilderPlus().setDelimiter(" ");
for (int i = 0; i < mSubUnits.size(); i++)
{
SubUnit subUnit = mSubUnits.get(i);
String subUnitAbbrev = subUnit.getUnit().getAbbrev();
if (null == subUnitAbbrev)
{
abbrev = null;
break;
}
String powString = "";
if (subUnit.getPow() != null)
{
if (1 == i
&& -1 == subUnit.getPow()
&& (null == mSubUnits.get(0).getPow()
|| mSubUnits.get(0).getPow() > 1))
{
abbrev.append("/");
}
else
{
if (i > 0)
{
abbrev.append(" ");
}
powString = StringUtil.toSuperscript(subUnit.getPow());
}
}
if (subUnit.getScalingFactor() != null)
{
subUnitAbbrev = subUnit.getScalingFactor().getSymbol() + subUnitAbbrev;
}
subUnitAbbrev += powString;
abbrev.append(subUnitAbbrev);
}
if (StringUtil.isSet(abbrev))
{
mAbbrev = abbrev.toString();
}
}
return mAbbrev;
}
//---------------------------------------------------------------------------
public Unit addAlternateName(String inValue)
{
if (null == mAlternateNames)
{
mAlternateNames = new HashSet<>(4);
}
sLookupMap.put(inValue, this);
return this;
}
//---------------------------------------------------------------------------
public Set getAlternateNames()
{
return mAlternateNames;
}
//---------------------------------------------------------------------------
public Unit setPlural(String inValue)
{
mPlural = inValue;
sLookupMap.put(mPlural, this);
return this;
}
//---------------------------------------------------------------------------
public String getPlural()
{
return mPlural;
}
//---------------------------------------------------------------------------
public Unit setPluralAbbrev(String inValue)
{
mPluralAbbrev = inValue;
sLookupMap.put(mPluralAbbrev, this);
return this;
}
//---------------------------------------------------------------------------
public String getPluralAbbrev()
{
return mPluralAbbrev;
}
//---------------------------------------------------------------------------
public Unit setMeasurementSystem(MeasurementSystem inValue)
{
mMeasurementSystem = inValue;
return this;
}
//---------------------------------------------------------------------------
public MeasurementSystem getMeasurementSystem()
{
return mMeasurementSystem;
}
//---------------------------------------------------------------------------
public QuantityType getQuantityType()
{
if (null == mQuantityType
&& CollectionUtil.hasValues(getSubUnits())
&& 1 == getSubUnits().size())
{
mQuantityType = getSubUnits().get(0).getUnit().getQuantityType();
}
return mQuantityType;
}
//---------------------------------------------------------------------------
public Integer getPow()
{
if (null == mPow
&& CollectionUtil.hasValues(getSubUnits())
&& 1 == getSubUnits().size())
{
mPow = getSubUnits().get(0).getUnit().getPow();
}
return mPow;
}
//---------------------------------------------------------------------------
public boolean hasSubUnits()
{
return CollectionUtil.hasValues(mSubUnits);
}
//---------------------------------------------------------------------------
public List getSubUnits()
{
return mSubUnits;
}
//---------------------------------------------------------------------------
public double computeBaseSIValue(double inValue)
{
double value = inValue;
if (mConversionToBaseSIUnit != null)
{
value = mConversionToBaseSIUnit.apply(value);
}
else if (hasSubUnits()
&& 1 == getSubUnits().size()
&& getSubUnits().get(0).getUnit().mConversionToBaseSIUnit != null)
{
// The unit has a converter but was wrapped by another unit
value = getSubUnits().get(0).getUnit().mConversionToBaseSIUnit.apply(value);
}
else if (getMeasurementSystem() != null
&& (getMeasurementSystem().equals(MeasurementSystem.Metric)
|| getMeasurementSystem().equals(MeasurementSystem.SI)))
{
// Put the value in SI base units by scaling
Map scalingFactorMap = new HashMap<>(3);
if (getQuantityType() != null
|| ! hasSubUnits())
{
scalingFactorMap.put(getQuantityType(), SI_ScalingFactor.one);
}
else
{
for (SubUnit subUnit : getSubUnits())
{
QuantityType quantityType = subUnit.getUnit().getQuantityType();
scalingFactorMap.put(quantityType, SI_ScalingFactor.one);
}
}
value = computeScaledValue(value, scalingFactorMap);
}
else
{
throw new UnitException("Couldn't convert " + name() + " to base SI units! No converter specified.");
}
return value;
}
//---------------------------------------------------------------------------
public double computeValueFromBaseSIValue(double inValue)
{
double value = inValue;
if (CollectionUtil.hasValues(getSubUnits()))
{
for (SubUnit subUnit : getSubUnits())
{
if (subUnit.getScalingFactor() != null)
{
if (subUnit.getPow() != null
&& subUnit.getPow() < 0)
{
value *= subUnit.getScalingFactor().getScalingFactor();
}
else
{
value = value / subUnit.getScalingFactor().getScalingFactor();
}
}
}
}
if (mConversionToBaseSIUnit != null)
{
value = mConversionToBaseSIUnit.reverse(value);
}
return value;
}
//---------------------------------------------------------------------------
public double computeScaledValue(double inValue, Map inScalingFactorMap)
{
BigDecimal value = allocateBigDecimal(inValue);
if (CollectionUtil.hasValues(getSubUnits()))
{
for (SubUnit subUnit : getSubUnits())
{
SI_ScalingFactor scalingFactor = inScalingFactorMap.get(subUnit.getUnit().getQuantityType());
if (scalingFactor != null)
{
BigDecimal adjustedScalingFactor = allocateBigDecimal(scalingFactor.getScalingFactor())
.multiply(allocateBigDecimal(subUnit.getScalingFactor() != null ? subUnit.getScalingFactor().getScalingFactor() : 1d));
if (subUnit.getPow() != null
&& subUnit.getPow() < 0)
{
value = value.divide(adjustedScalingFactor, getMathContext());
}
else
{
value = value.multiply(adjustedScalingFactor);
}
}
}
}
else
{
SI_ScalingFactor scalingFactor = inScalingFactorMap.get(getQuantityType());
if (scalingFactor != null)
{
if (getPow() != null
&& getPow() < 0)
{
value = value.multiply(allocateBigDecimal(scalingFactor.getScalingFactor()));
}
else
{
value = value.divide(allocateBigDecimal(scalingFactor.getScalingFactor()), getMathContext());
}
}
}
return value.doubleValue();
}
//---------------------------------------------------------------------------
public String computeUnitLabel(Map inScalingFactorMap, boolean isPlural)
{
StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(" ");
if (! CollectionUtil.hasValues(inScalingFactorMap)
&& StringUtil.isSet(getAbbrev()))
{
buffer.delimitedAppend(isPlural && StringUtil.isSet(getPluralAbbrev()) ? getPluralAbbrev() : getAbbrev());
}
else
// if (StringUtil.isSet(getAbbrev())
// || ! hasSubUnits())
if (! hasSubUnits()
// || (1 == getSubUnits().size()))
|| getQuantityType() != null)
{
String prefix = "";
if (CollectionUtil.hasValues(inScalingFactorMap))
{
SI_ScalingFactor scalingFactor = inScalingFactorMap.get(getQuantityType());
if (scalingFactor != null)
{
prefix = scalingFactor.getSymbol();
}
}
buffer.delimitedAppend(prefix + toString());
if (getPow() != null)
{
buffer.append(StringUtil.toSuperscript(getPow()));
}
}
else
{
for (SubUnit subUnit : getSubUnits())
{
String prefix = "";
boolean prefixApplied = false;
if (inScalingFactorMap != null)
{
SI_ScalingFactor scalingFactor = inScalingFactorMap.get(subUnit.getUnit().getQuantityType());
if (scalingFactor != null)
{
prefix = scalingFactor.getSymbol();
prefixApplied = true;
}
}
if (subUnit.getPow() != null
&& subUnit.getPow().equals(-1)
&& 2 == getSubUnits().size()
&& 1 == getSubUnits().indexOf(subUnit)
&& (null == getSubUnits().get(0).getPow()
|| getSubUnits().get(0).getPow() > 0))
{
buffer.append("/" + prefix);
if (! prefixApplied
&& subUnit.getScalingFactor() != null)
{
buffer.append(subUnit.getScalingFactor().getSymbol());
}
buffer.append(subUnit.getUnit().getAbbrev());
}
else
{
buffer.delimitedAppend(prefix);
if (! prefixApplied
&& subUnit.getScalingFactor() != null)
{
buffer.append(subUnit.getScalingFactor().getSymbol());
}
buffer.append(subUnit.getUnit().getAbbrev());
if (subUnit.getPow() != null)
{
buffer.append(StringUtil.toSuperscript(subUnit.getPow()));
}
}
}
}
return buffer.toString();
}
//---------------------------------------------------------------------------
public Unit getNumerator()
{
Unit numerator = null;
if (CollectionUtil.hasValues(getSubUnits()))
{
List numeratorSubUnits = new ArrayList<>(4);
for (SubUnit subUnit : getSubUnits())
{
if (null == subUnit.getPow()
|| subUnit.getPow() > 0)
{
if (subUnit.getUnit().hasSubUnits()
&& ! subUnit.getUnit().getQuantityType().equals(QuantityType.VOLUME)) // Don't unroll L to dm3
{
for (SubUnit subSubUnit : subUnit.getUnit().getSubUnits())
{
if (null == subSubUnit.getPow()
|| subSubUnit.getPow() > 0)
{
numeratorSubUnits.add(new SubUnit(subSubUnit.getUnit(), subUnit.getScalingFactor(), subUnit.getPow()));
}
}
}
else
{
numeratorSubUnits.add(subUnit);
}
}
}
if (CollectionUtil.hasValues(numeratorSubUnits))
{
numerator = Unit.valueOf(numeratorSubUnits);
}
}
else if (null == getPow()
|| getPow() > 0)
{
numerator = this;
}
return numerator;
}
//---------------------------------------------------------------------------
public Unit getDenominator()
{
Unit denominator = null;
if (CollectionUtil.hasValues(getSubUnits()))
{
List denominatorSubUnits = new ArrayList<>(4);
for (SubUnit subUnit : getSubUnits())
{
if (subUnit.getUnit().hasSubUnits()
&& subUnit.getUnit().getSubUnits().size() > 1) // or L will unroll into dm3
{
for (SubUnit subSubUnit : subUnit.getUnit().getSubUnits())
{
if (subSubUnit.getPow() != null
&& subSubUnit.getPow() < 0)
{
// Don't use the scaling factor of the parent subunit
denominatorSubUnits.add(new SubUnit(subSubUnit.getUnit(), subSubUnit.getScalingFactor(), subSubUnit.getPow().equals(-1) ? null : -subSubUnit.getPow()));
}
}
}
else if (subUnit.getPow() != null
&& subUnit.getPow() < 0)
{
denominatorSubUnits.add(new SubUnit(subUnit.getUnit(), subUnit.getScalingFactor(), subUnit.getPow().equals(-1) ? null : -subUnit.getPow()));
}
}
if (CollectionUtil.hasValues(denominatorSubUnits))
{
denominator = Unit.valueOf(denominatorSubUnits);
}
}
else if (getPow() != null
&& getPow() < 0)
{
if (getPow().equals(-1))
{
denominator = Unit.valueOf(getAbbrev());
}
else
{
denominator = Unit.valueOf(new SubUnit(this, -getPow()));
}
}
return denominator;
}
//###########################################################################
// PRIVATE METHODS
//###########################################################################
//---------------------------------------------------------------------------
private BigDecimal allocateBigDecimal(double inValue)
{
return new BigDecimal(Double.toString(inValue), getMathContext());
}
//---------------------------------------------------------------------------
// This is done to ensure that calls to values() and valueOf() have registered values to work with.
// The file /META-DATA/services/com.hfg.units.Unit holds the names of the classes to be initialized.
// (Had to create a custom ServiceLoader that doesn't try to instantiate the properties but just
// calls Class.forName() to initialize the static definitions.)
private static void ensureExtendingClassesAreInitialized()
{
if (! sInitialized)
{
try
{
new UnitServiceLoader().load();
}
catch (Exception e)
{
// Ignore
}
sInitialized = true;
}
}
//---------------------------------------------------------------------------
private static List parseNumeratorBlockOfUnits(String inString)
{
List subUnits = new ArrayList<>(4);
String[] pieces = inString.split("\\s+");
String unparsableBit = null;
for (String piece : pieces)
{
if (StringUtil.isSet(unparsableBit))
{
piece = unparsableBit + " " + piece;
}
SubUnit subUnit = parseSubUnit(piece);
if (null == subUnit)
{
// Not recognized? Perhaps it's part of a multi-word unit?
unparsableBit = piece;
}
else
{
unparsableBit = null;
// Special nasty case: 'fl oz' can get misinterpreted as femto liters and ounce.
if (subUnit.getUnit().equals(MassUnit.ounce)
&& subUnits.size() > 0
&& subUnits.get(subUnits.size() - 1).getUnit().equals(VolumeUnit.liter)
&& subUnits.get(subUnits.size() - 1).getScalingFactor().equals(SI_ScalingFactor.femto))
{
subUnits.remove(subUnits.size() - 1);
subUnit = new SubUnit(VolumeUnit.fluid_ounce, subUnit.getPow());
}
subUnits.add(subUnit);
}
}
if (StringUtil.isSet(unparsableBit))
{
// Special nasty case: "mole equivalent"
if (unparsableBit.equalsIgnoreCase("equivalent")
&& 1 == subUnits.size()
&& subUnits.get(0).equals(AmountOfSubstanceUnit.mole))
{
subUnits.set(0, new SubUnit(AmountOfSubstanceUnit.mole_equivalent));
}
// Special nasty case: "fluid dram". 'fl' can get misinterpreted as femto liters.
else if (unparsableBit.equalsIgnoreCase("dr")
&& 1 == subUnits.size()
&& subUnits.get(0).getUnit().equals(VolumeUnit.liter)
&& subUnits.get(0).getScalingFactor().equals(SI_ScalingFactor.femto))
{
subUnits.set(0, new SubUnit(VolumeUnit.fluid_dram));
}
else
{
throw new UnitParseException("Couldn't match " + StringUtil.singleQuote(unparsableBit) + " to a unit!");
}
}
return subUnits;
}
//---------------------------------------------------------------------------
private static List parseDenominatorBlockOfUnits(String inString)
{
List subUnits = new ArrayList<>(4);
String[] pieces = inString.split("\\s+");
String unparsableBit = null;
for (String piece : pieces)
{
if (StringUtil.isSet(unparsableBit))
{
piece = unparsableBit + " " + piece;
}
SubUnit subUnit = parseSubUnit(piece);
if (null == subUnit)
{
// Not recognized? Perhaps it's part of a multi-word unit?
unparsableBit = piece;
}
else
{
unparsableBit = null;
// Special nasty case: 'fl oz' can get misinterpreted as femto liters and ounce.
if (subUnit.getUnit().equals(MassUnit.ounce)
&& subUnits.size() > 0
&& subUnits.get(subUnits.size() - 1).getUnit().equals(VolumeUnit.liter)
&& subUnits.get(subUnits.size() - 1).getScalingFactor().equals(SI_ScalingFactor.femto))
{
subUnits.remove(subUnits.size() - 1);
subUnit = new SubUnit(VolumeUnit.fluid_ounce, subUnit.getPow());
}
Integer pow = subUnit.getPow();
if (pow != null)
{
pow = - pow;
}
else
{
pow = -1;
}
subUnits.add(new SubUnit(subUnit.getUnit(), subUnit.getScalingFactor(), pow));
}
}
if (StringUtil.isSet(unparsableBit))
{
throw new UnitParseException("Couldn't match " + StringUtil.singleQuote(unparsableBit) + " to a unit!");
}
return subUnits;
}
//---------------------------------------------------------------------------
private static SubUnit parseSubUnit(String inString)
{
SubUnit subUnit = null;
String string = inString;
Integer pow = extractPow(string);
Unit unit = null;
SI_ScalingFactor scalingFactor = null;
if (pow != null)
{
// Remove the pow from the end of the string
string = string.substring(0, string.length() - pow.toString().length());
}
// Attempt to match using unit abbreviations
for (Unit unitValue : sInstances)
{
if (StringUtil.isSet(unitValue.getAbbrev())
&& string.endsWith(unitValue.getAbbrev()))
{
// Is there a prefix?
int endIndex = string.length() - unitValue.getAbbrev().length();
if (endIndex > 0)
{
if (unitValue.getMeasurementSystem().equals(MeasurementSystem.SI)
|| unitValue.getMeasurementSystem().equals(MeasurementSystem.Metric))
{
String prefix = string.substring(0, endIndex);
SI_ScalingFactor matchingScalingFactor = SI_ScalingFactor.valueOf(prefix);
if (matchingScalingFactor != null)
{
// We found a match
unit = unitValue;
scalingFactor = matchingScalingFactor;
break;
}
}
}
else
{
// We found a match that has no scaling factor
unit = unitValue;
break;
}
}
}
if (null == unit)
{
// Attempt to match using unit names
for (String key : sLookupMap.keySet())
{
if (StringUtil.isSet(key))
{
// Does the string end with the unit name?
if (Pattern.compile(key + "$", Pattern.CASE_INSENSITIVE).matcher(string).find())
{
Unit unitValue = sLookupMap.get(key);
// Is there a prefix?
int endIndex = string.length() - key.length();
if (endIndex > 0)
{
String prefix = string.substring(0, endIndex);
SI_ScalingFactor matchingScalingFactor = SI_ScalingFactor.valueOf(prefix);
if (matchingScalingFactor != null)
{
// We found a match
unit = unitValue;
scalingFactor = matchingScalingFactor;
break;
}
}
else
{
// We found a match that has no scaling factor
unit = unitValue;
break;
}
}
}
}
}
if (null == unit
&& inString.endsWith("s"))
{
// The units may have been pluralized
subUnit = parseSubUnit(inString.substring(0, inString.length() - 1));
}
if (null == unit
&& inString.endsWith("."))
{
// The units may have had a period at the end of its abbreviation
subUnit = parseSubUnit(inString.substring(0, inString.length() - 1));
}
return (subUnit != null ? subUnit : unit != null ? new SubUnit(unit, scalingFactor, pow) : null);
}
//---------------------------------------------------------------------------
private static Integer extractPow(String inString)
{
StringBuilder buffer = new StringBuilder();
for (int i = inString.length() - 1; i > 0; i--)
{
Character superscriptChar = null;
switch (inString.charAt(i))
{
case '\u207B':
superscriptChar = '-';
break;
case '\u2070':
superscriptChar = '0';
break;
case 0xB9:
superscriptChar = '1';
break;
case 0xB2:
superscriptChar = '2';
break;
case 0xB3:
superscriptChar = '3';
break;
case '\u2074':
superscriptChar = '4';
break;
case '\u2075':
superscriptChar = '5';
break;
case '\u2076':
superscriptChar = '6';
break;
case '\u2077':
superscriptChar = '7';
break;
case '\u2078':
superscriptChar = '8';
break;
case '\u2079':
superscriptChar = '9';
break;
}
if (superscriptChar != null)
{
buffer.insert(0, superscriptChar);
}
else
{
break;
}
}
return (buffer.length() > 0 ? Integer.parseInt(buffer.toString()) : null);
}
}