Length.java

/**
 * **********************************************************************
 *
 * <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * <p>Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * <p>Use is subject to license terms.
 *
 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0. You can also obtain a copy of the License at
 * http://odftoolkit.org/docs/license.txt
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 *
 * <p>See the License for the specific language governing permissions and limitations under the
 * License.
 *
 * <p>**********************************************************************
 */
package org.odftoolkit.odfdom.type;

import java.util.logging.Logger;
import java.util.regex.Pattern;

/**
 * This class represents the in OpenDocument format used data type {
 *
 * @odf.datatype length}
 */
public class Length implements OdfDataType {

  private static final Logger LOG = Logger.getLogger(Length.class.getName());

  /** Measurement units for ODF datatype length */
  public enum Unit {
    //		TWIP(0.0176389241667372, "twip"), // TWentieth of an Inch Point

    POINT(0.352777778, "pt"), // Pica Point
    PIXEL(0.28, "px"), // Pixel (see http://www.w3.org/TR/2001/REC-xsl-20011015/slice5.html#pixels)
    //		DIDOT_POINT(0.375972222, "dpt"), // Didot point (or Point typographique) after the French
    // typographer Firmin Didot (1764-1836).
    MILLIMETER(1.0, "mm"), // see http://www.w3.org/TR/2001/REC-xsl-20011015/sliceD.html#ISO31
    MICROMETER(0.001, "\u00b5m"), // see http://en.wikipedia.org/wiki/Micrometre
    PICA(4.2176, "pc"), // 1 Inch = 6 Pica = 72 Pica Point
    CENTIMETER(10.0, "cm"), // see http://www.w3.org/TR/2001/REC-xsl-20011015/sliceD.html#ISO31
    INCH(25.4, "in");
    //		FEET(304.8, "ft"),
    //		METER(1000.0, "m"),
    //		KILOMETER(1000000.0, "km"),
    //		MILES(1609344.0, "mi");
    private final double mUnitInMillimiter;
    private final String mUnitAbbreviation;

    Unit(double unitInMillimiter, String unitAbbreviation) {
      this.mUnitInMillimiter = unitInMillimiter;
      this.mUnitAbbreviation = unitAbbreviation;
    }

    /** @return the length of the Unit in Millimeter */
    public double unitInMillimiter() {
      return mUnitInMillimiter;
    }

    /** @return the abbreviation of the Unit (e.g. cm for Centimeter) */
    public String abbr() {
      return mUnitAbbreviation;
    }
  }

  private String mLengthString = null;
  private static final Pattern lengthPattern =
      Pattern.compile("^-?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc)|(px))$");

  /**
   * Construct Length by the parsing the given string
   *
   * @param length The String to be parsed into Length
   * @throws NumberFormatException if the given argument is not a valid Length (an Integer only will
   *     be presumed to be 'pt')
   */
  public Length(String length) throws NumberFormatException {
    if (!isValid(length)) {
      try {
        Integer.parseInt(length);
        length = length + Unit.POINT.abbr();
      } catch (NumberFormatException e) {
        throw new NumberFormatException(
            "parameter '" + length + "' is invalid for datatype Length");
      }
    }
    mLengthString = length;
  }

  /**
   * Check if the specified String instance is a valid {
   *
   * @odf.datatype length} data type
   * @param stringValue the value to be tested
   * @return true if the value of argument is valid for {
   * @odf.datatype length} data type false otherwise
   */
  public static boolean isValid(String stringValue) {
    if ((stringValue == null) || (!lengthPattern.matcher(stringValue).matches())) {
      return false;
    } else {
      return true;
    }
  }

  /**
   * Returns the Unit of the given length.
   *
   * @param length the <code>Unit</code> should be obtained from
   * @return Returns a <code>Unit</code> object representing the specified Length unit.
   */
  public static Unit parseUnit(String length) {
    Unit lengthUnit = null;
    if (length == null) {
      throw new NumberFormatException("The input length should not be null!");
    } else {
      boolean identifiedInput = false;
      for (Unit unit : Unit.values()) {
        if (length.contains(unit.abbr())) {
          lengthUnit = unit;
          identifiedInput = true;
          break;
        }
      }
      if (!identifiedInput) {
        throw new NumberFormatException("The input length " + length + " has no valid Unit!");
      }
    }
    return lengthUnit;
  }

  /**
   * Returns the value of the given length as int.
   *
   * @param length the <code>int</code> value should be obtained from
   * @return Returns a <code>int</code> value representing the specified Length value.
   */
  public static int parseInt(String length) {
    return (int) parseDouble(length, null);
  }

  /**
   * Returns the value of the given length as int.
   *
   * @param length the <code>int</code> value should be obtained from
   * @param destinationUnit The unit to be converted to
   * @return Returns a <code>int</code> value representing the specified Length value.
   */
  public static int parseInt(String length, Unit destinationUnit) {
    return (int) parseDouble(length, destinationUnit);
  }

  /**
   * Returns the value of the given length as long.
   *
   * @param length the <code>long</code> value should be obtained from
   * @return Returns a <code>long</code> value representing the specified Length value.
   */
  public static long parseLong(String length) {
    return (long) parseDouble(length, null);
  }

  /**
   * Maps the a length string to a different unit
   *
   * @param length The value to be mapped
   * @param destinationUnit The unit to be converted to
   * @return The converted value without unit suffix as Double
   */
  public static long parseLong(String length, Unit destinationUnit) {
    return (long) parseDouble(length, destinationUnit);
  }

  /**
   * Returns the value of the given length as double.
   *
   * @param length the <code>double</code> value should be obtained from
   * @return Returns a <code>double</code> value representing the specified Length value.
   */
  public static double parseDouble(String length) {
    return parseDouble(length, null);
  }

  /**
   * Maps the a length string to a different unit
   *
   * @param length The value to be mapped
   * @param destinationUnit The unit to be converted to
   * @return The converted value without unit suffix as double
   */
  public static double parseDouble(String length, Unit destinationUnit) {
    double newValue = 0;
    if (length != null) {
      double roundingFactor = 10000.0;
      boolean identifiedInput = false;

      for (Unit unit : Unit.values()) {
        if (length.contains(unit.abbr())) {
          Double value = Double.valueOf(length.substring(0, length.indexOf(unit.abbr())));
          // if no destination unit was given the unit remains the same
          if (destinationUnit != null) {
            // using roundfactor proved to be more precise when used with Java XSLT processor
            newValue =
                Math.round(
                        roundingFactor
                            * value
                            / destinationUnit.unitInMillimiter()
                            * unit.mUnitInMillimiter)
                    / roundingFactor;
          } else {
            destinationUnit = unit;
          }
          identifiedInput = true;
          break;
        }
      }
      if (!identifiedInput) {
        LOG.warning(
            "The unit "
                + length
                + " could not be transformed to "
                + destinationUnit.toString()
                + "!");
      }
    } else {
      LOG.warning("The input length should not be null!");
    }
    return newValue;
  }

  /**
   * @param destinationUnit The unit to be converted to
   * @return The converted value as result
   */
  public String mapToUnit(Unit destinationUnit) {
    return mapToUnit(mLengthString, destinationUnit);
  }

  /** @return The length in Millimeter */
  public Double getMillimeters() {
    return getLength(this.mLengthString, Unit.MILLIMETER);
  }

  /**
   * WARNING: Not an allowed ODF value, just for interim calculation used!
   *
   * @return The length in Micrometer
   */
  public Double getMicrometer() {
    return getLength(this.mLengthString, Unit.MICROMETER);
  }

  /** @return The length in point */
  public Double getPoint() {
    return getLength(this.mLengthString, Unit.POINT);
  }

  /** @return The length in pixel */
  public Double getPixel() {
    return getLength(this.mLengthString, Unit.PIXEL);
  }

  /** @return The length in pica */
  public Double getPica() {
    return getLength(this.mLengthString, Unit.PICA);
  }

  /** @return The length in inch */
  public Double getInch() {
    return getLength(this.mLengthString, Unit.INCH);
  }

  /** @return The length in centimeter */
  public Double getCentimeter() {
    return getLength(this.mLengthString, Unit.CENTIMETER);
  }

  /**
   * Maps the a length string to a different unit
   *
   * @param length The value to be mapped
   * @param destinationUnit The unit to be converted to
   * @return The converted value without unit suffix as Integer
   */
  public static Double getLength(String length, Unit destinationUnit) {
    double newValue = 0.0;
    if (length != null) {
      double roundingFactor = 10000.0;
      boolean identifiedInput = false;

      for (Unit unit : Unit.values()) {
        if (length.contains(unit.abbr())) {
          Double value = Double.valueOf(length.substring(0, length.indexOf(unit.abbr())));
          // if no destination unit was given the unit remains the same
          if (destinationUnit != null) {
            // using roundfactor proved to be more precise when used with Java XSLT processor
            newValue =
                Math.round(
                        roundingFactor
                            * value
                            / destinationUnit.unitInMillimiter()
                            * unit.mUnitInMillimiter)
                    / roundingFactor;
          } else {
            destinationUnit = unit;
          }
          identifiedInput = true;
          break;
        }
      }
      if (!identifiedInput) {
        LOG.warning(
            "The unit "
                + length
                + " could not be transformed to "
                + destinationUnit.toString()
                + "!");
      }
    } else {
      LOG.warning("The input length should not be null!");
    }
    return newValue;
  }

  /**
   * Maps the a length string to a different unit
   *
   * @param length The value to be mapped
   * @param destinationUnit The unit to be converted to
   * @return The converted value with unit suffix as String
   */
  public static String mapToUnit(String length, Unit destinationUnit) {
    Double newValue = getLength(length, destinationUnit);
    return String.valueOf(newValue) + destinationUnit.abbr();
  }

  /**
   * Returns a Length instance representing the specified String value
   *
   * @param stringValue a String value
   * @return return a Length instance representing stringValue
   * @throws NumberFormatException if the given argument is not a valid Length
   */
  public static Length valueOf(String stringValue) throws NumberFormatException {
    return new Length(stringValue);
  }

  /**
   * Returns a String Object representing this Length's value
   *
   * @return return a string representation of the value of this Length object
   */
  @Override
  public String toString() {
    return mLengthString;
  }
}