| // Copyright (C) 2009 The Android Open Source Project |
| // |
| // 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 com.google.gerrit.server.query; |
| |
| import static com.google.gerrit.server.query.Predicate.and; |
| import static com.google.gerrit.server.query.Predicate.not; |
| import static com.google.gerrit.server.query.Predicate.or; |
| import static com.google.gerrit.server.query.QueryParser.AND; |
| import static com.google.gerrit.server.query.QueryParser.DEFAULT_FIELD; |
| import static com.google.gerrit.server.query.QueryParser.EXACT_PHRASE; |
| import static com.google.gerrit.server.query.QueryParser.FIELD_NAME; |
| import static com.google.gerrit.server.query.QueryParser.NOT; |
| import static com.google.gerrit.server.query.QueryParser.OR; |
| import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD; |
| import static com.google.gerrit.server.query.QueryParser.VARIABLE_ASSIGN; |
| |
| import org.antlr.runtime.tree.Tree; |
| |
| import java.lang.annotation.ElementType; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.annotation.Target; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Base class to support writing parsers for query languages. |
| * <p> |
| * Subclasses may document their supported query operators by declaring public |
| * methods that perform the query conversion into a {@link Predicate}. For |
| * example, to support "is:starred", "is:unread", and nothing else, a subclass |
| * may write: |
| * |
| * <pre> |
| * @Operator |
| * public Predicate is(final String value) { |
| * if ("starred".equals(value)) { |
| * return new StarredPredicate(); |
| * } |
| * if ("unread".equals(value)) { |
| * return new UnreadPredicate(); |
| * } |
| * throw new IllegalArgumentException(); |
| * } |
| * </pre> |
| * <p> |
| * The available operator methods are discovered at runtime via reflection. |
| * Method names (after being converted to lowercase), correspond to operators in |
| * the query language, method string values correspond to the operator argument. |
| * Methods must be declared {@code public}, returning {@link Predicate}, |
| * accepting one {@link String}, and annotated with the {@link Operator} |
| * annotation. |
| * <p> |
| * Subclasses may also declare a handler for values which appear without |
| * operator by overriding {@link #defaultField(String)}. |
| * |
| * @param <T> type of object the predicates can evaluate in memory. |
| */ |
| public abstract class QueryBuilder<T> { |
| /** |
| * Defines the operators known by a QueryBuilder. |
| * |
| * This class is thread-safe and may be reused or cached. |
| * |
| * @param <T> type of object the predicates can evaluate in memory. |
| * @param <Q> type of the query builder subclass. |
| */ |
| public static class Definition<T, Q extends QueryBuilder<T>> { |
| private final Map<String, OperatorFactory<T, Q>> opFactories = |
| new HashMap<String, OperatorFactory<T, Q>>(); |
| |
| public Definition(Class<Q> clazz) { |
| // Guess at the supported operators by scanning methods. |
| // |
| Class<?> c = clazz; |
| while (c != QueryBuilder.class) { |
| for (final Method method : c.getDeclaredMethods()) { |
| if (method.getAnnotation(Operator.class) != null |
| && Predicate.class.isAssignableFrom(method.getReturnType()) |
| && method.getParameterTypes().length == 1 |
| && method.getParameterTypes()[0] == String.class |
| && (method.getModifiers() & Modifier.ABSTRACT) == 0 |
| && (method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) { |
| final String name = method.getName().toLowerCase(); |
| if (!opFactories.containsKey(name)) { |
| opFactories.put(name, new ReflectionFactory<T, Q>(name, method)); |
| } |
| } |
| } |
| c = c.getSuperclass(); |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private final Map<String, OperatorFactory> opFactories; |
| |
| @SuppressWarnings("unchecked") |
| protected QueryBuilder(Definition<T, ? extends QueryBuilder<T>> def) { |
| opFactories = (Map) def.opFactories; |
| } |
| |
| /** |
| * Parse a user supplied query string into a predicate. |
| * |
| * @param query the query string. |
| * @return predicate representing the user query. |
| * @throws QueryParseException the query string is invalid and cannot be |
| * parsed by this parser. This may be due to a syntax error, may be |
| * due to an operator not being supported, or due to an invalid value |
| * being passed to a recognized operator. |
| */ |
| public Predicate<T> parse(final String query) throws QueryParseException { |
| return toPredicate(QueryParser.parse(query)); |
| } |
| |
| private Predicate<T> toPredicate(final Tree r) throws QueryParseException, |
| IllegalArgumentException { |
| switch (r.getType()) { |
| case AND: |
| return and(children(r)); |
| case OR: |
| return or(children(r)); |
| case NOT: |
| return not(toPredicate(onlyChildOf(r))); |
| |
| case DEFAULT_FIELD: |
| return defaultField(onlyChildOf(r)); |
| |
| case FIELD_NAME: |
| return operator(r.getText(), onlyChildOf(r)); |
| |
| case VARIABLE_ASSIGN: { |
| final String var = r.getText(); |
| final Tree opTree = onlyChildOf(r); |
| if (opTree.getType() == FIELD_NAME) { |
| final Tree val = onlyChildOf(opTree); |
| if (val.getType() == SINGLE_WORD && "*".equals(val.getText())) { |
| final String op = opTree.getText(); |
| final WildPatternPredicate<T> pat = new WildPatternPredicate<T>(op); |
| return new VariablePredicate<T>(var, pat); |
| } |
| } |
| return new VariablePredicate<T>(var, toPredicate(opTree)); |
| } |
| |
| default: |
| throw error("Unsupported operator: " + r); |
| } |
| } |
| |
| private Predicate<T> operator(final String name, final Tree val) |
| throws QueryParseException { |
| switch (val.getType()) { |
| // Expand multiple values, "foo:(a b c)", as though they were written |
| // out with the longer form, "foo:a foo:b foo:c". |
| // |
| case AND: |
| case OR: { |
| List<Predicate<T>> p = new ArrayList<Predicate<T>>(val.getChildCount()); |
| for (int i = 0; i < val.getChildCount(); i++) { |
| final Tree c = val.getChild(i); |
| if (c.getType() != DEFAULT_FIELD) { |
| throw error("Nested operator not expected: " + c); |
| } |
| p.add(operator(name, onlyChildOf(c))); |
| } |
| return val.getType() == AND ? and(p) : or(p); |
| } |
| |
| case SINGLE_WORD: |
| case EXACT_PHRASE: |
| if (val.getChildCount() != 0) { |
| throw error("Expected no children under: " + val); |
| } |
| return operator(name, val.getText()); |
| |
| default: |
| throw error("Unsupported node in operator " + name + ": " + val); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Predicate<T> operator(final String name, final String value) |
| throws QueryParseException { |
| final OperatorFactory f = opFactories.get(name); |
| if (f == null) { |
| throw error("Unsupported operator " + name + ":" + value); |
| } |
| return f.create(this, value); |
| } |
| |
| private Predicate<T> defaultField(final Tree r) throws QueryParseException { |
| switch (r.getType()) { |
| case SINGLE_WORD: |
| case EXACT_PHRASE: |
| if (r.getChildCount() != 0) { |
| throw error("Expected no children under: " + r); |
| } |
| return defaultField(r.getText()); |
| |
| default: |
| throw error("Unsupported node: " + r); |
| } |
| } |
| |
| /** |
| * Handle a value present outside of an operator. |
| * <p> |
| * This default implementation always throws an "Unsupported query: " message |
| * containing the input text. Subclasses may override this method to perform |
| * do-what-i-mean guesses based on the input string. |
| * |
| * @param value the value supplied by itself in the query. |
| * @return predicate representing this value. |
| * @throws QueryParseException the parser does not recognize this value. |
| */ |
| protected Predicate<T> defaultField(final String value) |
| throws QueryParseException { |
| throw error("Unsupported query:" + value); |
| } |
| |
| /** |
| * Locate a predicate in the predicate tree. |
| * |
| * @param p the predicate to find. |
| * @param clazz type of the predicate instance. |
| * @return the predicate, null if not found. |
| */ |
| @SuppressWarnings("unchecked") |
| public <P extends Predicate<T>> P find(Predicate<T> p, Class<P> clazz) { |
| if (clazz.isAssignableFrom(p.getClass())) { |
| return (P) p; |
| } |
| |
| for (Predicate<T> c : p.getChildren()) { |
| P r = find(c, clazz); |
| if (r != null) { |
| return r; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Locate a predicate in the predicate tree. |
| * |
| * @param p the predicate to find. |
| * @param clazz type of the predicate instance. |
| * @param name name of the operator. |
| * @return the predicate, null if not found. |
| */ |
| @SuppressWarnings("unchecked") |
| public <P extends OperatorPredicate<T>> P find(Predicate<T> p, |
| Class<P> clazz, String name) { |
| if (p instanceof OperatorPredicate |
| && ((OperatorPredicate) p).getOperator().equals(name) |
| && clazz.isAssignableFrom(p.getClass())) { |
| return (P) p; |
| } |
| |
| for (Predicate<T> c : p.getChildren()) { |
| P r = find(c, clazz, name); |
| if (r != null) { |
| return r; |
| } |
| } |
| |
| return null; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Predicate<T>[] children(final Tree r) throws QueryParseException, |
| IllegalArgumentException { |
| final Predicate<T>[] p = new Predicate[r.getChildCount()]; |
| for (int i = 0; i < p.length; i++) { |
| p[i] = toPredicate(r.getChild(i)); |
| } |
| return p; |
| } |
| |
| private Tree onlyChildOf(final Tree r) throws QueryParseException { |
| if (r.getChildCount() != 1) { |
| throw error("Expected exactly one child: " + r); |
| } |
| return r.getChild(0); |
| } |
| |
| protected static QueryParseException error(String msg) { |
| return new QueryParseException(msg); |
| } |
| |
| protected static QueryParseException error(String msg, Throwable why) { |
| return new QueryParseException(msg, why); |
| } |
| |
| /** Converts a value string passed to an operator into a {@link Predicate}. */ |
| protected interface OperatorFactory<T, Q extends QueryBuilder<T>> { |
| Predicate<T> create(Q builder, String value) throws QueryParseException; |
| } |
| |
| /** Denotes a method which is a query operator. */ |
| @Retention(RetentionPolicy.RUNTIME) |
| @Target(ElementType.METHOD) |
| protected @interface Operator { |
| } |
| |
| private static class ReflectionFactory<T, Q extends QueryBuilder<T>> |
| implements OperatorFactory<T, Q> { |
| private final String name; |
| private final Method method; |
| |
| ReflectionFactory(final String name, final Method method) { |
| this.name = name; |
| this.method = method; |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public Predicate<T> create(Q builder, String value) |
| throws QueryParseException { |
| try { |
| return (Predicate<T>) method.invoke(builder, value); |
| } catch (RuntimeException e) { |
| throw error("Error in operator " + name + ":" + value, e); |
| } catch (IllegalAccessException e) { |
| throw error("Error in operator " + name + ":" + value, e); |
| } catch (InvocationTargetException e) { |
| if (e.getCause() instanceof QueryParseException) { |
| throw (QueryParseException) e.getCause(); |
| } |
| throw error("Error in operator " + name + ":" + value, e.getCause()); |
| } |
| } |
| } |
| } |