001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.pojotools; 014 015import static java.util.Calendar.*; 016import static org.apache.juneau.internal.StateMachineState.*; 017 018import java.text.*; 019import java.util.*; 020 021import org.apache.juneau.*; 022import org.apache.juneau.internal.*; 023 024/** 025 * TODO 026 */ 027public class TimeMatcherFactory extends MatcherFactory { 028 029 /** 030 * Default reusable matcher. 031 */ 032 public static final TimeMatcherFactory DEFAULT = new TimeMatcherFactory(); 033 034 private final SimpleDateFormat[] formats; 035 036 /** 037 * Constructor. 038 */ 039 protected TimeMatcherFactory() { 040 this.formats = getTimestampFormats(); 041 } 042 043 /** 044 * TODO 045 * 046 * @return TODO 047 */ 048 protected SimpleDateFormat[] getTimestampFormats() { 049 String[] s = getTimestampFormatStrings(); 050 SimpleDateFormat[] a = new SimpleDateFormat[s.length]; 051 for (int i = 0; i < s.length; i++) 052 a[i] = new SimpleDateFormat(s[i]); 053 return a; 054 } 055 056 /** 057 * TODO 058 * 059 * @return TODO 060 */ 061 protected String[] getTimestampFormatStrings() { 062 return new String[]{ 063 "yyyy-MM-dd'T'HH:mm:ss", 064 "yyyy-MM-dd'T'HH:mm", 065 "yyyy-MM-dd'T'HH", 066 "yyyy-MM-dd", 067 "yyyy-MM", 068 "yyyy" 069 }; 070 } 071 072 @Override 073 public boolean canMatch(ClassMeta<?> cm) { 074 return cm.isDateOrCalendar(); 075 } 076 077 @Override 078 public Matcher create(String pattern) { 079 return new TimeMatcher(formats, pattern); 080 } 081 082 /** 083 * A construct representing a single search pattern. 084 */ 085 private static class TimeMatcher extends Matcher { 086 087 private static final AsciiSet 088 DT = AsciiSet.create("0123456789-:T./"), 089 WS = AsciiSet.create(" \t"); 090 091 TimestampRange[] ranges; 092 List<TimestampRange> l = new LinkedList<>(); 093 094 public TimeMatcher(SimpleDateFormat[] f, String s) { 095 096 // Possible patterns: 097 // >2000, <2000, >=2000, <=2000, > 2000, 2000 - 2001, '2000', >'2000', '2000'-'2001', '2000' - '2001' 098 099 // Possible states: 100 // S01 = Looking for [<]/[>]/quote/NUM ([>]=S02, [<]=S03, [']=S05, ["]=S06, NUM=S08) 101 // S02 = Found [>], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08) 102 // S03 = Found [<], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08) 103 // S04 = Found [>=] or [<=], looking for quote/NUM ([']=S05, ["]=S06, NUM=S08) 104 // S05 = Found ['], looking for ['] ([']=S01) 105 // S06 = Found ["], looking for ["] (["]=S01) 106 // S07 = Found [123"] or [123'], looking for WS (WS=S09) 107 // S08 = Found [2], looking for WS (WS=S09) 108 // S09 = Found [2000 ], looking for [-]/quote/NUM ([-]=S10, [']=S11, ["]=S12, NUM=S13) 109 // S10 = Found [2000 -], looking for quote/NUM ([']=S11, ["]=S12, NUM=S13) 110 // S11 = Found [2000 - '], looking for ['] ([']=S01) 111 // S12 = Found [2000 - "], looking for ["] (["]=S01) 112 // S13 = Found [2000 - 2], looking for WS (WS=S01) 113 114 StateMachineState state = S01; 115 int mark = 0; 116 Equality eq = Equality.NONE; 117 String s1 = null, s2 = null; 118 119 int i; 120 char c = 0; 121 for (i = 0; i < s.trim().length(); i++) { 122 c = s.charAt(i); 123 if (state == S01) { 124 // S01 = Looking for [>]/[<]/quote/NUM ([>]=S02, [<]=S03, [']=S05, ["]=S06, NUM=S08) 125 if (WS.contains(c)) { 126 state = S01; 127 } else if (c == '>') { 128 state = S02; 129 eq = Equality.GT; 130 } else if (c == '<') { 131 state = S03; 132 eq = Equality.LT; 133 } else if (c == '\'') { 134 state = S05; 135 mark = i+1; 136 } else if (c == '"') { 137 state = S06; 138 mark = i+1; 139 } else if (DT.contains(c)) { 140 state = S08; 141 mark = i; 142 } else { 143 break; 144 } 145 } else if (state == S02) { 146 // S02 = Found [>], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08) 147 if (WS.contains(c)) { 148 state = S02; 149 } else if (c == '=') { 150 state = S04; 151 eq = Equality.GTE; 152 } else if (c == '\'') { 153 state = S05; 154 mark = i+1; 155 } else if (c == '"') { 156 state = S06; 157 mark = i+1; 158 } else if (DT.contains(c)) { 159 state = S08; 160 mark = i; 161 } else { 162 break; 163 } 164 } else if (state == S03) { 165 // S03 = Found [<], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08) 166 if (WS.contains(c)) { 167 state = S03; 168 } else if (c == '=') { 169 state = S04; 170 eq = Equality.LTE; 171 } else if (c == '\'') { 172 state = S05; 173 mark = i+1; 174 } else if (c == '"') { 175 state = S06; 176 mark = i+1; 177 } else if (DT.contains(c)) { 178 state = S08; 179 mark = i; 180 } else { 181 break; 182 } 183 } else if (state == S04) { 184 // S04 = Found [>=] or [<=], looking for quote/NUM ([']=S05, ["]=S06, NUM=S08) 185 if (WS.contains(c)) { 186 state = S04; 187 } else if (c == '\'') { 188 state = S05; 189 mark = i+1; 190 } else if (c == '"') { 191 state = S06; 192 mark = i+1; 193 } else if (DT.contains(c)) { 194 state = S08; 195 mark = i; 196 } else { 197 break; 198 } 199 } else if (state == S05) { 200 // S05 = Found ['], looking for ['] ([']=S07) 201 if (c == '\'') { 202 state = S07; 203 s1 = s.substring(mark, i); 204 } 205 } else if (state == S06) { 206 // S06 = Found ["], looking for ["] (["]=S07) 207 if (c == '"') { 208 state = S07; 209 s1 = s.substring(mark, i); 210 } 211 } else if (state == S07) { 212 // S07 = Found [123"] or [123'], looking for WS (WS=S09) 213 if (WS.contains(c)) { 214 state = S09; 215 } else if (c == '-') { 216 state = S10; 217 } else { 218 break; 219 } 220 } else if (state == S08) { 221 // S08 = Found [1], looking for WS (WS=S09) 222 if (WS.contains(c)) { 223 state = S09; 224 s1 = s.substring(mark, i); 225 } 226 } else if (state == S09) { 227 // S09 = Found [2000 ], looking for [-]/[>]/[<]/quote/NUM ([-]=S10, [>]=S02, [<]=S03, [']=S05, ["]=S06, NUM=S08) 228 if (WS.contains(c)) { 229 state = S09; 230 } else if (c == '-') { 231 state = S10; 232 } else if (c == '>') { 233 state = S02; 234 l.add(new TimestampRange(f, eq, s1)); 235 eq = Equality.GT; 236 s1 = null; 237 } else if (c == '<') { 238 state = S03; 239 l.add(new TimestampRange(f, eq, s1)); 240 eq = Equality.LT; 241 s1 = null; 242 } else if (c == '\'') { 243 state = S05; 244 l.add(new TimestampRange(f, eq, s1)); 245 mark = i+1; 246 eq = null; 247 s1 = null; 248 } else if (c == '"') { 249 state = S06; 250 l.add(new TimestampRange(f, eq, s1)); 251 mark = i+1; 252 eq = null; 253 s1 = null; 254 } else if (DT.contains(c)) { 255 state = S08; 256 l.add(new TimestampRange(f, eq, s1)); 257 eq = null; 258 s1 = null; 259 mark = i; 260 } else { 261 break; 262 } 263 } else if (state == S10) { 264 // S10 = Found [2000 -], looking for quote/NUM ([']=S11, ["]=S12, NUM=S13) 265 if (WS.contains(c)) { 266 state = S10; 267 } else if (c == '\'') { 268 state = S11; 269 mark = i+1; 270 } else if (c == '"') { 271 state = S12; 272 mark = i+1; 273 } else if (DT.contains(c)) { 274 state = S13; 275 mark = i; 276 } else { 277 break; 278 } 279 } else if (state == S11) { 280 // S11 = Found [2000 - '], looking for ['] ([']=S01) 281 if (c == '\'') { 282 state = S01; 283 s2 = s.substring(mark, i); 284 l.add(new TimestampRange(f, s1, s2)); 285 s1 = null; 286 s2 = null; 287 } 288 } else if (state == S12) { 289 // S12 = Found [2000 - "], looking for ["] (["]=S01) 290 if (c == '"') { 291 state = S01; 292 s2 = s.substring(mark, i); 293 l.add(new TimestampRange(f, s1, s2)); 294 s1 = null; 295 s2 = null; 296 } 297 } else /* (state == S13) */ { 298 // S13 = Found [2000 - 2], looking for WS (WS=S01) 299 if (WS.contains(c)) { 300 state = S01; 301 s2 = s.substring(mark, i); 302 l.add(new TimestampRange(f, s1, s2)); 303 s1 = null; 304 s2 = null; 305 } 306 } 307 } 308 309 if (i != s.length()) 310 throw new PatternException("Invalid range pattern ({0}): pattern=[{1}], pos=[{2}], char=[{3}]", state, s, i, c); 311 312 if (state == S01) { 313 // No tokens found. 314 } else if (state == S02 || state == S03 || state == S04 || state == S05 || state == S06 || state == S10 || state == S11 || state == S12) { 315 throw new PatternException("Invalid range pattern (E{0}): {1}", state, s); 316 } else if (state == S07) { 317 l.add(new TimestampRange(f, eq, s1)); 318 } else if (state == S08) { 319 s1 = s.substring(mark).trim(); 320 l.add(new TimestampRange(f, eq, s1)); 321 } else /* (state == S13) */ { 322 s2 = s.substring(mark).trim(); 323 l.add(new TimestampRange(f, s1, s2)); 324 } 325 326 ranges = l.toArray(new TimestampRange[l.size()]); 327 } 328 329 @Override 330 public boolean matches(ClassMeta<?> cm, Object o) { 331 if (ranges.length == 0) return true; 332 333 Calendar c = null; 334 if (cm.isCalendar()) 335 c = (Calendar)o; 336 else { 337 c = Calendar.getInstance(); 338 c.setTime((Date)o); 339 } 340 for (int i = 0; i < ranges.length; i++) 341 if (ranges[i].matches(c)) 342 return true; 343 return false; 344 } 345 } 346 347 /** 348 * A construct representing a single search range in a single search pattern. 349 * All possible forms of search patterns are boiled down to these timestamp ranges. 350 */ 351 private static class TimestampRange { 352 Calendar start; 353 Calendar end; 354 355 public TimestampRange(SimpleDateFormat[] formats, String start, String end) { 356 CalendarP start1 = parseDate(formats, start); 357 CalendarP end1 = parseDate(formats, end); 358 this.start = start1.copy().roll(MILLISECOND, -1).getCalendar(); 359 this.end = end1.roll(1).getCalendar(); 360 } 361 362 public TimestampRange(SimpleDateFormat[] formats, Equality eq, String singleDate) { 363 CalendarP singleDate1 = parseDate(formats, singleDate); 364 if (eq == Equality.GT) { 365 this.start = singleDate1.roll(1).roll(MILLISECOND, -1).getCalendar(); 366 this.end = new CalendarP(new Date(Long.MAX_VALUE), 0).getCalendar(); 367 } else if (eq == Equality.LT) { 368 this.start = new CalendarP(new Date(0), 0).getCalendar(); 369 this.end = singleDate1.getCalendar(); 370 } else if (eq == Equality.GTE) { 371 this.start = singleDate1.roll(MILLISECOND, -1).getCalendar(); 372 this.end = new CalendarP(new Date(Long.MAX_VALUE), 0).getCalendar(); 373 } else if (eq == Equality.LTE) { 374 this.start = new CalendarP(new Date(0), 0).getCalendar(); 375 this.end = singleDate1.roll(1).getCalendar(); 376 } else { 377 this.start = singleDate1.copy().roll(MILLISECOND, -1).getCalendar(); 378 this.end = singleDate1.roll(1).getCalendar(); 379 } 380 } 381 382 public boolean matches(Calendar c) { 383 boolean b = (c.after(start) && c.before(end)); 384 return b; 385 } 386 } 387 388 private static int getPrecisionField(String pattern) { 389 if (pattern.indexOf('s') != -1) 390 return SECOND; 391 if (pattern.indexOf('m') != -1) 392 return MINUTE; 393 if (pattern.indexOf('H') != -1) 394 return HOUR_OF_DAY; 395 if (pattern.indexOf('d') != -1) 396 return DAY_OF_MONTH; 397 if (pattern.indexOf('M') != -1) 398 return MONTH; 399 if (pattern.indexOf('y') != -1) 400 return YEAR; 401 return Calendar.MILLISECOND; 402 } 403 404 /** 405 * Parses a timestamp string off the beginning of the string segment 'seg'. 406 * Goes through each possible valid timestamp format until it finds a match. 407 * The position where the parsing left off is stored in pp. 408 * 409 * @param seg The string segment being parsed. 410 * @param pp Where parsing last left off. 411 * @return An object representing a timestamp. 412 */ 413 static CalendarP parseDate(SimpleDateFormat[] formats, String seg) { 414 ParsePosition pp = new ParsePosition(0); 415 for (int i = 0; i < formats.length; i++) { 416 SimpleDateFormat f = formats[i]; 417 Date d = f.parse(seg, pp); 418 int idx = pp.getIndex(); 419 if (idx != 0) { 420 // it only counts if the next character is '-', 'space', or end-of-string. 421 char c = (seg.length() == idx ? 0 : seg.charAt(idx)); 422 if (c == 0 || c == '-' || Character.isWhitespace(c)) 423 return new CalendarP(d, getPrecisionField(f.toPattern())); 424 } 425 } 426 427 throw new FormattedRuntimeException("Invalid date encountered: ''{0}''", seg); 428 } 429 430 /** 431 * Combines a Calendar with a precision identifier. 432 */ 433 private static class CalendarP { 434 public Calendar c; 435 public int precision; 436 437 public CalendarP(Date date, int precision) { 438 c = Calendar.getInstance(); 439 c.setTime(date); 440 this.precision = precision; 441 } 442 443 public CalendarP copy() { 444 return new CalendarP(c.getTime(), precision); 445 } 446 447 public CalendarP roll(int field, int amount) { 448 c.add(field, amount); 449 return this; 450 } 451 452 public CalendarP roll(int amount) { 453 return roll(precision, amount); 454 } 455 456 public Calendar getCalendar() { 457 return c; 458 } 459 } 460}