blob: 567b645ce72439ed41400f9f3113e5fb888accf2 [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 org.apache.juneau.internal.StateMachineState.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.internal.*;
/**
* TODO
*
*/
public class NumberMatcherFactory extends MatcherFactory {
/**
* Default reusable matcher.
*/
public static final NumberMatcherFactory DEFAULT = new NumberMatcherFactory();
@Override
public boolean canMatch(ClassMeta<?> cm) {
return cm.isNumber();
}
@Override
public Matcher create(String pattern) {
return new NumberMatcher(pattern);
}
/**
* A construct representing a single search pattern.
*/
private static class NumberMatcher extends Matcher {
NumberRange[] numberRanges;
String pattern;
private static final AsciiSet
SNUM = AsciiSet.create("-0123456789."),
NUM = AsciiSet.create("0123456789."),
WS = AsciiSet.create(" \t");
public NumberMatcher(String s) {
s = s.trim();
pattern = s;
List<NumberRange> l = new LinkedList<>();
// Possible patterns:
// 123, >123, <123, >=123, <=123, >-123, >=-123, 123-456, -123--456, !123, !123-456, 123 - 456 (one token), 123 -456 (two separate tokens)
// Possible states:
// S01 = Looking for start (WS=S01, [!]=S01, [>]=S02, [<]=S03, SNUM=S06)
// S02 = Found [>], looking for [=]/SNUM ([=]=S04, WS=S05, SNUM=S06)
// S03 = Found [<], looking for [=]/SNUM ([=]=S05, WS=S05, SNUM=S06)
// S04 = Found [=], looking for SNUN (WS=S05, SNUM=S06)
// S05 = Found [... ], looking for SNUM (SNUM=S06)
// S06 = Found [1], looking for [-]/WS (WS=S07, [-]=S08)
// S07 = Found [123 ], looking for [-]/SNUM (if -, could be 123 - 456 or 123 -456) ([-]=S09, SNUM=S07)
// S08 = Found [123-], looking for SNUM (Could be 123- 456 or 123-456) (SNUM=S11)
// S09 = Found [123 -], looking for WS/SNUM (If WS, then it's 123 - 456, otherwise 123 -456) (WS=S10, SNUM=S06)
// S10 = Found [123 - ], looking for SNUM (SNUM=S12)
// S11 = Found [123 - 4], looking for WS (WS=S01)
StateMachineState state = S01;
int mark = 0;
boolean isNot = false;
Equality eq = Equality.NONE;
Integer n1 = null, n2 = null;
int i;
for (i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (state == S01) {
if (c == '!') {
isNot = true;
} else if (WS.contains(c)) {
state = S01;
} else if (c == '>') {
state = S02;
eq = Equality.GT;
} else if (c == '<') {
state = S03;
eq = Equality.LT;
} else if (SNUM.contains(c)) {
state = S06;
mark = i;
} else {
break;
}
} else if (state == S02) {
if (c == '=') {
state = S04;
eq = Equality.GTE;
} else if (WS.contains(c)) {
state = S05;
} else if (SNUM.contains(c)) {
state = S06;
mark = i;
} else {
break;
}
} else if (state == S03) {
if (c == '=') {
state = S04;
eq = Equality.LTE;
} else if (WS.contains(c)) {
state = S05;
} else if (SNUM.contains(c)) {
state = S06;
mark = i;
} else {
break;
}
} else if (state == S04) {
if (WS.contains(c)) {
state = S05;
} else if (SNUM.contains(c)) {
mark = i;
state = S06;
} else {
break;
}
} else if (state == S05) {
if (WS.contains(c)) {
state = S05;
} else if (SNUM.contains(c)) {
state = S06;
mark = i;
} else {
break;
}
} else if (state == S06) {
if (NUM.contains(c)) {
state = S06;
} else if (WS.contains(c)) {
state = S07;
n1 = Integer.parseInt(s.substring(mark, i));
} else if (c == '-') {
state = S08;
n1 = Integer.parseInt(s.substring(mark, i));
} else {
break;
}
} else if (state == S07) {
if (WS.contains(c)) {
state = S07;
} else if (c == '-') {
state = S09;
} else if (SNUM.contains(c)) {
state = S06;
l.add(new NumberRange(eq, n1, isNot));
eq = Equality.NONE;
n1 = null;
isNot = false;
mark = i;
} else {
break;
}
} else if (state == S08) {
if (WS.contains(c)) {
state = S08;
} else if (SNUM.contains(c)) {
state = S11;
mark = i;
} else {
break;
}
} else if (state == S09) {
if (WS.contains(c)) {
state = S10;
} else if (NUM.contains(c)) {
state = S06;
l.add(new NumberRange(eq, n1, isNot));
eq = Equality.NONE;
n1 = null;
isNot = false;
mark = i-1;
} else {
break;
}
} else if (state == S10) {
if (WS.contains(c)) {
state = S10;
} else if (SNUM.contains(c)) {
state = S11;
mark = i;
} else {
break;
}
} else /* (state == S11) */ {
if (SNUM.contains(c)) {
state = S11;
} else if (WS.contains(c)) {
state = S01;
n2 = Integer.parseInt(s.substring(mark, i));
l.add(new NumberRange(eq, n1, n2, isNot));
eq = Equality.NONE;
n1 = n2 = null;
isNot = false;
} else {
break;
}
}
}
if (i != s.length())
throw new PatternException("Invalid range pattern ({0}): {1}", state, s);
if (state == S01) {
// No tokens found.
} else if (state == S02 || state == S03 || state == S04 || state == S08 || state == S09) {
throw new PatternException("Invalid range pattern (E{0}): {1}", state, s);
} else if (state == S06) {
n1 = Integer.parseInt(s.substring(mark).trim());
l.add(new NumberRange(eq, n1, isNot));
} else /* (state == S11) */ {
n2 = Integer.parseInt(s.substring(mark).trim());
l.add(new NumberRange(eq, n1, n2, isNot));
}
numberRanges = l.toArray(new NumberRange[l.size()]);
}
@Override /* Matcher */
public boolean matches(ClassMeta<?> cm, Object o) {
Number n = (Number)o;
if (numberRanges.length == 0) return true;
for (int i = 0; i < numberRanges.length; i++)
if (numberRanges[i].matches(n))
return true;
return false;
}
@Override /* Object */
public String toString() {
return pattern;
}
}
/**
* A construct representing a single search range in a single search pattern.
* All possible forms of search patterns are boiled down to these number ranges.
*/
private static class NumberRange {
int start;
int end;
boolean isNot;
public NumberRange(Equality eq, Integer num, boolean isNot) {
this(eq, num, null, isNot);
}
public NumberRange(Equality eq, Integer start, Integer end, boolean isNot) {
this.isNot = isNot;
// 123, >123, <123, >=123, <=123, >-123, >=-123, 123-456, -123--456
if (eq == Equality.NONE && end == null) { // 123
this.start = start;
this.end = this.start;
} else if (eq == Equality.GT) {
this.start = start+1;
this.end = Integer.MAX_VALUE;
} else if (eq == Equality.GTE) {
this.start = start;
this.end = Integer.MAX_VALUE;
} else if (eq == Equality.LT) {
this.start = Integer.MIN_VALUE;
this.end = start-1;
} else if (eq == Equality.LTE) {
this.start = Integer.MIN_VALUE;
this.end = start;
} else {
this.start = start;
this.end = end;
}
}
public boolean matches(Number n) {
long i = n.longValue();
boolean b = (i>=start && i<=end);
if (isNot) b = !b;
return b;
}
}
}