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

net.avcompris.commons.query.impl.Parser Maven / Gradle / Ivy

There is a newer version: 0.6.3
Show newest version
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 filteringClass;
	private final Class fieldClass;

	private final Filterings filterings;

	public Parser( //
			final Class filteringsClass, //
			final Class filteringClass, //
			final Class 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> 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