net.avcompris.commons.query.impl.Parser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of avc-commons3-query Show documentation
Show all versions of avc-commons3-query Show documentation
Common classes for avc-commons3 queries and filtering.
package net.avcompris.commons.query.impl;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Locale.ENGLISH;
import static net.avcompris.commons.query.impl.FieldUtils.isBooleanField;
import static net.avcompris.commons.query.impl.FieldUtils.isDateTimeField;
import static net.avcompris.commons.query.impl.FieldUtils.isEnumField;
import static net.avcompris.commons.query.impl.FieldUtils.isIntField;
import static net.avcompris.commons.query.impl.FieldUtils.isStringField;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import java.lang.reflect.Field;
import java.util.Locale;
import org.apache.commons.lang3.NotImplementedException;
import net.avcompris.commons.query.Arg;
import net.avcompris.commons.query.FilterSyntaxException;
import net.avcompris.commons.query.Filtering;
import net.avcompris.commons.query.Filterings;
import net.avcompris.commons.query.impl.Tokenizer.ParsedArg;
final class Parser, U extends Filtering.Field, V extends Filterings> {
private final Class extends T> filteringClass;
private final Class extends U> fieldClass;
private final Filterings filterings;
public Parser( //
final Class extends V> filteringsClass, //
final Class extends T> filteringClass, //
final Class extends U> fieldClass) {
// this.filteringsClass =
checkNotNull(filteringsClass, "filteringsClass");
this.filteringClass = checkNotNull(filteringClass, "filteringClass");
this.fieldClass = checkNotNull(fieldClass, "fieldClass");
filterings = FilteringsFactory.instantiate(filteringsClass);
}
public T parse(final String expression) throws FilterSyntaxException {
checkNotNull(expression, "expression");
return parse(new Tokenizer(expression));
}
private T parse(final Tokenizer tokenizer) throws FilterSyntaxException {
checkNotNull(tokenizer, "tokenizer");
final String trim = tokenizer.normalizeSpace();
tokenizer.setCurrent(trim);
if (trim.startsWith("!")) {
return handleNot("!", tokenizer);
} else if (trim.startsWith("not")) {
return handleNot("not", tokenizer);
} else if (trim.startsWith("(")) {
final String left = getLeftParenthesisExpression(trim);
tokenizer.substringAfter(left);
if (left.length() == trim.length()) {
return parse(trim.substring(1, trim.length() - 1));
}
final T leftFiltering = parse(left);
return parseRight(leftFiltering, tokenizer);
}
final String firstTokenLowercase = extractFirstTokenLowercase(trim.toLowerCase(ENGLISH));
final String afterLeft = trim.substring(firstTokenLowercase.length()).trim();
final T leftFiltering = getLeftFiltering(firstTokenLowercase, tokenizer.setCurrent(afterLeft));
if (!tokenizer.hasNext()) {
return leftFiltering;
} else {
return parseRight(leftFiltering, tokenizer);
}
}
private static String extractFirstTokenLowercase(final String s) throws FilterSyntaxException {
final char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; ++i) {
final char c = chars[i];
if (c >= 'a' && c <= 'z' || c == '_') {
continue;
}
switch (c) {
case ' ':
case '!':
case '<':
case '=':
case '>':
return s.substring(0, i);
default:
throw new FilterSyntaxException("Unknown char: " + c + ", in s: " + s);
}
}
return s;
}
private T getLeftFiltering(final String firstTokenLowercase, final Tokenizer afterLeft)
throws FilterSyntaxException {
// final String withoutUnderscores = firstTokenLowercase.replace("_", "");
for (final U enumValue : fieldClass.getEnumConstants()) {
final Enum> enumConstant = (Enum>) enumValue;
if (isCompatibleName(firstTokenLowercase, enumConstant)) {
return parseAfterLeft(enumValue, afterLeft);
}
}
throw new FilterSyntaxException("Unknown field name: " + firstTokenLowercase);
}
private static boolean isCompatibleName(final String lowercase, final Enum> enumConstant) {
checkNotNull(lowercase, "lowercase");
checkNotNull(enumConstant, "enumConstant");
final String enumNameLowercase = enumConstant.name().toLowerCase(ENGLISH);
if (lowercase.contentEquals(enumNameLowercase) //
|| lowercase.contentEquals(enumNameLowercase.replace("_", ""))) {
return true;
}
final Field field = FieldUtils.getEnumField((Filtering.Field) enumConstant);
for (final String alias : FieldUtils.extractAliases(field)) {
final String aliasLowercase = alias.toLowerCase(ENGLISH);
if (lowercase.contentEquals(aliasLowercase) //
|| lowercase.contentEquals(aliasLowercase.replace("_", ""))) {
return true;
}
}
return false;
}
// @SuppressWarnings("rawtypes")
private T parseAfterLeft(final U field, final Tokenizer afterLeft) throws FilterSyntaxException {
final String lowercase = afterLeft.getCurrent().toLowerCase(Locale.ENGLISH);
// parser.setCurrent(afterLeft);
if (lowercase.startsWith("gte ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isIntField(field)) {
return filterings.gte(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.gte(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"gte\" for field: " + field);
}
} else if (lowercase.startsWith(">=")) {
final Tokenizer remaining = afterLeft.substringAfter(">=");
if (isIntField(field)) {
return filterings.gte(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.gte(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \">=\" for field: " + field);
}
} else if (lowercase.startsWith("gt ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isIntField(field)) {
return filterings.gt(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.gt(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"gt\" for field: " + field);
}
} else if (lowercase.startsWith(">")) {
final Tokenizer remaining = afterLeft.substringAfter(">");
if (isIntField(field)) {
return filterings.gt(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.gt(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \">\" for field: " + field);
}
} else if (lowercase.startsWith("lte ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isIntField(field)) {
return filterings.lte(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.lte(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"lte\" for field: " + field);
}
} else if (lowercase.startsWith("<=")) {
final Tokenizer remaining = afterLeft.substringAfter("<=");
if (isIntField(field)) {
return filterings.lte(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.lte(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"<=\" for field: " + field);
}
} else if (lowercase.startsWith("lt ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isIntField(field)) {
return filterings.lt(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.lt(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"lt\" for field: " + field);
}
} else if (lowercase.startsWith("<")) {
final Tokenizer remaining = afterLeft.substringAfter("<");
if (isIntField(field)) {
return filterings.lt(field, parseInt(remaining));
} else if (isDateTimeField(field)) {
return filterings.lt(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"<\" for field: " + field);
}
} else if (lowercase.startsWith("eq ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isStringField(field)) {
return filterings.eq(field, parseString(remaining));
} else if (isIntField(field)) {
return filterings.eq(field, parseInt(remaining));
} else if (isBooleanField(field)) {
return filterings.eq(field, parseBoolean(remaining));
} else if (isEnumField(field)) {
return filterings.eq(field, parseEnum(field, remaining));
} else if (isDateTimeField(field)) {
return filterings.eq(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"eq\" for field: " + field);
}
} else if (lowercase.startsWith("==")) {
final Tokenizer remaining = afterLeft.substringAfter("==");
if (isStringField(field)) {
return filterings.eq(field, parseString(remaining));
} else if (isIntField(field)) {
return filterings.eq(field, parseInt(remaining));
} else if (isBooleanField(field)) {
return filterings.eq(field, parseBoolean(remaining));
} else if (isEnumField(field)) {
return filterings.eq(field, parseEnum(field, remaining));
} else if (isDateTimeField(field)) {
return filterings.eq(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"==\" for field: " + field);
}
} else if (lowercase.startsWith("=")) {
final Tokenizer remaining = afterLeft.substringAfter("=");
if (isStringField(field)) {
return filterings.eq(field, parseString(remaining));
} else if (isIntField(field)) {
return filterings.eq(field, parseInt(remaining));
} else if (isBooleanField(field)) {
return filterings.eq(field, parseBoolean(remaining));
} else if (isEnumField(field)) {
return filterings.eq(field, parseEnum(field, remaining));
} else if (isDateTimeField(field)) {
return filterings.eq(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"=\" for field: " + field);
}
} else if (lowercase.startsWith("ne ") || lowercase.startsWith("neq ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isStringField(field)) {
return filterings.neq(field, parseString(remaining));
} else if (isIntField(field)) {
return filterings.neq(field, parseInt(remaining));
} else if (isBooleanField(field)) {
return filterings.neq(field, parseBoolean(remaining));
} else if (isEnumField(field)) {
return filterings.neq(field, parseEnum(field, remaining));
} else if (isDateTimeField(field)) {
return filterings.neq(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"neq\" for field: " + field);
}
} else if (lowercase.startsWith("!=")) {
final Tokenizer remaining = afterLeft.substringAfter("!=");
if (isStringField(field)) {
return filterings.neq(field, parseString(remaining));
} else if (isIntField(field)) {
return filterings.neq(field, parseInt(remaining));
} else if (isBooleanField(field)) {
return filterings.neq(field, parseBoolean(remaining));
} else if (isEnumField(field)) {
return filterings.neq(field, parseEnum(field, remaining));
} else if (isDateTimeField(field)) {
return filterings.neq(field, parseDateTime(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"!=\" for field: " + field);
}
} else if (lowercase.startsWith("not ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
final T f = parse(remaining.getCurrent());
return FilteringsFactory.not(f);
} else if (lowercase.startsWith("!")) {
final Tokenizer remaining = afterLeft.substringAfter("!");
final T f = parse(remaining.getCurrent());
return FilteringsFactory.not(f);
} else if (lowercase.startsWith("contains ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isStringField(field)) {
return filterings.contains(field, parseString(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"contains\" for field: " + field);
}
} else if (lowercase.startsWith("doesnt_contain ") || lowercase.startsWith("doesntcontain ")) {
final Tokenizer remaining = afterLeft.substringAfter(" ");
if (isStringField(field)) {
return filterings.doesntContain(field, parseString(remaining));
} else {
throw new FilterSyntaxException("Illegal use of \"doesnt_contain\" for field: " + field);
}
}
throw new NotImplementedException("query: " + afterLeft);
}
private Arg parseString(final Tokenizer tokenizer) throws FilterSyntaxException {
checkNotNull(tokenizer, "tokenizer");
final ParsedArg arg = tokenizer.nextArg();
if (arg.isNull()) {
return NullArg.INSTANCE;
}
return new StringArg(arg.s);
}
private Arg parseInt(final Tokenizer tokenizer) throws FilterSyntaxException {
checkNotNull(tokenizer, "tokenizer");
final ParsedArg arg = tokenizer.nextArg();
if (arg.isNull()) {
return NullArg.INSTANCE;
}
return new IntArg(arg.s);
}
private Arg parseBoolean(final Tokenizer tokenizer) throws FilterSyntaxException {
checkNotNull(tokenizer, "tokenizer");
final ParsedArg arg = tokenizer.nextArg();
if (arg.isNull()) {
return NullArg.INSTANCE;
}
return new BooleanArg(arg.s);
}
private Arg parseDateTime(final Tokenizer tokenizer) throws FilterSyntaxException {
checkNotNull(tokenizer, "tokenizer");
final ParsedArg arg = tokenizer.nextArg();
if (arg.isNull()) {
return NullArg.INSTANCE;
}
return new DateTimeArg(arg.s);
}
private Arg parseEnum(final U field, final Tokenizer tokenizer) throws FilterSyntaxException {
checkNotNull(field, "field");
checkNotNull(tokenizer, "tokenizer");
final ParsedArg arg = tokenizer.nextArg();
if (arg.isNull()) {
return NullArg.INSTANCE;
}
final Class extends Enum>> enumClass = FieldUtils.getEnumFieldClass(field);
return new EnumArg(enumClass, arg.s);
}
private T handleNot(final String notKeyword, final Tokenizer tokenizer) throws FilterSyntaxException {
// parser.substringAfter(notKeyword);
final String trim2 = substringAfter(tokenizer.getCurrent(), notKeyword);
if (!trim2.startsWith("(")) {
throw new FilterSyntaxException(
"\"not\" should be followed by a parenthesis, but was: " + tokenizer.getCurrent());
}
final String left = getLeftParenthesisExpression(trim2);
final String remaining = left.substring(1, left.length() - 1);
if (left.length() == trim2.length()) {
tokenizer.setCurrent(null);
// return instanceNot(parse(parser.setCurrent(trim2.substring(1, trim2.length()
// - 1))));
final T f = parse(remaining);
return FilteringsFactory.not(f);
}
tokenizer.substringAfter(left);
final T leftFiltering = FilteringsFactory.not(parse(remaining));
return parseRight(leftFiltering, tokenizer);
}
private static String getLeftParenthesisExpression(final String trim) throws FilterSyntaxException {
checkArgument(trim != null && trim.length() != 0 && trim.charAt(0) == '(', //
"trim: %s", trim);
final StringBuilder sb = new StringBuilder();
int count = 0;
boolean escape = false;
boolean inDoubleQuotes = false;
boolean inSimpleQuotes = false;
for (char c : trim.toCharArray()) {
switch (c) {
case '\\':
escape = !escape;
break;
case '"':
if (!inSimpleQuotes && !escape) {
inDoubleQuotes = !inDoubleQuotes;
}
escape = false;
break;
case '\'':
if (!inDoubleQuotes && !escape) {
inSimpleQuotes = !inSimpleQuotes;
}
escape = false;
break;
case '(':
if (!inDoubleQuotes && !inSimpleQuotes) {
++count;
}
escape = false;
break;
case ')':
if (!inDoubleQuotes && !inSimpleQuotes) {
--count;
}
escape = false;
break;
default:
break;
}
sb.append(c);
if (count == 0) {
return sb.toString();
}
}
throw new FilterSyntaxException("Unclosed parenthesis in: " + trim);
}
private T parseRight(final T leftFiltering, final Tokenizer tokenizer) throws FilterSyntaxException {
checkNotNull(leftFiltering, "leftFiltering");
checkNotNull(tokenizer, "tokenizer");
final String trim = tokenizer.normalizeSpace().toLowerCase(ENGLISH);
if (trim.startsWith("and ")) {
return parseRightAndConnector(leftFiltering, tokenizer.substringAfter(4));
} else if (trim.startsWith("&&")) {
return parseRightAndConnector(leftFiltering, tokenizer.substringAfter(2));
} else if (trim.startsWith("or ")) {
return parseRightOrConnector(leftFiltering, tokenizer.substringAfter(3));
} else if (trim.startsWith("||")) {
return parseRightOrConnector(leftFiltering, tokenizer.substringAfter(2));
}
throw new FilterSyntaxException("expression: " + trim);
}
private T parseRightAndConnector(final T leftFiltering, final Tokenizer tokenizer) throws FilterSyntaxException {
final T rightFiltering = parse(tokenizer);
final T concat = concatAndConnectors(leftFiltering, rightFiltering);
if (!tokenizer.hasNext()) {
return concat;
} else {
return parseRight(concat, tokenizer);
}
}
private T parseRightOrConnector(final T leftFiltering, final Tokenizer tokenizer) throws FilterSyntaxException {
final T rightFiltering = parse(tokenizer);
final T concat = concatOrConnectors(leftFiltering, rightFiltering);
if (!tokenizer.hasNext()) {
return concat;
} else {
return parseRight(concat, tokenizer);
}
}
private static > boolean isAndProxy(final T proxy) {
return (proxy instanceof ConnectorProxy) //
&& "and".contentEquals(((ConnectorProxy, ?>) proxy).getConnector());
}
private static > boolean isOrProxy(final T proxy) {
return (proxy instanceof ConnectorProxy) //
&& "or".contentEquals(((ConnectorProxy, ?>) proxy).getConnector());
}
@SuppressWarnings({ "unchecked" })
private T concatAndConnectors(final T leftFiltering, final T rightFiltering) {
final AndProxy andProxy;
if (isAndProxy(leftFiltering) && isAndProxy(rightFiltering)) {
andProxy = new AndProxy(filteringClass, //
((ConnectorProxy) leftFiltering).getFs(), //
((ConnectorProxy) rightFiltering).getFs());
} else if (isAndProxy(leftFiltering)) {
andProxy = new AndProxy(filteringClass, //
((ConnectorProxy) leftFiltering).getFs(), //
rightFiltering);
} else if (isAndProxy(rightFiltering)) {
andProxy = new AndProxy(filteringClass, //
leftFiltering, //
((ConnectorProxy) rightFiltering).getFs());
} else {
andProxy = new AndProxy(filteringClass, //
leftFiltering, rightFiltering);
}
return FilteringsFactory.proxy(new Class>[] { //
ConnectorProxy.class, //
filteringClass, //
}, andProxy);
}
@SuppressWarnings({ "unchecked" })
private T concatOrConnectors(final T leftFiltering, final T rightFiltering) {
final OrProxy orProxy;
if (isOrProxy(leftFiltering) && isOrProxy(rightFiltering)) {
orProxy = new OrProxy(filteringClass, //
((ConnectorProxy) leftFiltering).getFs(), //
((ConnectorProxy) rightFiltering).getFs());
} else if (isOrProxy(leftFiltering)) {
orProxy = new OrProxy(filteringClass, //
((ConnectorProxy) leftFiltering).getFs(), //
rightFiltering);
} else if (isOrProxy(rightFiltering)) {
orProxy = new OrProxy(filteringClass, //
leftFiltering, //
((ConnectorProxy) rightFiltering).getFs());
} else {
orProxy = new OrProxy(filteringClass, //
leftFiltering, rightFiltering);
}
return FilteringsFactory.proxy(new Class>[] { //
ConnectorProxy.class, //
filteringClass, //
}, orProxy);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy