All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.objecttools.TimeMatcherFactory Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * 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.objecttools;

import static java.util.Calendar.*;
import static org.apache.juneau.internal.StateMachineState.*;

import java.text.*;
import java.util.*;

import org.apache.juneau.*;
import org.apache.juneau.common.internal.*;
import org.apache.juneau.internal.*;

/**
 * Date/time matcher factory for the {@link ObjectSearcher} class.
 *
 * 

* The class provides searching based on the following patterns: *

*
    *
  • "property=2011" - A single year *
  • "property=2011 2013 2015" - Multiple years *
  • "property=2011-01" - A single month *
  • "property=2011-01-01" - A single day *
  • "property=2011-01-01T12" - A single hour *
  • "property=2011-01-01T12:30" - A single minute *
  • "property=2011-01-01T12:30:45" - A single second *
  • "property=>2011","property=>=2011","property=<2011","property=<=2011" - Open-ended ranges *
  • "property=>2011","property=>=2011","property=<2011","property=<=2011" - Open-ended ranges *
  • "property=2011 - 2013-06-30" - Closed ranges *
* *
See Also:
*/ 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 AbstractMatcher create(String pattern) { return new TimeMatcher(formats, pattern); } /** * A construct representing a single search pattern. */ private static class TimeMatcher extends AbstractMatcher { private static final AsciiSet DT = AsciiSet.create("0123456789-:T./"), WS = AsciiSet.create(" \t"); TimestampRange[] ranges; List 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 (TimestampRange range : ranges) if (range.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 (SimpleDateFormat f : formats) { 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 new BasicRuntimeException("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; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy