/* * Rapid Beans Framework: RapidQuantity.java * * Copyright (C) 2009 Martin Bluemel * * Creation Date: 11/21/2005 * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; * either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * You should have received a copies of the GNU Lesser General Public License and the * GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ package org.rapidbeans.core.basic; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.text.NumberFormat; import java.util.List; import org.rapidbeans.core.common.RapidBeansLocale; import org.rapidbeans.core.exception.EnumException; import org.rapidbeans.core.exception.QuantityConversionNotSupportedException; import org.rapidbeans.core.exception.RapidBeansRuntimeException; import org.rapidbeans.core.exception.UtilException; import org.rapidbeans.core.exception.ValidationException; import org.rapidbeans.core.type.TypeRapidEnum; import org.rapidbeans.core.type.TypeRapidQuantity; import org.rapidbeans.core.util.StringHelper; /** * the abstract base class for quantity value objects. * * @author Martin Bluemel */ public abstract class RapidQuantity implements Cloneable, Comparable<RapidQuantity> { /** * the magnitude. */ private BigDecimal magnitude; /** * returns null if the enumTypeName could not be found in the table and a * concrete enum class could not be loaded. * * @return the magnitude */ public final BigDecimal getMagnitude() { return this.magnitude; } /** * the getter for the magnitude as long. * * @return the magnitude */ public final long getMagnitudeLong() { return this.magnitude.longValue(); } /** * the getter for the magnitude as double. * * @return the magnitude */ public final double getMagnitudeDouble() { return this.magnitude.doubleValue(); } /** * the unit. */ private RapidEnum unit; /** * the getter for the unit. * * @return the unit */ public final RapidEnum getUnit() { return this.unit; } /** * the type accesor method. * * @return the type instance */ public abstract TypeRapidQuantity getType(); /** * parameter types for constructor RapidQuantity(String). */ private static final Class<?>[] CONSTR_PARAMTYPES_STRING = { String.class }; /** * Factory method for a RapidQuantity. * * @param typename * determines the quantity's type * @param val * determines the quantity's value e. g. "12 km" * * @return the new quantity instance */ @SuppressWarnings("unchecked") public static final RapidQuantity createInstance(final String typename, final String val) { RapidQuantity quantity = null; final TypeRapidQuantity type = TypeRapidQuantity.forName(typename); final Class<?> clazz = type.getImplementingClass(); if (clazz != null) { try { final Constructor<RapidQuantity> constr = (Constructor<RapidQuantity>) clazz .getConstructor(CONSTR_PARAMTYPES_STRING); final Object[] initargs = new Object[1]; initargs[0] = val; quantity = (RapidQuantity) constr.newInstance(initargs); } catch (NoSuchMethodException e) { throw new RapidBeansRuntimeException("RapidQuantity class \"" + clazz.getName() + "\" does not have a constructor with String", e); } catch (IllegalArgumentException e) { throw new RapidBeansRuntimeException("IllegalArgumentException while trying" + "to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + typename + "\"", e); } catch (InstantiationException e) { throw new RapidBeansRuntimeException("InstantiationException while trying" + "to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + typename + "\"", e); } catch (IllegalAccessException e) { throw new RapidBeansRuntimeException("IllegalAccessException while trying" + "to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + typename + "\"", e); } catch (InvocationTargetException e) { throw new RapidBeansRuntimeException("InvocationTargetException while trying" + " to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + typename + "\"", e); } } else { quantity = new GenericQuantity(type, val); } return quantity; } /** * parameter types for constructor RapidQuantity(String). */ private static final Class<?>[] CONSTR_PARAMTYPES_MAG_UNIT = { BigDecimal.class, RapidEnum.class }; /** * Factory method for a RapidQuantity. * * @param typename * determines the quantity's type * @param val * determines the quantity's value e. g. "12 km" * * @return the new quantity instance */ @SuppressWarnings("unchecked") public static final RapidQuantity createInstance(final TypeRapidQuantity type, final BigDecimal magnitude, final RapidEnum unit) { RapidQuantity quantity = null; final Class<?> clazz = type.getImplementingClass(); if (clazz != null) { try { final Constructor<RapidQuantity> constr = (Constructor<RapidQuantity>) clazz .getConstructor(CONSTR_PARAMTYPES_MAG_UNIT); final Object[] initargs = new Object[2]; initargs[0] = magnitude; initargs[1] = unit; quantity = (RapidQuantity) constr.newInstance(initargs); } catch (NoSuchMethodException e) { throw new RapidBeansRuntimeException("RapidQuantity class \"" + clazz.getName() + "\" does not have a constructor with BigDecimal and RapidEnum", e); } catch (IllegalArgumentException e) { throw new RapidBeansRuntimeException("IllegalArgumentException while trying" + "to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + type.getName() + "\"", e); } catch (InstantiationException e) { throw new RapidBeansRuntimeException("InstantiationException while trying" + "to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + type.getName() + "\"", e); } catch (IllegalAccessException e) { throw new RapidBeansRuntimeException("IllegalAccessException while trying" + "to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + type.getName() + "\"", e); } catch (InvocationTargetException e) { throw new RapidBeansRuntimeException("InvocationTargetException while trying" + " to create instance for quantity class \"" + clazz.getName() + "\", quantity type \"" + type.getName() + "\"", e); } } else { quantity = new GenericQuantity(type, magnitude, unit); } return quantity; } /** * simple constructor. * * @param description * the enum's string representation. */ protected RapidQuantity() { this.magnitude = null; this.unit = null; } /** * constructor. * * @param description * the enum's string representation. */ protected RapidQuantity(final String description) { this(null, description); } /** * constructor. * * @param argType * the RapidQuantity's type * @param description * the enum's string representation. */ protected RapidQuantity(final TypeRapidQuantity type, final String description) { TypeRapidQuantity tp = type; if (tp == null) { tp = this.getType(); } List<String> tokenizedDescr = StringHelper.split(description, " "); switch (tokenizedDescr.size()) { case 0: Object[] oa1 = { description }; throw new ValidationException("invalid.quantity.empty", this, "Invalid empty quantity description \"" + description + "\"", oa1); case 1: this.magnitude = parseMagnitudeOneToken(description); this.unit = parseUnitOneToken(description); break; case 2: try { this.magnitude = new BigDecimal(tokenizedDescr.get(0)); } catch (NumberFormatException e) { throw new ValidationException("invalid.quantity.magnitude", this, "RapidQuantity with invalid magnitude \"" + tokenizedDescr.get(0) + "\"", new Object[] { description, tokenizedDescr.get(0) }); } try { this.unit = tp.getUnitInfo().elementOf(tokenizedDescr.get(1)); } catch (EnumException e) { String validEnums = TypeRapidEnum.format(tp.getUnitInfo().getElements()); throw new ValidationException("invalid.quantity.unit", this, "RapidQuantity with invalid unit \"" + tokenizedDescr.get(1) + "\"", new Object[] { description, tokenizedDescr.get(1), validEnums }); } break; default: Object[] oa3 = { description }; throw new ValidationException("invalid.quantity.format.threeormore", this, "Invalid quantity description \"" + description + "\" with more than two tokens.\n" + "Need two tokens: <magnitude> <unit>", oa3); } } /** * constructor. * * @param argMagnitude * the magnitude * @param argUnit * the unit */ protected RapidQuantity(final BigDecimal argMagnitude, final RapidEnum argUnit) { this.magnitude = argMagnitude; this.unit = argUnit; } /** * to conversion to a string. * * @return the string */ public String toString() { StringBuffer buf = new StringBuffer(); if (this.magnitude == null) { buf.append("null"); } else { buf.append(this.magnitude.toString()); } buf.append(' '); if (this.magnitude == null) { buf.append("null"); } else { buf.append(this.unit.toString()); } return buf.toString(); } /** * Convert to a localized string * * @param locale * the locale * @param minimumFractionDigits * the minimal fraction digits * @param maximumFractionDigits * the maximal fraction digits * * @return the localized string */ public String toStringGui(final RapidBeansLocale locale, final int minimumFractionDigits, final int maximumFractionDigits) { // later on we can construct here with a locale NumberFormat ft = NumberFormat.getInstance(); ft.setMinimumFractionDigits(minimumFractionDigits); ft.setMaximumFractionDigits(minimumFractionDigits); return ft.format(this.getMagnitude()) + " " + this.getUnit().toStringGui(locale); } /** * equals. * * @param o * the object to compare with * * @return if equals or not */ public final boolean equals(final Object o) { if (o == null) { return false; } if (!(o instanceof RapidQuantity)) { return false; } RapidQuantity quant = (RapidQuantity) o; return (this.getUnit() == quant.getUnit() && ((this.getMagnitude() == null && quant.getMagnitude() == null) || (this .getMagnitude() != null && quant.getMagnitude() != null && this.getMagnitude().equals(quant.getMagnitude())))); } /** * @return the quantity's hash code. */ public final int hashCode() { return this.toString().hashCode(); } /** * the comparator. * * @param o * the other quantity * @return -1, 0, 1 if less, equal, greater than */ public int compareTo(final RapidQuantity quant) { int comp = 0; if (this.getType() != quant.getType()) { throw new UtilException("Cannot compare a RapidQuantity against a \"" + quant.getClass().getName() + "\".\n" + "only quantities of same type can be compared against each other."); } if (this.getUnit() == quant.getUnit()) { comp = this.getMagnitude().compareTo(quant.getMagnitude()); } else { try { RapidQuantity cQuant = quant.convert(this.getUnit()); comp = this.getMagnitude().compareTo(cQuant.getMagnitude()); } catch (QuantityConversionNotSupportedException e) { RapidQuantity cThis = this.convert(quant.getUnit()); comp = cThis.getMagnitude().compareTo(quant.getMagnitude()); } } return comp; } /** * the conversion routine. * * @param conversionUnit * the unit to convert into * * @return a new quantity containing the converted value */ public final RapidQuantity convert(final RapidEnum conversionUnit) { RapidQuantity converted = null; try { converted = (RapidQuantity) this.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new UtilException(e.getMessage()); } if (this.getUnit() != conversionUnit) { final BigDecimal conversionFactor = this.getType().getConversionTable() .getConversionFactor(this.unit, conversionUnit); if (conversionFactor == null) { throw new QuantityConversionNotSupportedException("conversion of quantity class \"" + this.getClass().getName() + "\" from Unit \"" + this.unit + "\" to Unit \"" + conversionUnit + "\" is not supported."); } final boolean conversionFactorReciprocal = this.getType().getConversionTable() .getConversionFactorReciprocalFlag(this.unit, conversionUnit); if (conversionFactorReciprocal) { try { converted.magnitude = (BigDecimal) this.magnitude.divide(conversionFactor); } catch (ArithmeticException e) { double dMagnitude = this.magnitude.doubleValue(); double dConversionFactor = conversionFactor.doubleValue(); converted.magnitude = new BigDecimal(dMagnitude / dConversionFactor); } } else { converted.magnitude = (BigDecimal) this.magnitude.multiply(conversionFactor); } converted.unit = conversionUnit; } return converted; } /** * Parser function for quantity magnitudes. To be overwritten for specific * quantity classes. * * @param token * the token * * @return throws an exception */ public BigDecimal parseMagnitudeOneToken(final String token) { Object[] oa = { token }; throw new ValidationException("invalid.quantity.format.one", this, "Invalid quantity description \"" + token + "\" with only one token.\n" + "Need two tokens: <magnitude> <unit>", oa); } /** * Parser function for quantity units. To be overwritten for specific * quantity classes. * * @param token * the token * * @return throws an exception */ public RapidEnum parseUnitOneToken(final String token) { Object[] oa = { token }; throw new ValidationException("invalid.quantity.format.one", this, "Invalid quantity description \"" + token + "\" with only one token.\n" + "Need two tokens: <magnitude> <unit>", oa); } /** * Round the quantity with rounding mode "half up". * * @param fraction * specifies the number of digits behind the decimal sign */ public RapidQuantity round(final int fraction) { return round(fraction, RoundingMode.HALF_UP); } /** * Add the given summand to this. The given summand is converted to this * unit. * * @param summand * the summand * * @return the addition result. */ public RapidQuantity add(final RapidQuantity summand) { RapidQuantity sd = summand; if (this.unit != sd.getUnit()) { sd = sd.convert(this.getUnit()); } return RapidQuantity.createInstance(this.getType(), this.getMagnitude().add(sd.getMagnitude()), this.unit); } /** * Divide this value by the given dividend. The given dividend is converted * to this unit. * * @param dividend * the dividend * * @return the division result. */ public RapidQuantity divide(final RapidQuantity dividend) { RapidQuantity div = dividend; if (this.unit != div.getUnit()) { div = dividend.convert(this.getUnit()); } RapidQuantity result = null; try { result = RapidQuantity.createInstance(this.getType(), this.getMagnitude().divide(div.getMagnitude()), this.unit); } catch (ArithmeticException e) { double d = this.getMagnitude().doubleValue() / div.getMagnitude().doubleValue(); result = RapidQuantity.createInstance(this.getType(), new BigDecimal(d), this.unit); } return result; } /** * Round the quantity. * * @param friction * specifies the number of digits behind the decimal sign * @param roundingMode * rounding mode */ public RapidQuantity round(final int fraction, RoundingMode roundingMode) { if (this.magnitude == null) { throw new IllegalArgumentException("can not round a quantity with null magnitude"); } int precision = 3; double mag = this.magnitude.doubleValue(); while (mag > 10.0) { mag /= 10.0; precision++; } RapidQuantity rounded = RapidQuantity.createInstance(this.getType(), this.magnitude.round(new MathContext(precision, roundingMode)), this.getUnit()); return rounded; } }