com.adobe.xfa.ut.UnitSpan Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* ADOBE CONFIDENTIAL
*
* Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of
* Adobe Systems Incorporated and its suppliers, if any. The intellectual and
* technical concepts contained herein are proprietary to Adobe Systems
* Incorporated and its suppliers and may be covered by U.S. and Foreign
* Patents, patents in process, and are protected by trade secret or copyright
* law. Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained from
* Adobe Systems Incorporated.
*/
package com.adobe.xfa.ut;
/**
* A class to describe a unit span. It consists of a value with a
* units specification. Unit spans of unknown units are valid.
*
* Instances of this class are immutable. All change operations
* return a new instance of this UnitSpan
class.
*/
public final class UnitSpan implements Comparable {
/**
* A class returned by the validatingParse() method.
* @exclude from published api.
*/
public static class ParseData {
public ParseData(
int nValue,
int nFraction,
int nFractionScale,
int eUnits,
char cUnit0,
char cUnit1,
char cUnit2,
boolean bValuePerUnit,
boolean bPercent) {
mnValue = nValue;
mnFraction = nFraction;
mnFractionScale = nFractionScale;
meUnits = eUnits;
mcUnit0 = cUnit0;
mcUnit1 = cUnit1;
mcUnit2 = cUnit2;
mbValuePerUnit = bValuePerUnit;
mbPercent = bPercent;
}
/**
* Non-fractional or entire value.
* @exclude from published api.
*/
public final int mnValue;
/**
* Fraction part of value.
* @exclude from published api.
*/
public final int mnFraction;
/**
* Amount to divide fraction part by.
* @exclude from published api.
*/
public final int mnFractionScale;
/**
* Units parsed or defaulted.
* @exclude from published api.
*/
public final int meUnits;
/**
* Unit text.
* Valid units are always two characters, so in order to be valid,
* mcUnit0 and mcUnit1 must be non-zero, and mcUnit2 must be zero.
* @exclude from published api.
*/
public final char mcUnit0;
public final char mcUnit1;
public final char mcUnit2;
/**
* Should the result be interpreted as value per unit?
* @exclude from published api.
*/
public final boolean mbValuePerUnit;
/**
* Was the unit expressed as a percent?
* @exclude from published api.
*/
public final boolean mbPercent;
}
private static class UnitMapEntry {
final String msLower; // lower case unit string
final String msUpper; // upper case unit string
final int meUnits; // unit code
final int mnUnitScale; // unit conversion scale
UnitMapEntry(String lower, String upper, int units, int unitScale) {
msLower = lower;
msUpper = upper;
meUnits = units;
mnUnitScale = unitScale;
}
}
private static final UnitMapEntry gUnitMap[] = {
new UnitMapEntry("pt", "PT", UnitSpan.POINTS_1K, 1000),
new UnitMapEntry("mm", "MM", UnitSpan.MM_25K, 25000),
new UnitMapEntry("in", "IN", UnitSpan.INCHES_72K, 72000),
new UnitMapEntry("cm", "CM", UnitSpan.CM_250K, 250000),
new UnitMapEntry("mp", "MP", UnitSpan.MILLIPOINT, 1),
new UnitMapEntry("pc", "PC", UnitSpan.PICAS_12K, 12000)
};
static final private String gsCm = "cm, centimeters";
static final private String gsInch = "in, inches";
static final private String gsMm = "mm, millimeters";
static final private String gsMp = "mp, millipoints";
static final private String gsPt = "pt, points";
static final private String gsPica = "pc, picas";
static final private String gsNumeric = "-0123456789.";
private enum State {
PreSign, // before sign
PreNumber, // sign seen
WholePart, // in digits before decimal
FractionStart, // decimal seen with no whole-number digits
FractionPart, // in decimal part
PostNumber, // after last digit
Units, // in unit sequence
PostValue // after units
}
/*
* Enumeration for unit type codes:
*
* Note: the code is made up of two parts. The low four bits represent a
* unique resolution, while the high four bits represent a variation. If two
* values have the same low four bits, they don't require conversion,
* regardless of the high four bits. The high four bits simply control text
* converson.
*/
/**
* Unit code for 1,000,000 units per inch.
*/
public static final int INCHES_1M = 0x00;
/**
* Unit code for 250,000 units per cm.
*/
public static final int CM_250K = 0x01;
/**
* Unit code for 72,000 units per inch.
*/
public static final int INCHES_72K = 0x03;
/**
* Unit code for 25,000 units per mm.
*/
public static final int MM_25K = 0x11;
/**
* Unit code for 1,000 units per point (72,000 units per inch).
*/
public static final int POINTS_1K = 0X13;
/**
* Unit code for 1,000 units per point (72,000 units per inch).
* @deprecated Unit deprecated in favour of {@link #POINTS_1K}.
*/
public static final int PICA_PT_1K = POINTS_1K;
/**
* Unit code for 1,000 units per point (72,000 units per inch).
*/
public static final int MILLIPOINT = 0x23;
/**
* Unit code for 10,000 units per point.
* @deprecated Unit deprecated. The unit is not accurate and does not actually
* handle picas.
*/
public static final int PICA_PT_10K = 0x02;
/**
* Unit code for 12,000 units per pica (72,000 units per inch).
*/
public static final int PICAS_12K = 0x33;
/**
* @exclude from published api.
*/
public static final int UNIT_MASK = 0x0F;
/**
* Unit code for units unknown.
*/
public static final int UNIT_UNKNOWN = 0xFF;
/**
* @exclude from published api.
*/
public static final int UNITS_CM_250K = 635000; // 250000.0 * 2.54
/**
* @exclude from published api.
*/
public static final int UNITS_INCHES_1M = 1000000;
/**
* @exclude from published api.
*/
public static final int UNITS_INCHES_72K = 72000;
/**
* @exclude from published api.
*/
public static final int UNITS_MILLIPOINT = 72000;
/**
* @exclude from published api.
*/
public static final int UNITS_MM_25K = 635000; // 250000.0 * 2.54
/**
* @exclude from published api.
*/
public static final int UNITS_POINTS_1K = 72000;
/**
* @exclude from published api.
*/
public static final int UNITS_PICAS_12K = 72000;
/** @exclude from published api */
static public final UnitSpan ZERO = new UnitSpan();
/**
* Applies a factor to a long integer, applying rounding to the result.
*
* @param nValue
* the input value to apply a factor to.
* @param nNumerator
* the portion of the factor to multiply by.
* @param nDenominator
* the portion of the factor to divide by.
* @return
* the rounder result of applying a factor to an input.
*/
static int applyFactor(int nValue, int nNumerator, int nDenominator) {
// We need to apply the factor very carefully in order to avoid
// overflowing
// our integers.
// If we Multiply by the numerator, then divide by the denominator, we
// risk
// overflowing our number.
// if we Divide by the denominator then multiply by the
// numerator we lose too much precision.
// So...
// Break up our number into two portions. The largest amount that we can
// divide into evenly, and the remainder.
int nRemainder = nValue % nDenominator;
// lValue-lRemainder is guaranteed to be evenly divisible by the
// denominator, so we can divide first without losing precision.
if (nValue > 0) {
return (nValue - nRemainder) / nDenominator * nNumerator +
// lRemainder should be small enough number so we can safely
// multiply
// by the numerator first.
// Multiply/divide by 10 so we can round...
(10 * nRemainder * nNumerator / nDenominator + 5) / 10;
}
return (nValue - nRemainder) / nDenominator * nNumerator
+ (10 * nRemainder * nNumerator / nDenominator - 5) / 10;
}
/**
* Returns a UnitSpan
representing the
* change of units of this object to the given unit code.
*
* @param eUnits
* the new unit code.
* @return
* the unit span of the changed units
*
* @exclude from published api.
*/
public UnitSpan changeUnits(int eUnits) {
return new UnitSpan(eUnits, valueAsUnit(eUnits));
}
/*
* Converts a value from one unit to another - equivalent value in new * units is returned.
*/
static int convertReally(int nTo, int nFrom, int nValue) {
int nUnitsTo = nTo & UNIT_MASK;
int nUnitsFrom = nFrom & UNIT_MASK;
// Note: INCHES_72K, POINTS_1K, MILLIPOINT and PICAS_12K are effectively the same.
if (nUnitsTo == nUnitsFrom)
return nValue;
// Conversions between units involve multiplying by a certain factor.
// In order to process using integer arithmetic, we express that factor
// as a fraction, rather than a floating point number.
int nNumerator = 1;
int nDenominator = 1;
switch (nUnitsTo) {
case INCHES_1M:
switch (nUnitsFrom) {
case CM_250K:
/*
* x y / 2.54 ------- = --------- 250,000 1,000,000
*
* x = y*200 / 127
*
*/
nNumerator = 200;
nDenominator = 127;
break;
case PICA_PT_10K:
/*
* x y -------- = ------- 1000000 720000
*
* x = 25y/18;
*/
nNumerator = 25;
nDenominator = 18;
break;
case INCHES_72K:
/*
* x y -------- = ------- 1000000 72000
*
* x = 125y/9;
*
*/
nNumerator = 125;
nDenominator = 9;
break;
}
break;
case CM_250K:
switch (nUnitsFrom) {
case INCHES_1M:
nNumerator = 127;
nDenominator = 200;
break;
case PICA_PT_10K:
nNumerator = 127;
nDenominator = 144;
break;
case INCHES_72K:
/*
* x y*2.54 -------- = -------- 250000 72000
*
* x = 635y/72;
*
*/
nNumerator = 635;
nDenominator = 72;
break;
}
break;
case PICA_PT_10K:
switch (nUnitsFrom) {
case INCHES_1M:
nNumerator = 18;
nDenominator = 25;
break;
case CM_250K:
nNumerator = 144;
nDenominator = 127;
break;
case INCHES_72K:
nNumerator = 10;
nDenominator = 1;
break;
}
break;
case INCHES_72K: // also POINTS_1K, MILLIPOINT and PICAS_12K
switch (nUnitsFrom) {
case INCHES_1M:
nNumerator = 9;
nDenominator = 125;
break;
case CM_250K:
nNumerator = 72;
nDenominator = 635;
break;
case PICA_PT_10K:
nNumerator = 1;
nDenominator = 10;
break;
}
break;
default:
throw new ExFull(new MsgFormat(ResId.ERR_UNITSPAN_UNITS, Integer.toString(nTo)));
}
return applyFactor(nValue, nNumerator, nDenominator);
}
/**
* @exclude from published api.
*/
public static int convertUnit(int eNewUnits, int eOldUnits, int nValue) {
if ((eNewUnits & UNIT_MASK) == (eOldUnits & UNIT_MASK))
return nValue;
else
return convertReally(eNewUnits, eOldUnits, nValue);
}
/**
* Gets the default unit code for all UnitSpans
.
*
* @return
* the default unit code, which is INCHES_72K
.
*/
public static int defaultUnits() {
return INCHES_72K;
}
/**
* @exclude from published api.
*/
public static String measurementValidate(String sText, boolean negValid) {
// extract the number portion from the string.
int nNumStart = StringUtils.skipUntil(sText, gsNumeric, 0);
int nLength = StringUtils.skipOver(sText, gsNumeric, nNumStart);
// extract the unit portion from the string and set the unit.
int nUnitStart = nLength == 0 ? 0 : nNumStart + nLength;
if (nNumStart > 0) {
return new UnitSpan(sText).text(8, false, false);
}
int MAXUNITCHARS = 6;
char cUnits[] = new char[MAXUNITCHARS + 1];
int nUnitPos = 0;
boolean bUnitsStarted = false;
// Collect the first few characters from our unit.
// 5 characters is enough to disambiguate units, but not enough to
// provide validation...
int strLen = sText.length();
for (int n = nUnitStart; n < strLen;) {
char c = sText.charAt(n);
if (c == ' ' || c == '.' || c == '\0') {
if (bUnitsStarted || c == '\0') {
break;
}
} else {
cUnits[nUnitPos++] = c;
bUnitsStarted = true;
}
n++;
if (nUnitPos == MAXUNITCHARS) {
cUnits[nUnitPos] = '\0';
break;
}
}
bUnitsStarted = false;
if (nUnitPos > 1) {
if (cUnits[0] == 'i' || cUnits[0] == 'I') {
// in inches
bUnitsStarted = true;
}
else if (cUnits[0] == 'm' || cUnits[0] == 'M') {
// assume mm or millimeters
bUnitsStarted = true;
// Check for mp
if (cUnits[1] == 'p' || cUnits[1] == 'P')
bUnitsStarted = true;
// Check for millipoints
else if ((nUnitPos > 5)
&& (cUnits[5] == 'p' || cUnits[5] == 'P')) {
bUnitsStarted = true;
}
}
else if (cUnits[0] == 'c' || cUnits[0] == 'C') {
// cm centimeters
bUnitsStarted = true;
}
else if (cUnits[0] == 'p' || cUnits[0] == 'P') {
// pt points picas
bUnitsStarted = true;
}
}
if (! bUnitsStarted) {
return new UnitSpan(sText).text(8, false, false);
}
int nDecimals = 0; // Number of places after the decimal
for (int i = nNumStart; i != nNumStart + nLength; i++) {
char c = sText.charAt(i);
if (c == '-') {
if (! negValid) {
return UnitSpan.ZERO.text(0, false, false);
} else if (i != nNumStart) {
return new UnitSpan(sText).text(0, false, false);
}
}
else if (c == '.') {
if (nDecimals > 1) {
return new UnitSpan(sText).text(0, false, false);
}
// From now on we're working on fractional digits.
nDecimals++;
}
}
return sText;
}
/**
* Converts the given unit string to a unit code. If the string is not
* recognized as a unit string, a default may be specified.
*
* @param sUnit
* the string to be converted.
* @param eDefaultUnits
* the default unit code.
* @return
* the converted unit code.
*/
public static int stringToUnit(String sUnit, int eDefaultUnits/* = INCHES_72K */) {
String sUnitText;
if (!StringUtils.isEmpty(sUnit)) {
sUnitText = unitToString(INCHES_72K);
if (StringUtils.findNoCase(sUnitText, sUnit, 0) != -1)
return INCHES_72K;
sUnitText = unitToString(CM_250K);
if (StringUtils.findNoCase(sUnitText, sUnit, 0) != -1)
return CM_250K;
sUnitText = unitToString(MM_25K);
if (StringUtils.findNoCase(sUnitText, sUnit, 0) != -1)
return MM_25K;
sUnitText = unitToString(POINTS_1K);
if (StringUtils.findNoCase(sUnitText, sUnit, 0) != -1)
return POINTS_1K;
sUnitText = unitToString(MILLIPOINT);
if (StringUtils.findNoCase(sUnitText, sUnit, 0) != -1)
return MILLIPOINT;
sUnitText = unitToString(PICAS_12K);
if (StringUtils.findNoCase(sUnitText, sUnit, 0) != -1)
return PICAS_12K;
}
return (eDefaultUnits != UNIT_UNKNOWN) ? eDefaultUnits : defaultUnits();
}
/**
* Gets the units per inch for the given unit code.
*
* @return
* the units per inch for the unit code.
*/
public static int unitsPerInch(int eUnit) {
switch (eUnit) {
case INCHES_1M:
return UNITS_INCHES_1M;
case CM_250K:
case MM_25K:
return UNITS_CM_250K;
case INCHES_72K:
return UNITS_INCHES_72K;
case POINTS_1K:
return UNITS_POINTS_1K;
case MILLIPOINT:
return UNITS_MILLIPOINT;
case PICAS_12K:
return UNITS_PICAS_12K;
}
throw new ExFull(new MsgFormat(ResId.ERR_UNITSPAN_UNITS, Integer.toString(eUnit)));
}
/**
* Converts the given unit code to a string.
*
* @param eUnit
* the unit code to be converted.
* @return
* the string version of the unit code.
*/
public static String unitToString(int eUnit) {
switch (eUnit) {
case INCHES_1M:
case INCHES_72K:
return gsInch;
case CM_250K:
return gsCm;
case MM_25K:
return gsMm;
case POINTS_1K:
return gsPt;
case MILLIPOINT:
return gsMp;
case PICAS_12K:
return gsPica;
default:
throw new ExFull(new MsgFormat(ResId.ERR_UNITSPAN_UNITS,
Integer.toString(eUnit)));
}
}
/**
* Converts a unit measure to a unit value; e.g. 3in ⇒ 3,000,000.
* @exclude from published api.
*/
public static int unitToValue(double dValue, int eUnit) {
switch (eUnit) {
case INCHES_1M:
return (int) Math.round(dValue * 1000000.0d);
case CM_250K:
return (int) Math.round(dValue * 250000.0d);
case MM_25K:
return (int) Math.round(dValue * 25000.0d);
case INCHES_72K:
return (int) Math.round(dValue * 72000.0d);
case POINTS_1K:
return (int) Math.round(dValue * 1000.0d);
case MILLIPOINT:
return (int) Math.round(dValue);
case PICAS_12K:
return (int) Math.round(dValue * 12000.0);
}
throw new ExFull(new MsgFormat(ResId.ERR_UNITSPAN_UNITS,
Integer .toString(eUnit)));
}
/**
* Converts the given value to the given unit code.
*
* @param nValue
* the unit value to be converted.
* @param eUnit
* the unit code to be converted to.
* @return
* the converted value.
*
* @exclude from published api.
*/
public static double valueToUnit(int nValue, int eUnit) {
switch (eUnit) {
case INCHES_1M:
return nValue / 1000000.0d;
case CM_250K:
return (double) nValue / 250000.0d;
case MM_25K:
return (double) nValue / 25000.0d;
case INCHES_72K:
return (double) nValue / 72000.0d;
case POINTS_1K:
return (double) nValue / 1000.0d;
case MILLIPOINT:
return (double) nValue;
case PICAS_12K:
return (double) nValue / 12000.0d;
}
throw new ExFull(new MsgFormat(ResId.ERR_UNITSPAN_UNITS, Integer.toString(eUnit)));
}
/**
* The zero unit span.
*
* @return
* the unit span equal to zero.
*/
public static UnitSpan zero() {
return ZERO;
}
// These fields that define the value of a UnitSpan are conceptually final,
// but are not marked as such because of the way that the constructors
// are written.
private final int meUnit;
private final int mnValue;
/**
* Instantiates a UnitSpan
with the value 0 and
* default units INCHES_72K
.
*
* @see #zero()
*/
public UnitSpan() {
meUnit = defaultUnits();
mnValue = 0;
}
/**
* Instantiates a UnitSpan
with the given double value and units.
*
* @param dValue
* the value of the unit span.
* @param eUnits
* the unit code of the unit span.
*/
public UnitSpan(double dValue, int eUnits) {
meUnit = eUnits;
mnValue = unitToValue(dValue, eUnits);
}
/**
* Instantiates a UnitSpan
with the given int value and unknown units.
* The unit code will be UNIT_UNKNOWN
.
*
* @param nValue
* the value of the unit span.
*/
public UnitSpan(int nValue) {
meUnit = UNIT_UNKNOWN;
mnValue = nValue;
}
/**
* Instantiates a UnitSpan
with the given units and optional
* value.
*
* @param eUnits
* the unit code of the unit span.
* @param nValue
* the value of the unit span.
*
* @exclude from published api.
*/
public UnitSpan(int eUnits, int nValue/* = 0 */) {
meUnit = eUnits;
mnValue = nValue;
}
/**
* Instantiates a UnitSpan
with the given units and a value
* equal to the given value after it has been converted from it's old units
* to the new units.
*
* @param eNewUnits
* the unit code of the unit span to which the value will
* be converted.
* @param eOldUnits
* the unit code from which the value will be converted.
* @param nOldValue
* the value to be converted and set as this object's value.
*/
public UnitSpan(int eNewUnits, int eOldUnits, int nOldValue) {
meUnit = eNewUnits;
mnValue = convertUnit(eNewUnits, eOldUnits, nOldValue);
}
/**
* Instantiates a UnitSpan
with a value and unit code parsed
* from the given text.
*
* @param sText
* text containing a numeric value possibly followed by a unit
* string of "in", "inches", "cm", "centimeters", "pt", "points",
* "picas", "mm", "millimeters", "mp", or "millipoints".
* If no unit is specified, this object's unit will
* default to UNIT_UNKNOWN
.
*/
public UnitSpan(String sText) {
this(sText, UNIT_UNKNOWN, false);
}
/**
* Instantiates a UnitSpan
with a value and unit code parsed
* from the given text.
*
* @param sText
* text containing a numeric value possibly followed by a unit
* string of "in", "inches", "cm", "centimeters", "pt", "points",
* "picas", "mm", "millimeters", "mp" or "millipoints".
* @param eDefaultUnits
* unit to use if the string contains no unit specification.
* @param bDefaultValuePerUnit
* a default interpretation of the value if no unit string is
* found.
*
* @exclude from published api.
*/
public UnitSpan(String sText,
int eDefaultUnits,
boolean bDefaultValuePerUnit/* = false */) {
// The body of this constructor would be in jfUnitSpan::SetFromText in C++,
// but it is moved inline here to allow meUnit and mlValue to be made final.
// First try parsing with strict validation. If that succeeds, run with
// the results.
ParseData oParseData = validatingParse (sText, eDefaultUnits, true, bDefaultValuePerUnit, false);
if (oParseData != null) {
if (oParseData.meUnits != UNIT_UNKNOWN) {
meUnit = oParseData.meUnits;
mnValue = oParseData.mnValue;
return;
}
}
// Could not be validated: re-parse with the more relaxed old-style
// algorithm. This code may be removed once we get a handle on how
// pervasive syntax errors are in unit span values.
// Start by extracting the numeric portion and the unit portion
// into separate strings
// extract the number portion from the string.
int nNumStart = StringUtils.skipUntil(sText, gsNumeric, 0);
int nLength = StringUtils.skipOver(sText, gsNumeric, nNumStart);
// extract the unit portion from the string and set the unit.
int nUnitStart = nLength == 0 ? 0 : nNumStart + nLength;
int nUnitPos = 0;
boolean bUnitsStarted = false;
boolean bSlashFound = false;
int eUnits = (eDefaultUnits != UNIT_UNKNOWN) ? eDefaultUnits : defaultUnits();
boolean bDefaultUnitsUsed = true;
boolean bValuePerUnit = bDefaultValuePerUnit;
// Examine the first few characters from our unit.
// 5 characters is enough to disambiguate units, but not enough to
// provide validation...
char c1 = 0;
char c2 = 0;
char c5 = 0;
for (int n = nUnitStart; n < sText.length();) {
char c = sText.charAt(n);
if (c == ' ' || c == '.' || c == '\0') {
if (bUnitsStarted || c == '\0') {
break;
}
}
else if (c == '/') {
bSlashFound = true;
}
else {
if (nUnitPos == 0) {
c1 = c;
}
else if (nUnitPos == 1) {
c2 = c;
} else if (nUnitPos == 5) {
c5 = c;
}
nUnitPos++;
bUnitsStarted = true;
}
n++;
}
if (nUnitPos > 1) {
bDefaultUnitsUsed = false;
if (c1 == 'i' || c1 == 'I') {
// in inches
eUnits = INCHES_72K;
bValuePerUnit = bSlashFound;
// need more precision for value per unit
if (bValuePerUnit)
eUnits = INCHES_1M;
}
else if (c1 == 'm' || c1 == 'M') {
// assume mm or millimeters
eUnits = MM_25K;
// Check for mp
if (c2 == 'p' || c2 == 'P')
eUnits = MILLIPOINT;
// Check for millipoints
else if ((nUnitPos > 5)
&& (c5 == 'p' || c5 == 'P')) {
eUnits = MILLIPOINT;
}
bValuePerUnit = bSlashFound;
}
else if (c1 == 'c' || c1 == 'C') {
// cm centimeters
eUnits = CM_250K;
bValuePerUnit = bSlashFound;
}
else if (c1 == 'p' || c1 == 'P') {
// assume pt or points
eUnits = POINTS_1K;
bValuePerUnit = bSlashFound;
// Check for pc or picas
if (c2 == 'c' || c2 == 'C' || c2 == 'i' || c2 == 'I') {
eUnits = PICAS_12K;
}
}
else {
// didn't find anything useful; back to default
bDefaultUnitsUsed = true;
}
}
// Set the units according to what we found
meUnit = eUnits;
// Now convert the numeric value from a string to the internal
// representation as a int -- avoiding float precision arithmetic
// and avoiding system routines to do numeric conversions.
// Start by determining what factor to apply to the real world number in order
// to get the internal int representation.
// e.g. "1.0in" in units INCHES_72K would have a factor of 72000 applied.
int nFactor = 0;
switch (units()) {
case INCHES_1M:
nFactor = 1000000;
break;
case CM_250K:
nFactor = 250000;
break;
case MM_25K:
nFactor = 25000;
break;
case INCHES_72K:
nFactor = 72000;
break;
case POINTS_1K:
nFactor = 1000;
break;
case MILLIPOINT:
nFactor = 1;
break;
case PICAS_12K:
nFactor = 12000;
break;
}
// Now parse the number from its string form into its internal number.
// Our algorithm is to take each digit, convert it from ascii to a number
// by subtracting ascii '0' (49).
// Taking this digit value, we multiply by our factor and add to our
// accumulated value.
// Working through the number before the decimal place, each time we
// encounter a digit, we multiply the accumulated value by 10.
int nValue = 0; // The internal value we're computing
int nFraction = 0; // Portion after the decimal
int nDecimals = -1; // Number of places after the decimal
boolean bNegative = false; // true if the value is negative
for (int i = nNumStart; i != nNumStart + nLength; i++) {
char c = sText.charAt(i);
if (c == '-') {
bNegative = true;
}
else if (c == '.') {
// From now on we're working on fractional digits.
nDecimals = 0;
}
else if (nDecimals == -1) {
// This digit is before the decimal place.
// Multiply the accumulated value
// by 10 and then add the next digit.
nValue *= 10;
nValue += (c - 48) * nFactor;
if (nValue < 0) {
// We've overflowed; no sense going on
nValue = Integer.MAX_VALUE;
break;
}
}
else {
// We're after the decimal place.
// Take the digit value and divide by 10 the appropriate number
// of times.
// Accumulate our fractional value as 10* larger than it should
// be so that we can round it when we're done
if (c != '0') {
int nDecimal = (c - 48) * nFactor;
for (int k = 0; k < nDecimals; k++) {
nDecimal /= 10;
}
nFraction += nDecimal;
}
nDecimals++;
}
}
// At this point, nFraction is 10* larger than it should be
// Round it as we add to the accumulated value
nValue += (nFraction + 5) / 10;
if (nValue < 0) {
// Handle overflow
mnValue = Integer.MAX_VALUE;
return;
}
if (bNegative)
nValue *= -1;
else if (bValuePerUnit) {
if (bDefaultUnitsUsed && eUnits == MM_25K) {
// lines per mm not very useful; assume lines per cm instead
nValue /= 10;
}
else if (eUnits == POINTS_1K) {
// lines per point not very useful; assume lines per pica
// instead
nValue /= 12;
}
// value = (1 / (valuePerUnit / factor)) * factor
// simplifies to: value = factor ^ 2 / valuePerUnit
double dFactor = nFactor;
nValue = (int) (dFactor * dFactor / nValue);
}
// set the value based on the new unit.
mnValue = nValue;
}
/**
* Instantiates a UnitSpan
from the given UnitSpan
.
*
* @param source
* the UnitSpan
to copy to this object.
* @deprecated UnitSpan is immutable, so there is no need to copy an instance.
*/
public UnitSpan(UnitSpan source) {
meUnit = source.meUnit;
mnValue = source.mnValue;
}
/**
* Returns a UnitSpan
representing
* the absolute value of this UnitSpan
.
*
* @return
* a unit span of the absolute value.
*/
public UnitSpan abs() {
if (value() > 0)
return this;
return new UnitSpan(units(), -value());
}
/**
* Returns a UnitSpan
representing the
* addition of this object and the given UnitSpan
.
* The given UnitSpan
's value is converted
* to this object's units for the operation.
*
* @param add
* the UnitSpan
to add.
* @return
* a unit span of the addition.
*/
public UnitSpan add(UnitSpan add) {
int nAdd = convertUnit(units(), add.units(), add.value());
return new UnitSpan(units(), value() + nAdd);
}
/*
* Multiply this object's value by the given int value and divide by the
* second int value. This is an attempt to increase performance by avoiding
* the ftol function. The resulting value is rounded to 0 decimal places.
*
* @param nNumerator
* the numerator.
* @param nDenominator
* the denominator.
* @return
* the answer of this object's value with the factor applied.
*/
UnitSpan applyFactor(int nNumerator, int nDenominator) {
// Easy case: numerator and denominator divide evenly.
if (nNumerator % nDenominator == 0) {
return new UnitSpan(units(), value()
* (int) (nNumerator / nDenominator));
}
// Second easy case: value() and denominator divide evenly.
if (value() % nDenominator == 0) {
return new UnitSpan(units(), nNumerator
* (int) (value() / nDenominator));
}
// Often the easy cases do not succeed only because of factors of 10.
// Divide by 10 until we hit a success or we can't reduce any further.
int nTop = nNumerator;
int nBottom = nDenominator;
while (nTop % 10 == 0 && nBottom % 10 == 0) {
nTop /= 10L;
nBottom /= 10L;
if (value() % nBottom == 0) {
return new UnitSpan(units(), nTop * (int) (value() / nBottom));
}
}
// Last ditch expensive effort.
double dScale = (double) nNumerator / nDenominator;
return new UnitSpan(units(), (int) Math.round(value() * dScale));
}
// ----------------------------------------------------------------------
//
// Assignment.
//
// ----------------------------------------------------------------------
/**
* Returns a UnitSpan
representing the
* division of this object's value by the given int value.
* The resulting value is rounded to 0 decimal places.
*
* @param nDivisor
* the divisor.
* @return
* a unit span of the division.
*/
public UnitSpan divide(int nDivisor) {
return new UnitSpan(units(), mnValue / nDivisor);
}
/**
* Returns a UnitSpan
representing the
* division of this object's value by the given UnitSpan
.
* The given UnitSpan
's value is converted to this object's
* units for the operation.
*
* @param divisor
* the divisor.
* @return
* a unit span of the division.
*/
public double divide(UnitSpan divisor) {
UnitSpan oConverted = new UnitSpan(units(), divisor.units(), divisor.value());
return ((double) value()) / ((double) oConverted.value());
}
/**
* Determines if this object is equal to the given Object
.
* Comparisons with instances of non-UnitSpan
objects are never equal.
*
* @param object
* the Object
to compare.
* @return
* true if equal, false otherwise.
*/
public boolean equals(Object object) {
if (this == object)
return true;
// This overrides Object.equals(boolean) directly, so...
if (object == null)
return false;
if (object.getClass() != getClass())
return false;
return compareTo((UnitSpan)object) == 0;
}
/**
* @exclude from published api.
*/
public int hashCode () {
return (31 * (meUnit & UNIT_MASK)) ^ mnValue;
}
/**
* Returns a UnitSpan
representing this
* UnitSpan
's value snaped to the nearest
* grid coordinate of the given UnitSpan
.
* The given UnitSpan
's value is converted to this object's
* units for the calculation.
*
* Positive grid values will
* "move" this object's value to the left (or down), and negative grid values will
* "move" this object's value to the right (or up).
*
* The algorithm used to "snap" this object's value is (<y/x> ∗ x) where y is this
* value, x is the grid size, and <> is the floor function; this is best
* explained with examples:
*
* grid = 5, pt = 7 and (<7/5> * 5) = 1 * 5 = 5 (moved to left)
* grid = 5, pt = -7 and (<-7/5> * * 5) = -2 * 5 = -10 (moved to left)
*
* grid = -5, pt = 7 and (<7/-5> * -5) = -2 * -5 = 10 (moved to right)
* grid = -5, pt = * -7 and (<-7/-5> * -5) = 1 * -5 = -5 (moved to right)
*
*
* @param grid
* the grid coordinate
* @return
* a unit span aligned to the grid.
*/
public UnitSpan grid(UnitSpan grid) {
// this value in oGrid units.
int value = convertUnit(grid.units(), units(), value());
int gridValue = grid.value();
if (gridValue != 0 && value % gridValue != 0) {
value = (int) (Math.floor((double) value
/ (double) gridValue) * gridValue);
return new UnitSpan(units(), convertUnit(units(), grid.units(), value));
}
return this;
}
/**
* Determines if this object is greater than the given UnitSpan
.
* The given UnitSpan
's value is converted to this object's
* units for the comparison.
*
* @param compare
* the UnitSpan
to compare.
* @return
* true if greater than, false otherwise.
*/
public boolean gt(UnitSpan compare) {
return compareTo(compare) > 0;
}
/**
* Determines if this object is greater than or equal to the given.
* UnitSpan
. The given UnitSpan
's value is
* converted to this object's units for the comparison.
*
* @param compare
* the UnitSpan
to compare.
* @return
* true if greater than or equal to, false otherwise.
*/
public boolean gte(UnitSpan compare) {
return compareTo(compare) >= 0;
}
/**
* Determines if this object is less than the given UnitSpan
.
* The given UnitSpan
's value is converted to this object's
* units for the comparison.
*
* @param compare
* the UnitSpan
to compare.
* @return
* true if less than, false otherwise.
*/
public boolean lt(UnitSpan compare) {
return compareTo(compare) < 0;
}
/**
* Determines if this object is less than or equal to the given
* UnitSpan
. The given UnitSpan
's value is
* converted to this object's units for the comparison.
*
* @param compare
* the UnitSpan
to compare.
* @return
* true if less than or equal to, false otherwise.
*/
public boolean lte(UnitSpan compare) {
return compareTo(compare) <= 0;
}
/**
* Returns a UnitSpan
representing the
* multiplication of this object's value by the given double value.
* The resulting value is rounded to 0 decimal places.
*
* @param dScale
* the multiplier.
* @return
* a unit span of the multiplication.
*/
public UnitSpan multiply(double dScale) {
return new UnitSpan(units(), (int) Math.round(value() * dScale));
}
/**
* Returns a UnitSpan
representing the
* multiplication of this object's value by the given int value.
*
* @param nScale
* the multiplier.
* @return
* a unit span of the multiplication.
*/
public UnitSpan multiply(int nScale) {
return new UnitSpan(units(), mnValue * nScale);
}
/**
* Returns a UnitSpan
representing the
* rounding of this object's value to the given UnitSpan
.
* Positive and negative round values have the same effect.
*
* This value is rounded by adding one to the quotient (subtracting one for
* negative quotients) of [y / |x|] if the remainder of [y / |x|] is >=
* [x/2] or <= [-x/2] where y is this object's value, and x is the round
* value. Again this is best explained with examples:
*
* rnd = 5, pt = 7, quo = 1, rem = 2, rem / rnd < 0.5 and pt = quo * rnd = 5
* rnd = 5, pt = 8, quo = 1, rem = 3, rem / rnd > 0.5 and pt = (quo + 1) * rnd = 10
* rnd = 5, pt = -7, quo = -1, rem = 2, rem / rnd < 0.5 and pt = quo * rnd = -5
* rnd = 5, pt = -8, quo = -1, rem = 2, rem / rnd > 0.5 and pt = (quo - 1) * rnd = -10
*
*
* @param round
* the rounding coordinate.
* @return
* a unit span of the rounding.
*
* @exclude from published api.
*/
public UnitSpan round(UnitSpan round) {
// this value in oRound units.
int rounded = convertUnit(round.units(), units(), value());
int nAbsRound = Math.abs(round.value());
if (round.value() != 0 && rounded % round.value() != 0) {
int nQuotient = rounded / nAbsRound;
if (Math.abs(((double) rounded / (double) nAbsRound)
- nQuotient) >= 0.5) {
if (nQuotient > 0)
nQuotient++;
else
nQuotient--;
}
rounded = (nQuotient) * nAbsRound;
return new UnitSpan(units(), convertUnit(units(), round.units(), rounded));
}
return this;
}
/**
* Returns a UnitSpan
representing the
* subtraction of this object's value from the given UnitSpan
.
* The given
* UnitSpan
's value is converted to this object's units for
* the operation.
*
* @param subtract
* the UnitSpan
to subtract.
* @return
* a unit span of the subtraction.
*
* @exclude from published api.
*/
public UnitSpan subtract(UnitSpan subtract) {
int lSubtract = convertUnit(units(), subtract.units(), subtract
.value());
return new UnitSpan(units(), value() - lSubtract);
}
/**
* Returns a String
containing this object's value converted
* followed by its units, formatted according to the given arguments.
*
* @param nPrecision
* the number of decimal places to which the value will be
* calculated.
* @param bTruncate
* If false, the decimal portion will be padded out with 0's up
* to the number specified by nPrecision
. If true, '0'
* placeholders will be suppressed.
* @param bValuePerUnits
* indicates the number should be expressed in quantity per unit,
* i.e., lines per inch.
* @return
* The output string, which will be the value converted to units
* followed by the (abbreviated) unit.
*
* @exclude from published api.
*/
public String text(int nPrecision /* = 8 */,
boolean bTruncate /* = false */,
boolean bValuePerUnits /* = false */) {
//
// If units are unknown, simply write out the
// unitspan value without units
//
if (units() == UNIT_UNKNOWN) {
return Integer.toString(value());
}
int nValue = value();
int nFactor = 0; // Number of implicit decimal places
final int STRSIZE = 32;
char cValue[] = new char[STRSIZE];
cValue[STRSIZE - 1] = '\0';
switch (units()) {
case INCHES_1M:
nFactor = 6;
cValue[STRSIZE - 3] = 'i';
cValue[STRSIZE - 2] = 'n';
break;
case CM_250K:
nValue = applyFactor(value(), 10, 25);
nFactor = 5;
cValue[STRSIZE - 3] = 'c';
cValue[STRSIZE - 2] = 'm';
break;
case MM_25K:
nValue = applyFactor(value(), 10, 25);
nFactor = 4;
cValue[STRSIZE - 3] = 'm';
cValue[STRSIZE - 2] = 'm';
break;
case PICA_PT_10K:
nFactor = 4;
cValue[STRSIZE - 3] = 'p';
cValue[STRSIZE - 2] = 't';
break;
case INCHES_72K:
nValue = convertReally(INCHES_1M, INCHES_72K, value());
nFactor = 6;
cValue[STRSIZE - 3] = 'i';
cValue[STRSIZE - 2] = 'n';
break;
case POINTS_1K:
nFactor = 3;
cValue[STRSIZE - 3] = 'p';
cValue[STRSIZE - 2] = 't';
break;
case MILLIPOINT:
nFactor = 0;
cValue[STRSIZE - 3] = 'm';
cValue[STRSIZE - 2] = 'p';
break;
case PICAS_12K:
nValue = applyFactor(value(), 1, 12);
nFactor = 3;
cValue[STRSIZE - 3] = 'p';
cValue[STRSIZE - 2] = 'c';
break;
}
int nPos = STRSIZE - 4;
if (bValuePerUnits) {
// value per mm is pretty useless; convert to value per cm:
if (units() == MM_25K) {
nFactor = 5;
cValue[STRSIZE - 3] = 'c';
cValue[STRSIZE - 2] = 'm';
}
// value per point is pretty useless; convert to value per pica:
if (units() == PICA_PT_10K || units() == POINTS_1K
|| units() == MILLIPOINT) {
nValue /= 12L;
cValue[STRSIZE - 3] = 'p';
cValue[STRSIZE - 2] = 'c';
}
cValue[STRSIZE - 4] = '/';
nPos = STRSIZE - 5;
// valuePerUnit = (1 / (lValue / factor)) * factor
// simplifies to: valuePerUnit = factor ^ 2 / lValue
double factor = Math.pow(10.0, nFactor);
nValue = (int) (factor * factor / nValue);
nValue = ((nValue + 50) / 100) * 100; // round last 2 places
}
boolean bNegative = false;
if (nValue < 0) {
nValue = nValue * -1;
bNegative = true;
}
// reduce our number to the precision requested.
for (; (int) nFactor > nPrecision; nFactor--) {
if ((int) nFactor == (nPrecision + 1))
nValue = ((nValue + 5) / 10);
else
nValue = nValue / 10;
}
boolean bZeroValue = (nValue == 0L);
// Now start copying in numbers, back to front.
// Avoid writing out trailing zeros
boolean bAllZeros = true;
if (bZeroValue) {
cValue[nPos--] = '0';
}
else {
while (nValue > 0) {
if (nFactor == 0 && ! bAllZeros) {
cValue[nPos--] = '.';
}
int cDigit = (nValue % 10) + 48;
// Avoid writing out trailing zeros after the decimal
if (! (cDigit == '0' && bAllZeros && nFactor > 0)) {
// Character.toChars(cDigit, cValue, nPos--); JDK 1.5 only
cValue[nPos--] = (char) cDigit;
bAllZeros = false;
}
nValue = nValue / 10;
nFactor--;
}
// Write out any remaining zeros after the decimal place.
while (nFactor > 0) {
cValue[nPos--] = '0';
nFactor--;
}
if (nFactor == 0 && ! bAllZeros) {
cValue[nPos--] = '.';
cValue[nPos--] = '0';
}
if (bNegative && ! bZeroValue)
cValue[nPos--] = '-';
}
// here we should do a look up. to check for cached strings
return new String(cValue, nPos + 1, STRSIZE - nPos - 2);
}
/**
* Returns a String
containing this object's value
* followed by its units.
*
* @return
* a string of this UnitSpan
's value
* followed by its (abbreviated) unit.
*/
public String toString() {
return text(8, false, false);
}
/**
* Gets this object's units.
*
* @return
* the unit as an int.
*/
public int units() {
return meUnit;
}
// Javaport: commented out for immutability.
// /*
// * Sets this object's units to the given unit code.
// *
// * @param eUnits
// * the new unit code.
// */
// private void units(int eUnits) {
// meUnit = eUnits;
// }
/**
* Gets the units per inch for this object's unit code.
*
* @return
* the units per inch for this unit.
*/
public int unitsPerInch() {
return unitsPerInch(meUnit);
}
/**
* Parses a value with strict validation.
*
* Given a string representing a value with optional unit specification,
* this method parses the string content and returns results.
*
*
* The caller can invoke this method to parse specifically for supported
* UnitSpan types, or it can call it as a more general-purpose value
* and unit parser for caller-recognized types. It can also use a
* single call for both purposes.
*
*
* The method returns flag indicating whether the parse succeeded or
* failed. The parse will fail if the string is not in the general
* format of a value with optional units. If the string does fit the
* format, but the unit type is not recognized, the call succeeds, and
* the structure indicates that the caller may choose to validate the
* units. Alternatively, the caller may treat this situation as an
* error if it is expecting only UnitSpan units.
*
*
* Values in the structure are populated as described below. These
* values are relevant only if the parse succeeds. If the string
* contains recognized UnitSpan units, only two fields in the
* structure are relevant:
*
*
* -
* mnValue: The ultimate value for a resulting UnitSpan object.
* This will already have been scaled to match the unit type.
*
* -
* meUnits: The unit code to use for a resulting UnitSpan
* object.
*
*
* If the units are not recognized or are expressed as a percentage (and
* the parse otherwise succeeds), the caller may need to look at all
* fields in the structure.
*
*
* -
* mnValue: The whole number part of the value (i.e., the part
* before the decimal point).
*
* -
* mnFraction: The fractional part of the number (i.e., the part
* after the decimal point), expressed as an integer value. For
* example, .1234 would result in a value of 1,234 here.
*
* -
* mnFractionScale: The amount to divide the fraction part by to
* convert it back to its true fractional amount. For example, a
* fractional part of .1234 would yield a value of 10,000 in this field.
*
* -
* meUnits: UNIT_UNKNOWN to indicate that the parse didn't
* recognize the unit type as a valid UnitSpan type.
*
* -
* mcUnit0, mcUnit1, mcUnit2: First three characters of the unit text.
*
* -
* mbValuePerUnit: True if the string expressed the measurement
* as a value per unit (e.g., 6/in). False otherwise.
*
* -
* mbPercent: True if the string expressed the measurement as a
* percentage, instead of including unit text. False otherwise.
*
*
* @param sText Text to parse.
* @param eDefaultUnits (optional) Default units to use if the string
* does not contain a unit specification. The default is UNIT_UNKNOWN,
* which cause the result of DefaultUnit() to be used.
* @param bAllowNegative (optional) True if negative values are
* allowed. If false, a negative value will be considered an error.
* The default is false.
* @param bForceValuePerUnit (optional) True if the result is to be
* treated as value per unit even if the slash character is not present.
* The default is false.
* @param bAllowPercent (optional) True if the percent character is to
* be allowed as a unit type. The default is false.
* @return Result of the parse if it succeeded; null if it failed.
* Please see above for an explanation of the fields. If the unit text
* is not recognized as a valid UnitSpan unit type but the parse
* otherwise succeeds, true will be returned and the value of meUnits in
* the structure will be UNIT_UNKNOWN.
*
* @exclude from published api.
*/
public static ParseData validatingParse(String sText,
int eDefaultUnits,
boolean bAllowNegative,
boolean bForceValuePerUnit,
boolean bAllowPercent) {
// Initialize all fields in result.
int nValue = 0;
int nFraction = 0;
int nFractionScale = 1;
char cUnit0 = 0;
char cUnit1 = 0;
char cUnit2 = 0;
int eUnits = UNIT_UNKNOWN;
boolean bValuePerUnit = bForceValuePerUnit;
boolean bPercent = false;
//
// Parsing is implemented as a finite state machine that processes the
// text in a single pass, looking for the following sequence (all
// components are optional, however, there must be at least one digit
// present):
// ws sign ws digits '.' digits ws '/' ws units ws
// where:
// ws: white space
// sign: '+' or '-'
// digits: '0' through '9'
// units: alphabetic unit sequence or percent character
//
State eState = State.PreSign;
final int MAX_FRACTION_SCALE = 10000000;
boolean bNegative = false;
boolean bSlashSeen = false;
int nUnitIndex = 0;
//
// Process the text one character at a time.
// JavaPort: The C++ implementation iterates over Unicode chars, then
// treats the input as an 8-bit char, so there is really no point
// in iterating over Unicode characters. If unit text allowed
// code points > 0xFFFF, then this this implementation (and
// the C++) needs to be fixed!
//
for (int i = 0; i < sText.length(); i++) {
final char c = sText.charAt(i);
//
// Determine the character class (i.e., '0' for digits, 'a' for letters)
//
int cType = c;
if (('0' <= c) && (c <= '9')) {
cType = '0';
}
else if ((('a' <= c) && (c <= 'z')) ||
(('A' <= c) && (c <= 'Z'))) {
cType = 'a';
}
//
// This switch statement is the body of the machine. The major cases are
// for each state. Within each state, there is a secondary switch to
// transition to the next state depending on the character type. Note
// that there is some minor duplication in the state/input code snippets;
// however, it was felt that this was more readable than trying to
// isolate the common statements.
//
switch (eState) {
case PreSign:
switch (cType) {
case '-':
if (! bAllowNegative) {
return null;
}
bNegative = true;
eState = State.PreNumber;
break;
case '+':
eState = State.PreNumber;
break;
case '0':
nValue = c - '0';
eState = State.WholePart;
break;
case '.':
eState = State.FractionStart;
break;
case ' ':
break;
default:
return null;
}
break;
case PreNumber:
switch (cType) {
case '0':
nValue = c - '0';
eState = State.WholePart;
break;
case '.':
eState = State.FractionStart;
break;
case ' ':
break;
default:
return null;
}
break;
case WholePart:
switch (cType) {
case '0':
if (nValue < Integer.MAX_VALUE) {
nValue = (nValue * 10) + (c - '0');
if (nValue < 0) {
nValue = Integer.MAX_VALUE;
}
}
break;
case '.':
eState = State.FractionPart;
break;
case 'a':
assert (nUnitIndex == 0);
cUnit0 = c;
nUnitIndex++;
eState = State.Units;
break;
case '%':
if (! bAllowPercent) {
return null;
}
bPercent = true;
eState = State.PostValue;
break;
case '/':
bValuePerUnit = true;
bSlashSeen = true;
eState = State.PostNumber;
break;
case ' ':
eState = State.PostNumber;
break;
default:
return null;
}
break;
case FractionStart:
switch (cType) {
case '0':
nFraction = c - '0';
nFractionScale *= 10;
eState = State.FractionPart;
break;
default:
return null;
}
break;
case FractionPart:
switch (cType) {
case '0':
if (nFractionScale <= MAX_FRACTION_SCALE) {
nFraction = (nFraction * 10) + (c - '0');
nFractionScale *= 10;
}
break;
case 'a':
assert (nUnitIndex == 0);
cUnit0 = c;
nUnitIndex++;
eState = State.Units;
break;
case '%':
if (! bAllowPercent) {
return null;
}
bPercent = true;
eState = State.PostValue;
break;
case '/':
bValuePerUnit = true;
bSlashSeen = true;
eState = State.PostNumber;
break;
case ' ':
eState = State.PostNumber;
break;
default:
return null;
}
break;
case PostNumber:
switch (cType) {
case 'a':
assert (nUnitIndex == 0);
cUnit0 = c;
nUnitIndex++;
eState = State.Units;
break;
case '%':
if (! bAllowPercent) {
return null;
}
bPercent = true;
eState = State.PostValue;
break;
case '/':
if (bSlashSeen) {
return null;
}
bValuePerUnit = true;
bSlashSeen = true;
break;
case ' ':
break;
default:
return null;
}
break;
case Units:
switch (cType) {
case 'a':
if (nUnitIndex == 0) {
cUnit0 = c;
}
else if (nUnitIndex == 1) {
cUnit1 = c;
}
else if (nUnitIndex == 2) {
cUnit2 = c; // this flags units as invalid
}
else {
assert cUnit2 != 0; // already marked as invalid
// ignore remaining units characters
}
nUnitIndex++;
break;
case ' ':
eState = State.PostValue;
break;
default:
return null;
}
break;
case PostValue:
switch (cType) {
case ' ':
break;
default:
return null;
}
break;
}
}
//
// Cannot end in these states: error.
//
switch (eState) {
case PreSign: // all white-space
case PreNumber: // only white-space and sign
case FractionStart: // no digits
return null;
}
//
// Try to validate the units.
//
UnitMapEntry unitMapEntry = null;
//
// If no units were encountered (i.e., only a number), look up the
// default units in the table, by unit code.
//
if (eState != State.Units && eState != State.PostValue) {
if (eDefaultUnits == UNIT_UNKNOWN)
eDefaultUnits = defaultUnits();
for (int i = 0; i < gUnitMap.length; i++) {
UnitMapEntry oMapEntry = gUnitMap[i];
if (oMapEntry.meUnits == eDefaultUnits) {
unitMapEntry = oMapEntry;
break;
}
}
}
//
// Otherwise, some sort of units seen. If not a percent, look up the
// unit string in the table.
//
else if (! bPercent && nUnitIndex == 2) {
for (int i = 0; i < gUnitMap.length; i++) {
UnitMapEntry mapEntry = gUnitMap[i];
if (((cUnit0 == mapEntry.msLower.charAt(0))
|| (cUnit0 == mapEntry.msUpper.charAt(0)))
&& ((cUnit1 == mapEntry.msLower.charAt(1))
|| (cUnit1 == mapEntry.msUpper.charAt(1))) ) {
unitMapEntry = mapEntry;
break;
}
}
}
//
// If there are valid units, we can build up the final value, scaling it
// according to the amount in the unit table.
//
if (unitMapEntry != null) {
eUnits = unitMapEntry.meUnits;
if (nValue < Integer.MAX_VALUE) {
long lResult = (long)nValue * unitMapEntry.mnUnitScale;
//
// If there is a fraction part, it needs to be converted and added. Note
// that it starts out as a raw number (e.g., 1,234 for .1234). The
// accumulated fraction scale indicates how to scale it.
//
if (nFraction != 0) {
long lFraction = nFraction;
lFraction *= unitMapEntry.mnUnitScale;
lFraction += nFractionScale / 2;
lFraction /= nFractionScale;
lResult += lFraction;
}
if (lResult > Integer.MAX_VALUE) {
nValue = Integer.MAX_VALUE;
}
else {
nValue = (int) lResult;
}
}
//
// If value per unit requested, need to put in the form 1/x. Note: the
// old code contorted values based on questionable assumptions. For
// example, it you said 2/mm, it assumed you really meant 2/cm. These
// machinations have been intentionally omitted, but may have to be
// re-introduced if there are problems in customer collateral.
//
if (bValuePerUnit) {
if (nValue == 0) {
return null;
}
//
// Because the value has already been biased with the unit scale, we need
// to remove that bias, invert it and then multiply the result by the
// unit scale. i.e.,
// inverted = (1 / (value / unitScale)) * unitScale
// = (unitScale**2) / value
//
double dUnitScale = unitMapEntry.mnUnitScale;
nValue = (int) Math.round (dUnitScale * dUnitScale / nValue);
}
}
//
// Finally, negate if required.
//
if (bNegative) {
nValue = -nValue;
nFraction = -nFraction;
}
return new ParseData(nValue, nFraction, nFractionScale, eUnits, cUnit0, cUnit1, cUnit2, bValuePerUnit, bPercent);
}
/**
* Gets this object's value.
*
* @return
* the value.
*/
public int value() {
return mnValue;
}
// Javaport: commented out for immutability.
// /**
// * Sets this object's value.
// *
// * @param lValue
// * the new value.
// */
// private void value(int lValue) {
// mlValue = lValue;
// }
/**
* Gets this object's value converted to the given unit code.
*
* @param eUnits
* the unit code.
* @return
* the converted value.
*/
public int valueAsUnit(int eUnits) {
return convertUnit(eUnits, units(), value());
}
/**
* Compares two UnitSpan
(s) for equality, allowing for null arguments.
* @param u1
* the first UnitSpan
to compare.
* @param u2
* the second UnitSpan
to compare.
* @return
* true if equal, false othewise. Note that two null arguments will be
* considered equal, but comparing a null argument to a non-null one
* will not be considered equal.
*/
public static boolean match(UnitSpan u1, UnitSpan u2) {
if (u1 == u2) {
return true;
}
if ((u1 == null) || (u2 == null)) {
return false;
}
return u1.equals(u2);
}
/**
* Compares this object to the given UnitSpan
.
* The given UnitSpan
's value is converted to this object's
* units for the comparison.
*
* @param compare
* the UnitSpan
to compare.
* @return
* the value 0 if equal;
* a value less than zero if this object is less that the given argument; and
* a value greater than zero if this object is greater that the given argument.
*/
public int compareTo(UnitSpan compare) {
if (compare == null)
throw new NullPointerException();
int nTest = convertUnit(units(), compare.units(), compare.value());
if (value() < nTest) {
return -1;
}
else if (value() > nTest) {
return 1;
}
return 0;
}
}