001: /*
002:
003: Derby - Class org.apache.derby.iapi.types.DateTimeParser
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.types;
023:
024: import org.apache.derby.iapi.reference.SQLState;
025: import org.apache.derby.iapi.error.StandardException;
026:
027: /**
028: * This class provides a simple regular expression parser for standard format dates, times, and timestamps
029: */
030: class DateTimeParser {
031:
032: private String str;
033: private String trimmedString;
034: private int len;
035: private int fieldStart;
036: private char currentSeparator;
037:
038: DateTimeParser(String str) {
039: this .str = str;
040: len = str.length();
041: }
042:
043: /**
044: * Parse the next integer.
045: *
046: * @param maxDigits the maximum number of digits
047: * @param truncationAllowed If true then leading zeroes may be ommitted. If false then the integer must be
048: * exactly ndigits long.
049: * @param separator The separator at the end of the integer. If zero then the integer must be at the end of the string
050: * but may be followed by spaces.
051: * @param isFraction If true then the returned integer will be multiplied by 10**(maxDigits - actualDigitCount)
052: *
053: * @return the integer.
054: *
055: * @exception StandardException invalid syntax.
056: */
057: int parseInt(int maxDigits, boolean truncationAllowed,
058: char[] separator, boolean isFraction)
059: throws StandardException {
060: int number = 0;
061: char c;
062: int digitCount = 0;
063:
064: for (; fieldStart < len; fieldStart++) {
065: c = str.charAt(fieldStart);
066: if (Character.isDigit(c)) {
067: if (digitCount >= maxDigits)
068: throw StandardException
069: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
070: digitCount++;
071: number = number * 10 + Character.digit(c, 10);
072: } else
073: break;
074: }
075: if (truncationAllowed ? (digitCount == 0 && !isFraction)
076: : (digitCount != maxDigits))
077: throw StandardException
078: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
079:
080: updateCurrentSeparator();
081:
082: if (separator == null) {
083: // separator not required
084: if (fieldStart < len)
085: fieldStart++;
086: } else {
087: int sepIdx;
088: for (sepIdx = 0; sepIdx < separator.length; sepIdx++) {
089: if (separator[sepIdx] != 0) {
090: if (currentSeparator == separator[sepIdx]) {
091: fieldStart++;
092: break;
093: }
094: } else {
095: // separator[sepIdx] matches the end of the string
096: int j;
097: for (j = fieldStart; j < len; j++) {
098: if (str.charAt(j) != ' ')
099: break;
100: }
101: if (j == len) {
102: fieldStart = j;
103: break;
104: }
105: }
106: }
107: if (sepIdx >= separator.length)
108: throw StandardException
109: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
110: }
111:
112: if (isFraction) {
113: for (int i = digitCount; i < maxDigits; i++)
114: number *= 10;
115: }
116: return number;
117: } // end of parseInt
118:
119: /**
120: * Determine if the next characters are one of a choice of strings.
121: *
122: * @param choices An array of strings.
123: *
124: * @return An index in choices.
125: *
126: * @exception StandardException if the next characters are not in choices.
127: */
128: int parseChoice(String[] choices) throws StandardException {
129: for (int choiceIdx = 0; choiceIdx < choices.length; choiceIdx++) {
130: String choice = choices[choiceIdx];
131: int choiceLen = choice.length();
132: if (fieldStart + choiceLen <= len) {
133: int i;
134: for (i = 0; i < choiceLen; i++) {
135: if (choice.charAt(i) != str.charAt(fieldStart + i))
136: break;
137: }
138: if (i == choiceLen) {
139: fieldStart += choiceLen;
140: updateCurrentSeparator();
141: return choiceIdx;
142: }
143: }
144: }
145: throw StandardException
146: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
147: } // end of parseChoice
148:
149: private void updateCurrentSeparator() {
150: if (fieldStart >= len)
151: currentSeparator = 0;
152: else {
153: currentSeparator = str.charAt(fieldStart);
154: if (currentSeparator == ' ') {
155: // Trailing spaces are always OK. See if we are really at the end
156: for (int i = fieldStart + 1; i < len; i++) {
157: if (str.charAt(i) != ' ')
158: return;
159: }
160: currentSeparator = 0;
161: fieldStart = len;
162: }
163: }
164: } // end of updateCurrentSeparator
165:
166: /**
167: * Check that we are at the end of the string: that the rest of the characters, if any, are blanks.
168: *
169: * @return the original string with trailing blanks trimmed off.
170: * @exception StandardException if there are more non-blank characters.
171: */
172: String checkEnd() throws StandardException {
173: int end = fieldStart;
174: for (; fieldStart < len; fieldStart++) {
175: if (str.charAt(fieldStart) != ' ')
176: throw StandardException
177: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
178: }
179: currentSeparator = 0;
180: while (end > 0 && str.charAt(end - 1) == ' ')
181: end--;
182: trimmedString = (end == len) ? str : str.substring(0, end);
183: return trimmedString;
184: } // end of checkEnd
185:
186: /**
187: * Get the parsed string with trailing blanks removed. <b>This method is only valid after checkEnd
188: * has been called.</b>
189: *
190: * @return The string with trailing blanks removed.
191: */
192: String getTrimmedString() {
193: return trimmedString;
194: }
195:
196: /**
197: * @return the next separator, 0 if there are none
198: */
199: char nextSeparator() {
200: for (int i = fieldStart + 1; i < len; i++) {
201: char c = str.charAt(i);
202: if (!Character.isLetterOrDigit(c))
203: return c;
204: }
205: return 0;
206: }
207:
208: /**
209: * @return the separator between the last parsed integer and the next integer, 0 if the parser is at
210: * the end of the string.
211: */
212: char getCurrentSeparator() {
213: return currentSeparator;
214: }
215: }
|