1 /***
2 * Copyright (c) 2003, 2004, Chess iT
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * - Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * - Neither the name of Chess iT, nor the names of its contributors may be used
16 * to endorse or promote products derived from this software without specific
17 * prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 */
32 package nl.chess.it.util.config;
33
34 import java.text.NumberFormat;
35 import java.text.ParsePosition;
36 import java.util.ArrayList;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.StringTokenizer;
41
42
43 /***
44 * Helper class that parses a String and interprets it as a duration.
45 *
46 * @author Guus Bosman (Chess iT)
47 * @version $Revision: 1.1.1.1 $
48 */
49 class DurationParser {
50 /***
51 * Main parse method.
52 *
53 * @param key Is only used to display better error messages if necessary.
54 * @param value String to parse. Cannot be <code>null</code>.
55 *
56 * @return A duration (long) in milliseconds. Never a negative number.
57 */
58 public long getDurationInMs(String key, String value)
59 throws ConfigurationException {
60 if (value == null) {
61 throw new NullPointerException("value cannot be null here.");
62 }
63
64 /***
65 * Get rid of all comma's and connecting words. Makes parsing SO much easier.
66 */
67 value = value.replaceAll(",", "");
68 value = value.replaceAll("and", "");
69
70 if (value.endsWith(".")) {
71 value = value.substring(0, value.length() - 1);
72 }
73
74 try {
75 /***
76 * Convert the String to a List of token.
77 */
78 List tokens = parseTokens(value);
79
80 /***
81 * Parse as numbers...
82 */
83 List realValues = convertToRealNumbers(tokens);
84
85 /***
86 * ...and add all the numbers found to each other (taken Units into account).
87 */
88 double intermediateResult = calculateDuration(realValues);
89
90 return (long) intermediateResult;
91 }
92 catch (InvalidPropertyException e) {
93
94 e.setExpectedtype("duration");
95 e.setValue(value);
96 e.setPropertyname(key);
97 throw e;
98 }
99 }
100
101 private List parseTokens(String stringValue) {
102 StringTokenizer st = new StringTokenizer(stringValue);
103 List tokens = new ArrayList();
104
105 while (st.hasMoreTokens()) {
106 String currentToken = st.nextToken();
107 currentToken = currentToken.trim();
108
109
110 ParsePosition pp = new ParsePosition(0);
111
112 Number number = NumberFormat.getInstance(Locale.US).parse(currentToken, pp);
113 String leftOver = currentToken.substring(pp.getIndex());
114
115 if (number != null) {
116 tokens.add(number);
117 }
118
119 if ((leftOver != null) && (!leftOver.trim().equals(""))) {
120 tokens.add(leftOver);
121 }
122 }
123
124 return tokens;
125 }
126
127 /***
128 * Where applicable, convert string representations to Number objects (i.e. replace "4" with new Integer(4), and "two" with new
129 * Integer(2).
130 *
131 * @param tokens List to convert. Cannot be <code>null</code>.
132 *
133 * @return New List with Numbers and Strings.
134 */
135 private List convertToRealNumbers(List tokens) {
136 List realValues = new ArrayList();
137
138 for (Iterator iter = tokens.iterator(); iter.hasNext();) {
139 Object element = iter.next();
140
141 if (element instanceof String) {
142 Number currentNumberValue = getNumberValue((String) element);
143
144 if (currentNumberValue != null) {
145 realValues.add(currentNumberValue);
146 }
147 else {
148 realValues.add(element);
149 }
150 }
151 else {
152 realValues.add(element);
153 }
154 }
155
156 return realValues;
157 }
158
159 private double calculateDuration(List realValues) {
160 boolean expectingNumber = true;
161 double finalResult = 0;
162
163 Number currentNumberValue = null;
164
165 if (realValues.size() == 0) {
166 throw new PropertyParseException("Nothing to parse.");
167 }
168
169 for (Iterator iter = realValues.iterator(); iter.hasNext();) {
170 Object element = iter.next();
171
172 if (element instanceof Number) {
173
174 if (!expectingNumber) {
175 throw new PropertyParseException("Unexpected token '" + element + "'. Didn't expect a number.");
176 }
177
178 currentNumberValue = (Number) element;
179
180 expectingNumber = false;
181 }
182 else {
183
184 if (expectingNumber) {
185 if (currentNumberValue == null) {
186 throw new PropertyParseException("Unexpected token '" + element + "'. Expected a number.");
187 }
188 }
189
190 if (currentNumberValue instanceof Double) {
191 if (((Double) currentNumberValue).isInfinite()) {
192 throw new PropertyParseException("Overflow");
193 }
194 }
195
196 double currentDoubleValue = currentNumberValue.doubleValue();
197
198 if (currentDoubleValue < 0) {
199 throw new PropertyParseException("Negative number '" + currentDoubleValue + "' not allowed.");
200 }
201
202 long currentLongValue = currentNumberValue.longValue();
203
204 if (currentLongValue < currentDoubleValue - 1) {
205 throw new PropertyParseException("Overflow");
206 }
207
208 long multiplier = getMultiplierForTimeString((String) element, (currentLongValue == 1));
209 double intermediateResult = multiplier * currentDoubleValue;
210
211 finalResult += intermediateResult;
212
213 long finalResultTest = (long) finalResult;
214
215 if (finalResultTest < finalResult - 1) {
216 throw new PropertyParseException("Overflow");
217 }
218
219
220 expectingNumber = true;
221 currentNumberValue = null;
222 }
223 }
224
225 if (!expectingNumber) {
226
227 throw new PropertyParseException("Expected a unit but reached end of input.");
228 }
229
230 return finalResult;
231 }
232
233 private Number getNumberValue(String string) {
234 string = string.toLowerCase();
235
236 if (string.equals("zero")) {
237 return Integer.valueOf("0");
238 }
239
240 if (string.equals("a")) {
241 return Integer.valueOf("1");
242 }
243
244 if (string.equals("one")) {
245 return Integer.valueOf("1");
246 }
247
248 if (string.equals("two")) {
249 return Integer.valueOf("2");
250 }
251
252 if (string.equals("three")) {
253 return Integer.valueOf("3");
254 }
255
256 if (string.equals("four")) {
257 return Integer.valueOf("4");
258 }
259
260 if (string.equals("five")) {
261 return Integer.valueOf("5");
262 }
263
264 if (string.equals("six")) {
265 return Integer.valueOf("6");
266 }
267
268 if (string.equals("seven")) {
269 return Integer.valueOf("7");
270 }
271
272 if (string.equals("eight")) {
273 return Integer.valueOf("8");
274 }
275
276 if (string.equals("nine")) {
277 return Integer.valueOf("9");
278 }
279
280 if (string.equals("ten")) {
281 return Integer.valueOf("10");
282 }
283
284 return null;
285 }
286
287 private long getMultiplierForTimeString(String string, boolean singleWord) {
288 string = string.toLowerCase();
289
290 boolean addS = !singleWord;
291
292 if (string.equals("week" + (addS ? "s" : ""))) {
293 return 1000 * 60 * 60 * 24 * 7;
294 }
295
296 if (string.equals("day" + (addS ? "s" : ""))) {
297 return 1000 * 60 * 60 * 24;
298 }
299
300 if (string.equals("hour" + (addS ? "s" : ""))) {
301 return 60 * 60 * 1000;
302 }
303
304 if (string.equals("minute" + (addS ? "s" : ""))) {
305 return 60 * 1000;
306 }
307
308 if (string.equals("second" + (addS ? "s" : ""))) {
309 return 1000;
310 }
311
312 if (string.equals("ms")) {
313 return 1;
314 }
315
316 if (string.equals("s")) {
317 return 1000;
318 }
319
320 throw new PropertyParseException("Unknown unit: '" + string + "'");
321 }
322 }