blob: 0f20755887e6f72eea0220836dae86c6772e3ded [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.pojotools;
import static java.util.Calendar.*;
import static org.apache.juneau.internal.ExceptionUtils.*;
import static org.apache.juneau.internal.StateMachineState.*;
import java.text.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.internal.*;
/**
* TODO
*/
public class TimeMatcherFactory extends MatcherFactory {
/**
* Default reusable matcher.
*/
public static final TimeMatcherFactory DEFAULT = new TimeMatcherFactory();
private final SimpleDateFormat[] formats;
/**
* Constructor.
*/
protected TimeMatcherFactory() {
this.formats = getTimestampFormats();
}
/**
* TODO
*
* @return TODO
*/
protected SimpleDateFormat[] getTimestampFormats() {
String[] s = getTimestampFormatStrings();
SimpleDateFormat[] a = new SimpleDateFormat[s.length];
for (int i = 0; i < s.length; i++)
a[i] = new SimpleDateFormat(s[i]);
return a;
}
/**
* TODO
*
* @return TODO
*/
protected String[] getTimestampFormatStrings() {
return new String[]{
"yyyy-MM-dd'T'HH:mm:ss",
"yyyy-MM-dd'T'HH:mm",
"yyyy-MM-dd'T'HH",
"yyyy-MM-dd",
"yyyy-MM",
"yyyy"
};
}
@Override
public boolean canMatch(ClassMeta<?> cm) {
return cm.isDateOrCalendar();
}
@Override
public Matcher create(String pattern) {
return new TimeMatcher(formats, pattern);
}
/**
* A construct representing a single search pattern.
*/
private static class TimeMatcher extends Matcher {
private static final AsciiSet
DT = AsciiSet.create("0123456789-:T./"),
WS = AsciiSet.create(" \t");
TimestampRange[] ranges;
List<TimestampRange> l = new LinkedList<>();
public TimeMatcher(SimpleDateFormat[] f, String s) {
// Possible patterns:
// >2000, <2000, >=2000, <=2000, > 2000, 2000 - 2001, '2000', >'2000', '2000'-'2001', '2000' - '2001'
// Possible states:
// S01 = Looking for [<]/[>]/quote/NUM ([>]=S02, [<]=S03, [']=S05, ["]=S06, NUM=S08)
// S02 = Found [>], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08)
// S03 = Found [<], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08)
// S04 = Found [>=] or [<=], looking for quote/NUM ([']=S05, ["]=S06, NUM=S08)
// S05 = Found ['], looking for ['] ([']=S01)
// S06 = Found ["], looking for ["] (["]=S01)
// S07 = Found [123"] or [123'], looking for WS (WS=S09)
// S08 = Found [2], looking for WS (WS=S09)
// S09 = Found [2000 ], looking for [-]/quote/NUM ([-]=S10, [']=S11, ["]=S12, NUM=S13)
// S10 = Found [2000 -], looking for quote/NUM ([']=S11, ["]=S12, NUM=S13)
// S11 = Found [2000 - '], looking for ['] ([']=S01)
// S12 = Found [2000 - "], looking for ["] (["]=S01)
// S13 = Found [2000 - 2], looking for WS (WS=S01)
StateMachineState state = S01;
int mark = 0;
Equality eq = Equality.NONE;
String s1 = null, s2 = null;
int i;
char c = 0;
for (i = 0; i < s.trim().length(); i++) {
c = s.charAt(i);
if (state == S01) {
// S01 = Looking for [>]/[<]/quote/NUM ([>]=S02, [<]=S03, [']=S05, ["]=S06, NUM=S08)
if (WS.contains(c)) {
state = S01;
} else if (c == '>') {
state = S02;
eq = Equality.GT;
} else if (c == '<') {
state = S03;
eq = Equality.LT;
} else if (c == '\'') {
state = S05;
mark = i+1;
} else if (c == '"') {
state = S06;
mark = i+1;
} else if (DT.contains(c)) {
state = S08;
mark = i;
} else {
break;
}
} else if (state == S02) {
// S02 = Found [>], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08)
if (WS.contains(c)) {
state = S02;
} else if (c == '=') {
state = S04;
eq = Equality.GTE;
} else if (c == '\'') {
state = S05;
mark = i+1;
} else if (c == '"') {
state = S06;
mark = i+1;
} else if (DT.contains(c)) {
state = S08;
mark = i;
} else {
break;
}
} else if (state == S03) {
// S03 = Found [<], looking for [=]/quote/NUM ([=]=S04, [']=S05, ["]=S06, NUM=S08)
if (WS.contains(c)) {
state = S03;
} else if (c == '=') {
state = S04;
eq = Equality.LTE;
} else if (c == '\'') {
state = S05;
mark = i+1;
} else if (c == '"') {
state = S06;
mark = i+1;
} else if (DT.contains(c)) {
state = S08;
mark = i;
} else {
break;
}
} else if (state == S04) {
// S04 = Found [>=] or [<=], looking for quote/NUM ([']=S05, ["]=S06, NUM=S08)
if (WS.contains(c)) {
state = S04;
} else if (c == '\'') {
state = S05;
mark = i+1;
} else if (c == '"') {
state = S06;
mark = i+1;
} else if (DT.contains(c)) {
state = S08;
mark = i;
} else {
break;
}
} else if (state == S05) {
// S05 = Found ['], looking for ['] ([']=S07)
if (c == '\'') {
state = S07;
s1 = s.substring(mark, i);
}
} else if (state == S06) {
// S06 = Found ["], looking for ["] (["]=S07)
if (c == '"') {
state = S07;
s1 = s.substring(mark, i);
}
} else if (state == S07) {
// S07 = Found [123"] or [123'], looking for WS (WS=S09)
if (WS.contains(c)) {
state = S09;
} else if (c == '-') {
state = S10;
} else {
break;
}
} else if (state == S08) {
// S08 = Found [1], looking for WS (WS=S09)
if (WS.contains(c)) {
state = S09;
s1 = s.substring(mark, i);
}
} else if (state == S09) {
// S09 = Found [2000 ], looking for [-]/[>]/[<]/quote/NUM ([-]=S10, [>]=S02, [<]=S03, [']=S05, ["]=S06, NUM=S08)
if (WS.contains(c)) {
state = S09;
} else if (c == '-') {
state = S10;
} else if (c == '>') {
state = S02;
l.add(new TimestampRange(f, eq, s1));
eq = Equality.GT;
s1 = null;
} else if (c == '<') {
state = S03;
l.add(new TimestampRange(f, eq, s1));
eq = Equality.LT;
s1 = null;
} else if (c == '\'') {
state = S05;
l.add(new TimestampRange(f, eq, s1));
mark = i+1;
eq = null;
s1 = null;
} else if (c == '"') {
state = S06;
l.add(new TimestampRange(f, eq, s1));
mark = i+1;
eq = null;
s1 = null;
} else if (DT.contains(c)) {
state = S08;
l.add(new TimestampRange(f, eq, s1));
eq = null;
s1 = null;
mark = i;
} else {
break;
}
} else if (state == S10) {
// S10 = Found [2000 -], looking for quote/NUM ([']=S11, ["]=S12, NUM=S13)
if (WS.contains(c)) {
state = S10;
} else if (c == '\'') {
state = S11;
mark = i+1;
} else if (c == '"') {
state = S12;
mark = i+1;
} else if (DT.contains(c)) {
state = S13;
mark = i;
} else {
break;
}
} else if (state == S11) {
// S11 = Found [2000 - '], looking for ['] ([']=S01)
if (c == '\'') {
state = S01;
s2 = s.substring(mark, i);
l.add(new TimestampRange(f, s1, s2));
s1 = null;
s2 = null;
}
} else if (state == S12) {
// S12 = Found [2000 - "], looking for ["] (["]=S01)
if (c == '"') {
state = S01;
s2 = s.substring(mark, i);
l.add(new TimestampRange(f, s1, s2));
s1 = null;
s2 = null;
}
} else /* (state == S13) */ {
// S13 = Found [2000 - 2], looking for WS (WS=S01)
if (WS.contains(c)) {
state = S01;
s2 = s.substring(mark, i);
l.add(new TimestampRange(f, s1, s2));
s1 = null;
s2 = null;
}
}
}
if (i != s.length())
throw new PatternException("Invalid range pattern ({0}): pattern=[{1}], pos=[{2}], char=[{3}]", state, s, i, c);
if (state == S01) {
// No tokens found.
} else if (state == S02 || state == S03 || state == S04 || state == S05 || state == S06 || state == S10 || state == S11 || state == S12) {
throw new PatternException("Invalid range pattern (E{0}): {1}", state, s);
} else if (state == S07) {
l.add(new TimestampRange(f, eq, s1));
} else if (state == S08) {
s1 = s.substring(mark).trim();
l.add(new TimestampRange(f, eq, s1));
} else /* (state == S13) */ {
s2 = s.substring(mark).trim();
l.add(new TimestampRange(f, s1, s2));
}
ranges = l.toArray(new TimestampRange[l.size()]);
}
@Override
public boolean matches(ClassMeta<?> cm, Object o) {
if (ranges.length == 0) return true;
Calendar c = null;
if (cm.isCalendar())
c = (Calendar)o;
else {
c = Calendar.getInstance();
c.setTime((Date)o);
}
for (int i = 0; i < ranges.length; i++)
if (ranges[i].matches(c))
return true;
return false;
}
}
/**
* A construct representing a single search range in a single search pattern.
* All possible forms of search patterns are boiled down to these timestamp ranges.
*/
private static class TimestampRange {
Calendar start;
Calendar end;
public TimestampRange(SimpleDateFormat[] formats, String start, String end) {
CalendarP start1 = parseDate(formats, start);
CalendarP end1 = parseDate(formats, end);
this.start = start1.copy().roll(MILLISECOND, -1).getCalendar();
this.end = end1.roll(1).getCalendar();
}
public TimestampRange(SimpleDateFormat[] formats, Equality eq, String singleDate) {
CalendarP singleDate1 = parseDate(formats, singleDate);
if (eq == Equality.GT) {
this.start = singleDate1.roll(1).roll(MILLISECOND, -1).getCalendar();
this.end = new CalendarP(new Date(Long.MAX_VALUE), 0).getCalendar();
} else if (eq == Equality.LT) {
this.start = new CalendarP(new Date(0), 0).getCalendar();
this.end = singleDate1.getCalendar();
} else if (eq == Equality.GTE) {
this.start = singleDate1.roll(MILLISECOND, -1).getCalendar();
this.end = new CalendarP(new Date(Long.MAX_VALUE), 0).getCalendar();
} else if (eq == Equality.LTE) {
this.start = new CalendarP(new Date(0), 0).getCalendar();
this.end = singleDate1.roll(1).getCalendar();
} else {
this.start = singleDate1.copy().roll(MILLISECOND, -1).getCalendar();
this.end = singleDate1.roll(1).getCalendar();
}
}
public boolean matches(Calendar c) {
boolean b = (c.after(start) && c.before(end));
return b;
}
}
private static int getPrecisionField(String pattern) {
if (pattern.indexOf('s') != -1)
return SECOND;
if (pattern.indexOf('m') != -1)
return MINUTE;
if (pattern.indexOf('H') != -1)
return HOUR_OF_DAY;
if (pattern.indexOf('d') != -1)
return DAY_OF_MONTH;
if (pattern.indexOf('M') != -1)
return MONTH;
if (pattern.indexOf('y') != -1)
return YEAR;
return Calendar.MILLISECOND;
}
/**
* Parses a timestamp string off the beginning of the string segment 'seg'.
* Goes through each possible valid timestamp format until it finds a match.
* The position where the parsing left off is stored in pp.
*
* @param seg The string segment being parsed.
* @param pp Where parsing last left off.
* @return An object representing a timestamp.
*/
static CalendarP parseDate(SimpleDateFormat[] formats, String seg) {
ParsePosition pp = new ParsePosition(0);
for (int i = 0; i < formats.length; i++) {
SimpleDateFormat f = formats[i];
Date d = f.parse(seg, pp);
int idx = pp.getIndex();
if (idx != 0) {
// it only counts if the next character is '-', 'space', or end-of-string.
char c = (seg.length() == idx ? 0 : seg.charAt(idx));
if (c == 0 || c == '-' || Character.isWhitespace(c))
return new CalendarP(d, getPrecisionField(f.toPattern()));
}
}
throw runtimeException("Invalid date encountered: ''{0}''", seg);
}
/**
* Combines a Calendar with a precision identifier.
*/
private static class CalendarP {
public Calendar c;
public int precision;
public CalendarP(Date date, int precision) {
c = Calendar.getInstance();
c.setTime(date);
this.precision = precision;
}
public CalendarP copy() {
return new CalendarP(c.getTime(), precision);
}
public CalendarP roll(int field, int amount) {
c.add(field, amount);
return this;
}
public CalendarP roll(int amount) {
return roll(precision, amount);
}
public Calendar getCalendar() {
return c;
}
}
}