/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 Wolfgang M. Meier * wolfgang@exist-db.org * http://exist.sourceforge.net * * 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 2 * 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 copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.xquery.value; import java.math.BigDecimal; import java.math.BigInteger; import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.Duration; import org.exist.util.FastStringBuffer; import org.exist.xquery.XPathException; /** * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a> */ public class YearMonthDurationValue extends OrderedDurationValue { public static final Duration CANONICAL_ZERO_DURATION = TimeUtils.getInstance().newDuration(true, null, BigInteger.ZERO, null, null, null, null); YearMonthDurationValue(Duration duration) throws XPathException { super(duration); //Take care : not the same one than above ! //TODO : sort this out ! test case : //xs:yearMonthDuration("P1000Y6M") + xs:yearMonthDuration("P0Y0M") if (!duration.equals(DurationValue.CANONICAL_ZERO_DURATION)) { if (duration.isSet(DatatypeConstants.DAYS) || duration.isSet(DatatypeConstants.HOURS) || duration.isSet(DatatypeConstants.MINUTES) || //Always set ! //!duration.getField(DatatypeConstants.SECONDS).equals(BigInteger.ZERO)) duration.isSet(DatatypeConstants.SECONDS)) throw new XPathException("The value '" + duration + "' is not an " + Type.getTypeName(getType()) + " since it specifies days, hours, minutes or seconds values"); } } public YearMonthDurationValue(String str) throws XPathException { this(createDurationYearMonth(str)); } private static Duration createDurationYearMonth(String str) throws XPathException { try { return TimeUtils.getInstance().newDurationYearMonth(StringValue.trimWhitespace(str)); } catch (IllegalArgumentException e) { throw new XPathException("FORG0001: cannot construct " + Type.getTypeName(Type.YEAR_MONTH_DURATION) + " from \"" + str + "\""); } } protected Duration canonicalZeroDuration() { return CANONICAL_ZERO_DURATION; } public int getValue() { return duration.getSign() * (duration.getYears() * 12 + duration.getMonths()); } public int getType() { return Type.YEAR_MONTH_DURATION; } public String getStringValue() { FastStringBuffer sb = new FastStringBuffer(32); if (getCanonicalDuration().getSign() < 0) { sb.append('-'); } sb.append('P'); if (getCanonicalDuration().getYears() != 0) { sb.append(getCanonicalDuration().getYears() + "Y"); } if (getCanonicalDuration().getMonths() !=0 || getCanonicalDuration().getYears() == 0) { sb.append(getCanonicalDuration().getMonths() + "M"); } return sb.toString(); } public AtomicValue convertTo(int requiredType) throws XPathException { switch (requiredType) { case Type.ITEM : case Type.ATOMIC : case Type.YEAR_MONTH_DURATION : return this; case Type.STRING : return new StringValue(getStringValue()); case Type.DURATION : return new DurationValue(TimeUtils.getInstance().newDuration( duration.getSign() >= 0, (BigInteger) duration.getField(DatatypeConstants.YEARS), (BigInteger) duration.getField(DatatypeConstants.MONTHS), null, null, null, null )); case Type.DAY_TIME_DURATION: return new DayTimeDurationValue(DayTimeDurationValue.CANONICAL_ZERO_DURATION); //case Type.DOUBLE: //return new DoubleValue(monthsValueSigned().doubleValue()); //return new DoubleValue(Double.NaN); //case Type.DECIMAL: //return new DecimalValue(monthsValueSigned().doubleValue()); case Type.UNTYPED_ATOMIC : return new UntypedAtomicValue(getStringValue()); default : throw new XPathException( "XPTY0004: cannot cast 'xs:yearMonthDuration(\"" + getStringValue() + "\")' to " + Type.getTypeName(requiredType)); } } protected DurationValue createSameKind(Duration dur) throws XPathException { return new YearMonthDurationValue(dur); } public ComputableValue plus(ComputableValue other) throws XPathException { try { if (other.getType() == Type.TIME) throw new IllegalArgumentException(); return super.plus(other); } catch (IllegalArgumentException e) { throw new XPathException( "Operand to plus should be of type xdt:yearMonthDuration, xs:date, " + "or xs:dateTime; got: " + Type.getTypeName(other.getType())); } } public ComputableValue mult(ComputableValue other) throws XPathException { if (other instanceof NumericValue) { //If $arg2 is NaN an error is raised [err:FOCA0005] if (((NumericValue)other).isNaN()) { throw new XPathException("FOCA0005: Operand is not a number"); } //If $arg2 is positive or negative infinity, the result overflows if (((NumericValue)other).isInfinite()) { throw new XPathException("FODT0002: Multiplication by infinity overflow"); } } BigDecimal factor = numberToBigDecimal(other, "Operand to mult should be of numeric type; got: "); boolean isFactorNegative = factor.signum() < 0; YearMonthDurationValue product = fromDecimalMonths( new BigDecimal(monthsValueSigned()) .multiply(factor.abs()) .setScale(0, BigDecimal.ROUND_HALF_UP) ); if (isFactorNegative) return product.negate(); return product; } public ComputableValue div(ComputableValue other) throws XPathException { if (other.getType() == Type.YEAR_MONTH_DURATION) { return new IntegerValue(getValue()).div(new IntegerValue(((YearMonthDurationValue) other).getValue())); } if (other instanceof NumericValue) { if (((NumericValue)other).isNaN()) { throw new XPathException("FOCA0005: Operand is not a number"); } if (((NumericValue)other).isInfinite()) { return new YearMonthDurationValue("P0M"); } //If $arg2 is positive or negative zero, the result overflows and is handled as discussed in 10.1.1 Limits and Precision if (((NumericValue)other).isZero()) { throw new XPathException("FODT0002: Division by zero overflow"); } } BigDecimal divisor = numberToBigDecimal(other, "Can not divide xdt:yearMonthDuration by '" + Type.getTypeName(other.getType())+ "'"); boolean isDivisorNegative = divisor.signum() < 0; YearMonthDurationValue quotient = fromDecimalMonths( new BigDecimal(monthsValueSigned()) .divide(divisor.abs(), 20, BigDecimal.ROUND_HALF_EVEN)); if (isDivisorNegative) return quotient.negate(); return new YearMonthDurationValue(quotient.getCanonicalDuration()); } private YearMonthDurationValue fromDecimalMonths(BigDecimal x) throws XPathException { return new YearMonthDurationValue(TimeUtils.getInstance().newDurationYearMonth( x.signum() >= 0, null, x.toBigInteger())); } public boolean effectiveBooleanValue() throws XPathException { throw new XPathException("FORG0006: value of type " + Type.getTypeName(getType()) + " has no boolean value."); } }