1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Copyright 1999-2002,2004,2005 The Apache Software Foundation.
   7  *
   8  * Licensed under the Apache License, Version 2.0 (the "License");
   9  * you may not use this file except in compliance with the License.
  10  * You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.impl.dv.xs;
  22 
  23 import java.math.BigDecimal;
  24 import java.math.BigInteger;
  25 
  26 import javax.xml.datatype.DatatypeConstants;
  27 import javax.xml.datatype.Duration;
  28 
  29 import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeValueException;
  30 import com.sun.org.apache.xerces.internal.impl.dv.ValidationContext;
  31 
  32 /**
  33  * Validator for <duration> datatype (W3C Schema Datatypes)
  34  *
  35  * @xerces.internal
  36  *
  37  * @author Elena Litani
  38  * @author Gopal Sharma, SUN Microsystem Inc.
  39  * @version $Id: DurationDV.java,v 1.7 2010-11-01 04:39:47 joehw Exp $
  40  */
  41 public class DurationDV extends AbstractDateTimeDV {
  42 
  43         public static final int DURATION_TYPE = 0;
  44         public static final int YEARMONTHDURATION_TYPE = 1;
  45         public static final int DAYTIMEDURATION_TYPE = 2;
  46     // order-relation on duration is a partial order. The dates below are used to
  47     // for comparison of 2 durations, based on the fact that
  48     // duration x and y is x<=y iff s+x<=s+y
  49     // see 3.2.6 duration W3C schema datatype specs
  50     //
  51     // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
  52     private final static DateTimeData[] DATETIMES= {
  53         new DateTimeData(1696, 9, 1, 0, 0, 0, 'Z', null, true, null),
  54         new DateTimeData(1697, 2, 1, 0, 0, 0, 'Z', null, true, null),
  55         new DateTimeData(1903, 3, 1, 0, 0, 0, 'Z', null, true, null),
  56         new DateTimeData(1903, 7, 1, 0, 0, 0, 'Z', null, true, null)};
  57 
  58     public Object getActualValue(String content, ValidationContext context) throws InvalidDatatypeValueException{
  59         try{
  60             return parse(content, DURATION_TYPE);
  61         } catch (Exception ex) {
  62             throw new InvalidDatatypeValueException("cvc-datatype-valid.1.2.1", new Object[]{content, "duration"});
  63         }
  64     }
  65 
  66     /**
  67      * Parses, validates and computes normalized version of duration object
  68      *
  69      * @param str    The lexical representation of duration object PnYn MnDTnH nMnS
  70      * @param durationType
  71      * @return normalized date representation
  72      * @exception SchemaDateTimeException Invalid lexical representation
  73      */
  74     protected DateTimeData parse(String str, int durationType) throws SchemaDateTimeException{
  75         int len = str.length();
  76         DateTimeData date= new DateTimeData(str, this);
  77 
  78         int start = 0;
  79         char c=str.charAt(start++);
  80         if ( c!='P' && c!='-' ) {
  81             throw new SchemaDateTimeException();
  82         }
  83         else {
  84             date.utc=(c=='-')?'-':0;
  85             if ( c=='-' && str.charAt(start++)!='P' ) {
  86                 throw new SchemaDateTimeException();
  87             }
  88         }
  89 
  90         int negate = 1;
  91         //negative duration
  92         if ( date.utc=='-' ) {
  93             negate = -1;
  94 
  95         }
  96         //at least one number and designator must be seen after P
  97         boolean designator = false;
  98 
  99         int endDate = indexOf (str, start, len, 'T');
 100         if ( endDate == -1 ) {
 101             endDate = len;
 102         }
 103         else if (durationType == YEARMONTHDURATION_TYPE) {
 104             throw new SchemaDateTimeException();
 105         }
 106 
 107         //find 'Y'
 108         int end = indexOf (str, start, endDate, 'Y');
 109         if ( end!=-1 ) {
 110 
 111             if (durationType == DAYTIMEDURATION_TYPE) {
 112                 throw new SchemaDateTimeException();
 113             }
 114 
 115             //scan year
 116             date.year=negate * parseInt(str,start,end);
 117             start = end+1;
 118             designator = true;
 119         }
 120 
 121         end = indexOf (str, start, endDate, 'M');
 122         if ( end!=-1 ) {
 123 
 124             if (durationType == DAYTIMEDURATION_TYPE) {
 125                 throw new SchemaDateTimeException();
 126             }
 127 
 128             //scan month
 129             date.month=negate * parseInt(str,start,end);
 130             start = end+1;
 131             designator = true;
 132         }
 133 
 134         end = indexOf (str, start, endDate, 'D');
 135         if ( end!=-1 ) {
 136 
 137             if(durationType == YEARMONTHDURATION_TYPE) {
 138                 throw new SchemaDateTimeException();
 139             }
 140 
 141             //scan day
 142             date.day=negate * parseInt(str,start,end);
 143             start = end+1;
 144             designator = true;
 145         }
 146 
 147         if ( len == endDate && start!=len ) {
 148             throw new SchemaDateTimeException();
 149         }
 150         if ( len !=endDate ) {
 151 
 152             //scan hours, minutes, seconds
 153             //REVISIT: can any item include a decimal fraction or only seconds?
 154             //
 155 
 156             end = indexOf (str, ++start, len, 'H');
 157             if ( end!=-1 ) {
 158                 //scan hours
 159                 date.hour=negate * parseInt(str,start,end);
 160                 start=end+1;
 161                 designator = true;
 162             }
 163 
 164             end = indexOf (str, start, len, 'M');
 165             if ( end!=-1 ) {
 166                 //scan min
 167                 date.minute=negate * parseInt(str,start,end);
 168                 start=end+1;
 169                 designator = true;
 170             }
 171 
 172             end = indexOf (str, start, len, 'S');
 173             if ( end!=-1 ) {
 174                 //scan seconds
 175                 date.second = negate * parseSecond(str, start, end);
 176                 start=end+1;
 177                 designator = true;
 178             }
 179             // no additional data shouls appear after last item
 180             // P1Y1M1DT is illigal value as well
 181             if ( start != len || str.charAt(--start)=='T' ) {
 182                 throw new SchemaDateTimeException();
 183             }
 184         }
 185 
 186         if ( !designator ) {
 187             throw new SchemaDateTimeException();
 188         }
 189 
 190         return date;
 191     }
 192 
 193     /**
 194      * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
 195      *
 196      * @param date1  Unnormalized duration
 197      * @param date2  Unnormalized duration
 198      * @param strict (min/max)Exclusive strict == true ( LESS_THAN ) or ( GREATER_THAN )
 199      *               (min/max)Inclusive strict == false (LESS_EQUAL) or (GREATER_EQUAL)
 200      * @return INDETERMINATE if the order relationship between date1 and date2 is indeterminate.
 201      * EQUAL if the order relation between date1 and date2 is EQUAL.
 202      * If the strict parameter is true, return LESS_THAN if date1 is less than date2 and
 203      * return GREATER_THAN if date1 is greater than date2.
 204      * If the strict parameter is false, return LESS_THAN if date1 is less than OR equal to date2 and
 205      * return GREATER_THAN if date1 is greater than OR equal to date2
 206      */
 207     protected  short compareDates(DateTimeData date1, DateTimeData date2, boolean strict) {
 208 
 209         //REVISIT: this is unoptimazed vs of comparing 2 durations
 210         //         Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
 211         //
 212 
 213         //add constA to both durations
 214         short resultA, resultB= INDETERMINATE;
 215         //try and see if the objects are equal
 216         resultA = compareOrder (date1, date2);
 217         if ( resultA == 0 ) {
 218             return 0;
 219         }
 220 
 221         DateTimeData[] result = new DateTimeData[2];
 222         result[0] = new DateTimeData(null, this);
 223         result[1] = new DateTimeData(null, this);
 224 
 225         //long comparison algorithm is required
 226         DateTimeData tempA = addDuration (date1, DATETIMES[0], result[0]);
 227         DateTimeData tempB = addDuration (date2, DATETIMES[0], result[1]);
 228         resultA =  compareOrder(tempA, tempB);
 229         if ( resultA == INDETERMINATE ) {
 230             return INDETERMINATE;
 231         }
 232 
 233         tempA = addDuration(date1, DATETIMES[1], result[0]);
 234         tempB = addDuration(date2, DATETIMES[1], result[1]);
 235         resultB = compareOrder(tempA, tempB);
 236         resultA = compareResults(resultA, resultB, strict);
 237         if (resultA == INDETERMINATE) {
 238             return INDETERMINATE;
 239         }
 240 
 241         tempA = addDuration(date1, DATETIMES[2], result[0]);
 242         tempB = addDuration(date2, DATETIMES[2], result[1]);
 243         resultB = compareOrder(tempA, tempB);
 244         resultA = compareResults(resultA, resultB, strict);
 245         if (resultA == INDETERMINATE) {
 246             return INDETERMINATE;
 247         }
 248 
 249         tempA = addDuration(date1, DATETIMES[3], result[0]);
 250         tempB = addDuration(date2, DATETIMES[3], result[1]);
 251         resultB = compareOrder(tempA, tempB);
 252         resultA = compareResults(resultA, resultB, strict);
 253 
 254         return resultA;
 255     }
 256 
 257     private short compareResults(short resultA, short resultB, boolean strict){
 258 
 259       if ( resultB == INDETERMINATE ) {
 260             return INDETERMINATE;
 261         }
 262         else if ( resultA!=resultB && strict ) {
 263             return INDETERMINATE;
 264         }
 265         else if ( resultA!=resultB && !strict ) {
 266             if ( resultA!=0 && resultB!=0 ) {
 267                 return INDETERMINATE;
 268             }
 269             else {
 270                 return (resultA!=0)?resultA:resultB;
 271             }
 272         }
 273         return resultA;
 274     }
 275 
 276     private DateTimeData addDuration(DateTimeData date, DateTimeData addto, DateTimeData duration) {
 277 
 278         //REVISIT: some code could be shared between normalize() and this method,
 279         //         however is it worth moving it? The structures are different...
 280         //
 281 
 282         resetDateObj(duration);
 283         //add months (may be modified additionaly below)
 284         int temp = addto.month + date.month;
 285         duration.month = modulo (temp, 1, 13);
 286         int carry = fQuotient (temp, 1, 13);
 287 
 288         //add years (may be modified additionaly below)
 289         duration.year=addto.year + date.year + carry;
 290 
 291         //add seconds
 292         double dtemp = addto.second + date.second;
 293         carry = (int)Math.floor(dtemp/60);
 294         duration.second = dtemp - carry*60;
 295 
 296         //add minutes
 297         temp = addto.minute +date.minute + carry;
 298         carry = fQuotient (temp, 60);
 299         duration.minute= mod(temp, 60, carry);
 300 
 301         //add hours
 302         temp = addto.hour + date.hour + carry;
 303         carry = fQuotient(temp, 24);
 304         duration.hour = mod(temp, 24, carry);
 305 
 306 
 307         duration.day=addto.day + date.day + carry;
 308 
 309         while ( true ) {
 310 
 311             temp=maxDayInMonthFor(duration.year, duration.month);
 312             if ( duration.day < 1 ) { //original duration was negative
 313                 duration.day = duration.day + maxDayInMonthFor(duration.year, duration.month-1);
 314                 carry=-1;
 315             }
 316             else if ( duration.day > temp ) {
 317                 duration.day = duration.day - temp;
 318                 carry=1;
 319             }
 320             else {
 321                 break;
 322             }
 323             temp = duration.month+carry;
 324             duration.month = modulo(temp, 1, 13);
 325             duration.year = duration.year+fQuotient(temp, 1, 13);
 326         }
 327 
 328         duration.utc='Z';
 329         return duration;
 330     }
 331 
 332     protected double parseSecond(String buffer, int start, int end)
 333         throws NumberFormatException {
 334         int dot = -1;
 335         for (int i = start; i < end; i++) {
 336             char ch = buffer.charAt(i);
 337             if (ch == '.')
 338                 dot = i;
 339             else if (ch > '9' || ch < '0')
 340                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 341         }
 342         if (dot+1 == end) {
 343             throw new NumberFormatException("'" + buffer + "' has wrong format");
 344         }
 345         double value = Double.parseDouble(buffer.substring(start, end));
 346         if (value == Double.POSITIVE_INFINITY) {
 347             throw new NumberFormatException("'" + buffer + "' has wrong format");
 348         }
 349         return value;
 350     }
 351 
 352     protected String dateToString(DateTimeData date) {
 353         StringBuffer message = new StringBuffer(30);
 354         if ( date.year<0 || date.month<0 || date.day<0
 355                 || date.hour<0 || date.minute<0 || date.second<0) {
 356             message.append('-');
 357         }
 358         message.append('P');
 359         message.append((date.year < 0?-1:1) * date.year);
 360         message.append('Y');
 361         message.append((date.month < 0?-1:1) * date.month);
 362         message.append('M');
 363         message.append((date.day < 0?-1:1) * date.day);
 364         message.append('D');
 365         message.append('T');
 366         message.append((date.hour < 0?-1:1) * date.hour);
 367         message.append('H');
 368         message.append((date.minute < 0?-1:1) * date.minute);
 369         message.append('M');
 370         append2(message, (date.second < 0?-1:1) * date.second);
 371         message.append('S');
 372 
 373         return message.toString();
 374     }
 375 
 376     protected Duration getDuration(DateTimeData date) {
 377         int sign = 1;
 378         if ( date.year<0 || date.month<0 || date.day<0
 379                 || date.hour<0 || date.minute<0 || date.second<0) {
 380             sign = -1;
 381         }
 382         return datatypeFactory.newDuration(sign == 1,
 383                 date.year != DatatypeConstants.FIELD_UNDEFINED?BigInteger.valueOf(sign*date.year):null,
 384                 date.month != DatatypeConstants.FIELD_UNDEFINED?BigInteger.valueOf(sign*date.month):null,
 385                 date.day != DatatypeConstants.FIELD_UNDEFINED?BigInteger.valueOf(sign*date.day):null,
 386                 date.hour != DatatypeConstants.FIELD_UNDEFINED?BigInteger.valueOf(sign*date.hour):null,
 387                 date.minute != DatatypeConstants.FIELD_UNDEFINED?BigInteger.valueOf(sign*date.minute):null,
 388                 date.second != DatatypeConstants.FIELD_UNDEFINED?new BigDecimal(String.valueOf(sign*date.second)):null);
 389     }
 390 }