/*
* 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.ErrorCodes;
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);
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(ErrorCodes.XPTY0004, "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 (final IllegalArgumentException e) {
throw new XPathException(ErrorCodes.FORG0001, "cannot construct " + Type.getTypeName(Type.YEAR_MONTH_DURATION) +
" from \"" + str + "\"");
}
}
public DurationValue wrap() {
return this;
}
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() {
final 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(ErrorCodes.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 (final 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(ErrorCodes.FOCA0005, "Operand is not a number");
}
//If $arg2 is positive or negative infinity, the result overflows
if (((NumericValue)other).isInfinite()) {
throw new XPathException(ErrorCodes.FODT0002, "Multiplication by infinity overflow");
}
}
final BigDecimal factor = numberToBigDecimal(other, "Operand to mult should be of numeric type; got: ");
final boolean isFactorNegative = factor.signum() < 0;
final YearMonthDurationValue product = fromDecimalMonths(
new BigDecimal(monthsValueSigned())
.multiply(factor.abs())
.setScale(0, (isFactorNegative)? BigDecimal.ROUND_HALF_DOWN : 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(ErrorCodes.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(ErrorCodes.FODT0002, "Division by zero overflow");
}
}
final BigDecimal divisor = numberToBigDecimal(other, "Can not divide xdt:yearMonthDuration by '" + Type.getTypeName(other.getType())+ "'");
final boolean isDivisorNegative = divisor.signum() < 0;
final YearMonthDurationValue quotient = fromDecimalMonths(
new BigDecimal(monthsValueSigned())
.divide(divisor.abs(), 0, (isDivisorNegative)? BigDecimal.ROUND_HALF_DOWN : BigDecimal.ROUND_HALF_UP));
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(ErrorCodes.FORG0006,
"value of type " + Type.getTypeName(getType()) +
" has no boolean value.");
}
}