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

com.adobe.xfa.ut.UnitSpan Maven / Gradle / Ivy

There is a newer version: 2024.11.18598.20241113T125352Z-241000
Show newest version
/*
 * 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy