001: /**
002: * com.mckoi.util.GeneralParser 30 Oct 1998
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.util;
024:
025: import java.text.CharacterIterator;
026: import java.text.ParseException;
027: import java.math.BigDecimal;
028:
029: /**
030: * This class provides several static convenience functions for parsing
031: * various types of character sequences. In most cases, we use a
032: * CharacterIterator to represent the sequence of characters being parsed.
033: * <p>
034: * @author Tobias Downer
035: */
036:
037: public class GeneralParser {
038:
039: /**
040: * These statics represent some information about how many milliseconds are
041: * in various measures of time.
042: */
043: private static final BigDecimal MILLIS_IN_WEEK = new BigDecimal(7
044: * 24 * 60 * 60 * 1000);
045: private static final BigDecimal MILLIS_IN_DAY = new BigDecimal(
046: 24 * 60 * 60 * 1000);
047: private static final BigDecimal MILLIS_IN_HOUR = new BigDecimal(
048: 60 * 60 * 1000);
049: private static final BigDecimal MILLIS_IN_MINUTE = new BigDecimal(
050: 60 * 1000);
051: private static final BigDecimal MILLIS_IN_SECOND = new BigDecimal(
052: 1000);
053:
054: /**
055: * Parses a string of 0 or more digits and appends the digits into the string
056: * buffer.
057: */
058: public static void parseDigitString(CharacterIterator i,
059: StringBuffer digit_str) {
060: char c = i.current();
061: while (Character.isDigit(c)) {
062: digit_str.append(c);
063: c = i.next();
064: }
065: }
066:
067: /**
068: * Parses a string of 0 or more words and appends the characters into the
069: * string buffer.
070: */
071: public static void parseWordString(CharacterIterator i,
072: StringBuffer word_buffer) {
073: char c = i.current();
074: while (Character.isLetter(c)) {
075: word_buffer.append(c);
076: c = i.next();
077: }
078: }
079:
080: /**
081: * Moves the iterator past any white space. White space is ' ', '\t', '\n'
082: * and '\r'.
083: */
084: public static void skipWhiteSpace(CharacterIterator i) {
085: char c = i.current();
086: while (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
087: c = i.next();
088: }
089: }
090:
091: /**
092: * This assumes there is a decimal number waiting on the iterator. It
093: * parses the decimal and returns the BigDecimal representation. It throws
094: * a GeneralParseException if we are unable to parse the decimal.
095: */
096: public static BigDecimal parseBigDecimal(CharacterIterator i)
097: throws ParseException {
098: boolean done_decimal = false;
099: StringBuffer str_val = new StringBuffer();
100:
101: // We can start with a '-'
102: char c = i.current();
103: if (c == '-') {
104: str_val.append(c);
105: c = i.next();
106: }
107: // We can start or follow with a '.'
108: if (c == '.') {
109: done_decimal = true;
110: str_val.append(c);
111: c = i.next();
112: }
113: // We must be able to parse a digit
114: if (!Character.isDigit(c)) {
115: throw new ParseException("Parsing BigDecimal", i.getIndex());
116: }
117: // Parse the digit string
118: parseDigitString(i, str_val);
119: // Is there a decimal part?
120: c = i.current();
121: if (!done_decimal && c == '.') {
122: str_val.append(c);
123: c = i.next();
124: parseDigitString(i, str_val);
125: }
126:
127: return new BigDecimal(new String(str_val));
128: }
129:
130: /**
131: * Parses a time grammer waiting on the character iterator. The grammer is
132: * quite simple. It allows for us to specify quite precisely some unit of
133: * time measure and convert it to a Java understandable form. It returns the
134: * number of milliseconds that the unit of time represents.
135: * For example, the string '2.5 hours' would return:
136: * 2.5 hours * 60 minutes * 60 seconds * 1000 milliseconds = 9000000
137: * <p>
138: * To construct a valid time measure, you must supply a sequence of time
139: * measurements. The valid time measurements are 'week(s)', 'day(s)',
140: * 'hour(s)', 'minute(s)', 'second(s)', 'millisecond(s)'. To construct a
141: * time, we simply concatinate the measurements together. For example,
142: * '3 days 22 hours 9.5 minutes'
143: * <p>
144: * It accepts any number of time measurements, but not duplicates of the
145: * same.
146: * <p>
147: * The time measures are case insensitive. It is a little lazy how it reads
148: * the grammer. We could for example enter '1 hours 40 second' or even
149: * more extreme, '1 houraboutit 90 secondilianit' both of which are
150: * acceptable!
151: * <p>
152: * This method will keep on parsing the string until the end of the iterator
153: * is reached or a non-numeric time measure is found. It throws a
154: * ParseException if an invalid time measure is found or a number is invalid
155: * (eg. -3 days).
156: * <p>
157: * LOCALE ISSUE: This will likely be a difficult method to localise.
158: */
159: public static BigDecimal parseTimeMeasure(CharacterIterator i)
160: throws ParseException {
161: boolean time_measured = false;
162: BigDecimal time_measure = new BigDecimal(0);
163: boolean[] time_parsed = new boolean[6];
164: StringBuffer word_buffer = new StringBuffer();
165: BigDecimal num;
166:
167: while (true) {
168: // Parse the number
169: skipWhiteSpace(i);
170: try {
171: num = parseBigDecimal(i);
172: } catch (ParseException e) {
173: // If we can't parse a number, then return with the current time if
174: // any time has been parsed.
175: if (time_measured) {
176: return time_measure;
177: } else {
178: throw new ParseException("No time value found", i
179: .getIndex());
180: }
181: }
182: if (num.signum() < 0) {
183: throw new ParseException("Invalid time value: " + num,
184: i.getIndex());
185: }
186:
187: skipWhiteSpace(i);
188:
189: // Parse the time measure
190: word_buffer.setLength(0);
191: parseWordString(i, word_buffer);
192:
193: String str = new String(word_buffer).toLowerCase();
194: if ((str.startsWith("week") || str.equals("w"))
195: && !time_parsed[0]) {
196: time_measure = time_measure.add(num
197: .multiply(MILLIS_IN_WEEK));
198: time_parsed[0] = true;
199: } else if ((str.startsWith("day") || str.equals("d"))
200: && !time_parsed[1]) {
201: time_measure = time_measure.add(num
202: .multiply(MILLIS_IN_DAY));
203: time_parsed[1] = true;
204: } else if ((str.startsWith("hour") || str.startsWith("hr") || str
205: .equals("h"))
206: && !time_parsed[2]) {
207: time_measure = time_measure.add(num
208: .multiply(MILLIS_IN_HOUR));
209: time_parsed[2] = true;
210: } else if ((str.startsWith("minute")
211: || str.startsWith("min") || str.equals("m"))
212: && !time_parsed[3]) {
213: time_measure = time_measure.add(num
214: .multiply(MILLIS_IN_MINUTE));
215: time_parsed[3] = true;
216: } else if ((str.startsWith("second")
217: || str.startsWith("sec") || str.equals("s"))
218: && !time_parsed[4]) {
219: time_measure = time_measure.add(num
220: .multiply(MILLIS_IN_SECOND));
221: time_parsed[4] = true;
222: } else if ((str.startsWith("millisecond") || str
223: .equals("ms"))
224: && !time_parsed[5]) {
225: time_measure = time_measure.add(num);
226: time_parsed[5] = true;
227: } else {
228: throw new ParseException(
229: "Unknown time measure: " + str, i.getIndex());
230: }
231: time_measured = true;
232:
233: }
234:
235: }
236:
237: }
|