001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.xerces.impl.dv.xs;
019:
020: import java.math.BigDecimal;
021: import java.math.BigInteger;
022:
023: import javax.xml.datatype.DatatypeConstants;
024: import javax.xml.datatype.Duration;
025:
026: import org.apache.xerces.impl.dv.InvalidDatatypeValueException;
027: import org.apache.xerces.impl.dv.ValidationContext;
028:
029: /**
030: * Validator for <duration> datatype (W3C Schema Datatypes)
031: *
032: * @xerces.internal
033: *
034: * @author Elena Litani
035: * @author Gopal Sharma, SUN Microsystem Inc.
036: * @version $Id: DurationDV.java 517284 2007-03-12 17:03:54Z mrglavas $
037: */
038: public class DurationDV extends AbstractDateTimeDV {
039:
040: public static final int DURATION_TYPE = 0;
041: public static final int YEARMONTHDURATION_TYPE = 1;
042: public static final int DAYTIMEDURATION_TYPE = 2;
043: // order-relation on duration is a partial order. The dates below are used to
044: // for comparison of 2 durations, based on the fact that
045: // duration x and y is x<=y iff s+x<=s+y
046: // see 3.2.6 duration W3C schema datatype specs
047: //
048: // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
049: private final static DateTimeData[] DATETIMES = {
050: new DateTimeData(1696, 9, 1, 0, 0, 0, 'Z', null, true, null),
051: new DateTimeData(1697, 2, 1, 0, 0, 0, 'Z', null, true, null),
052: new DateTimeData(1903, 3, 1, 0, 0, 0, 'Z', null, true, null),
053: new DateTimeData(1903, 7, 1, 0, 0, 0, 'Z', null, true, null) };
054:
055: public Object getActualValue(String content,
056: ValidationContext context)
057: throws InvalidDatatypeValueException {
058: try {
059: return parse(content, DURATION_TYPE);
060: } catch (Exception ex) {
061: throw new InvalidDatatypeValueException(
062: "cvc-datatype-valid.1.2.1", new Object[] { content,
063: "duration" });
064: }
065: }
066:
067: /**
068: * Parses, validates and computes normalized version of duration object
069: *
070: * @param str The lexical representation of duration object PnYn MnDTnH nMnS
071: * @param durationType
072: * @return normalized date representation
073: * @exception SchemaDateTimeException Invalid lexical representation
074: */
075: protected DateTimeData parse(String str, int durationType)
076: throws SchemaDateTimeException {
077: int len = str.length();
078: DateTimeData date = new DateTimeData(str, this );
079:
080: int start = 0;
081: char c = str.charAt(start++);
082: if (c != 'P' && c != '-') {
083: throw new SchemaDateTimeException();
084: } else {
085: date.utc = (c == '-') ? '-' : 0;
086: if (c == '-' && str.charAt(start++) != 'P') {
087: throw new SchemaDateTimeException();
088: }
089: }
090:
091: int negate = 1;
092: //negative duration
093: if (date.utc == '-') {
094: negate = -1;
095:
096: }
097: //at least one number and designator must be seen after P
098: boolean designator = false;
099:
100: int endDate = indexOf(str, start, len, 'T');
101: if (endDate == -1) {
102: endDate = len;
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,
208: DateTimeData date2, boolean strict) {
209:
210: //REVISIT: this is unoptimazed vs of comparing 2 durations
211: // Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
212: //
213:
214: //add constA to both durations
215: short resultA, resultB = INDETERMINATE;
216: //try and see if the objects are equal
217: resultA = compareOrder(date1, date2);
218: if (resultA == 0) {
219: return 0;
220: }
221:
222: DateTimeData[] result = new DateTimeData[2];
223: result[0] = new DateTimeData(null, this );
224: result[1] = new DateTimeData(null, this );
225:
226: //long comparison algorithm is required
227: DateTimeData tempA = addDuration(date1, DATETIMES[0], result[0]);
228: DateTimeData tempB = addDuration(date2, DATETIMES[0], result[1]);
229: resultA = compareOrder(tempA, tempB);
230: if (resultA == INDETERMINATE) {
231: return INDETERMINATE;
232: }
233:
234: tempA = addDuration(date1, DATETIMES[1], result[0]);
235: tempB = addDuration(date2, DATETIMES[1], result[1]);
236: resultB = compareOrder(tempA, tempB);
237: resultA = compareResults(resultA, resultB, strict);
238: if (resultA == INDETERMINATE) {
239: return INDETERMINATE;
240: }
241:
242: tempA = addDuration(date1, DATETIMES[2], result[0]);
243: tempB = addDuration(date2, DATETIMES[2], result[1]);
244: resultB = compareOrder(tempA, tempB);
245: resultA = compareResults(resultA, resultB, strict);
246: if (resultA == INDETERMINATE) {
247: return INDETERMINATE;
248: }
249:
250: tempA = addDuration(date1, DATETIMES[3], result[0]);
251: tempB = addDuration(date2, DATETIMES[3], result[1]);
252: resultB = compareOrder(tempA, tempB);
253: resultA = compareResults(resultA, resultB, strict);
254:
255: return resultA;
256: }
257:
258: private short compareResults(short resultA, short resultB,
259: boolean strict) {
260:
261: if (resultB == INDETERMINATE) {
262: return INDETERMINATE;
263: } else if (resultA != resultB && strict) {
264: return INDETERMINATE;
265: } else if (resultA != resultB && !strict) {
266: if (resultA != 0 && resultB != 0) {
267: return INDETERMINATE;
268: } else {
269: return (resultA != 0) ? resultA : resultB;
270: }
271: }
272: return resultA;
273: }
274:
275: private DateTimeData addDuration(DateTimeData date,
276: 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: duration.day = addto.day + date.day + carry;
307:
308: while (true) {
309:
310: temp = maxDayInMonthFor(duration.year, duration.month);
311: if (duration.day < 1) { //original duration was negative
312: duration.day = duration.day
313: + maxDayInMonthFor(duration.year,
314: duration.month - 1);
315: carry = -1;
316: } else if (duration.day > temp) {
317: duration.day = duration.day - temp;
318: carry = 1;
319: } else {
320: break;
321: }
322: temp = duration.month + carry;
323: duration.month = modulo(temp, 1, 13);
324: duration.year = duration.year + fQuotient(temp, 1, 13);
325: }
326:
327: duration.utc = 'Z';
328: return duration;
329: }
330:
331: protected double parseSecond(String buffer, int start, int end)
332: throws NumberFormatException {
333: int dot = -1;
334: for (int i = start; i < end; i++) {
335: char ch = buffer.charAt(i);
336: if (ch == '.')
337: dot = i;
338: else if (ch > '9' || ch < '0')
339: throw new NumberFormatException("'" + buffer
340: + "' has wrong format");
341: }
342: if (dot + 1 == end) {
343: throw new NumberFormatException("'" + buffer
344: + "' has wrong format");
345: }
346: double value = Double.parseDouble(buffer.substring(start, end));
347: if (value == Double.POSITIVE_INFINITY) {
348: throw new NumberFormatException("'" + buffer
349: + "' has wrong format");
350: }
351: return value;
352: }
353:
354: protected String dateToString(DateTimeData date) {
355: StringBuffer message = new StringBuffer(30);
356: if (date.year < 0 || date.month < 0 || date.day < 0
357: || date.hour < 0 || date.minute < 0 || date.second < 0) {
358: message.append('-');
359: }
360: message.append('P');
361: message.append((date.year < 0 ? -1 : 1) * date.year);
362: message.append('Y');
363: message.append((date.month < 0 ? -1 : 1) * date.month);
364: message.append('M');
365: message.append((date.day < 0 ? -1 : 1) * date.day);
366: message.append('D');
367: message.append('T');
368: message.append((date.hour < 0 ? -1 : 1) * date.hour);
369: message.append('H');
370: message.append((date.minute < 0 ? -1 : 1) * date.minute);
371: message.append('M');
372: append2(message, (date.second < 0 ? -1 : 1) * date.second);
373: message.append('S');
374:
375: return message.toString();
376: }
377:
378: protected Duration getDuration(DateTimeData date) {
379: int sign = 1;
380: if (date.year < 0 || date.month < 0 || date.day < 0
381: || date.hour < 0 || date.minute < 0 || date.second < 0) {
382: sign = -1;
383: }
384: return factory
385: .newDuration(
386: sign == 1,
387: date.year != DatatypeConstants.FIELD_UNDEFINED ? BigInteger
388: .valueOf(sign * date.year)
389: : null,
390: date.month != DatatypeConstants.FIELD_UNDEFINED ? BigInteger
391: .valueOf(sign * date.month)
392: : null,
393: date.day != DatatypeConstants.FIELD_UNDEFINED ? BigInteger
394: .valueOf(sign * date.day)
395: : null,
396: date.hour != DatatypeConstants.FIELD_UNDEFINED ? BigInteger
397: .valueOf(sign * date.hour)
398: : null,
399: date.minute != DatatypeConstants.FIELD_UNDEFINED ? BigInteger
400: .valueOf(sign * date.minute)
401: : null,
402: date.second != DatatypeConstants.FIELD_UNDEFINED ? new BigDecimal(
403: String.valueOf(sign * date.second))
404: : null);
405: }
406: }
|