/*
* Copyright 2013 Eediom Inc.
*
* Licensed 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.araqne.logdb.query.expr;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.araqne.logdb.QueryContext;
import org.araqne.logdb.Row;
import org.araqne.logdb.Strings;
public class In extends FunctionExpression {
private static abstract class FieldMatcher {
public abstract boolean match(Row log, Object o);
}
static class StringMatcher extends FieldMatcher {
public static enum StringMatchMethod {
EQUALS, STARTSWITH, ENDSWITH, CONTAINS, PATTERN,
}
private String term;
private String operand;
private Pattern pattern;
private StringMatchMethod matchMethod;
private Matcher matcher;
public StringMatcher(String s) {
this.term = s;
int count = countOfAsterisk(term);
int first = term.indexOf('*');
int last = term.lastIndexOf('*');
if (count == 1 && first == 0) {
matchMethod = StringMatchMethod.ENDSWITH;
operand = term.substring(1);
} else if (count == 1 && last == term.length() - 1) {
matchMethod = StringMatchMethod.STARTSWITH;
operand = term.substring(0, last);
} else if (count == 2 && first == 0 && last == term.length() - 1) {
matchMethod = StringMatchMethod.CONTAINS;
operand = term.substring(1, last);
} else {
pattern = Strings.tryBuildPattern(term);
if (pattern != null) {
matchMethod = StringMatchMethod.PATTERN;
matcher = pattern.matcher("");
} else
matchMethod = StringMatchMethod.EQUALS;
}
}
public boolean match(Row log, Object o) {
if (o instanceof CharSequence) {
String token = o.toString();
switch (matchMethod) {
case EQUALS:
return token.equals(term);
case STARTSWITH:
return token.startsWith(operand);
case ENDSWITH:
return token.endsWith(operand);
case CONTAINS:
return token.contains(operand);
case PATTERN:
return matcher.reset(token).matches();
default:
throw new IllegalStateException("bad match method: " + matchMethod.toString());
}
} else {
return false;
}
}
private int countOfAsterisk(String term2) {
int cnt = 0;
int start = -1;
while (true) {
int p = term2.indexOf('*', start + 1);
if (p == -1)
break;
cnt++;
start = p;
}
return cnt;
}
}
private static class GenericMatcher extends FieldMatcher {
public Expression term;
public GenericMatcher(Expression term) {
this.term = term;
}
public boolean match(Row log, Object o) {
Object eval = term.eval(log);
if (eval == null)
return false;
else
return eval.equals(o);
}
}
private Expression field;
private List<Expression> values;
private List<FieldMatcher> matchers;
private Set<String> exactTerms;
public In(QueryContext ctx, List<Expression> exprs) {
super("in", exprs, 2);
this.field = exprs.get(0);
this.values = exprs.subList(1, exprs.size());
this.matchers = new ArrayList<FieldMatcher>(values.size());
this.exactTerms = new HashSet<String>();
for (Expression expr : this.values) {
Object eval = expr.eval(new Row());
if (eval instanceof String) {
String needle = (String) eval;
if (needle.indexOf('*') == -1)
exactTerms.add(needle);
else
matchers.add(new StringMatcher(needle));
} else
matchers.add(new GenericMatcher(expr));
}
}
@Override
public Object eval(Row map) {
Object o = field.eval(map);
if (o == null)
return false;
if (exactTerms.contains(o))
return true;
for (FieldMatcher matcher : matchers) {
boolean isMatch = matcher.match(map, o);
if (isMatch)
return true;
}
return false;
}
}