/*
* 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.");
}
}