/** * Original code taken from https://code.google.com/p/gql4j . * * Distributed under EPL 1.0 license. */ package org.jboss.capedwarf.gql4j; import java.math.BigDecimal; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.FetchOptions; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import com.google.appengine.api.datastore.Query; import com.google.appengine.api.datastore.Query.FilterOperator; import com.google.appengine.api.datastore.Query.SortDirection; import com.google.appengine.api.users.User; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CharStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.TokenStream; import org.jboss.capedwarf.gql4j.antlr.GQLLexer; import org.jboss.capedwarf.gql4j.antlr.GQLParser; /** * @author Max Zhu (thebbsky@gmail.com) * @author Ales Justin (or the one who copy/pasted this here :-) */ public class GqlQuery { private Query query; private FetchOptions fetchOptions; public GqlQuery(String queryStr, Map<String, Object> context) { build(queryStr, context); } public GqlQuery(String queryStr, Object... params) { // evaluation context Map<String, Object> context = Maps.newHashMap(); for (int i = 0; i < params.length; i++) { context.put(String.valueOf(i + 1), params[i]); } build(queryStr, context); } private void build(String queryStr, Map<String, Object> context) { Preconditions.checkNotNull(queryStr); ParseResult r = parse(queryStr); // from clause if (r.from == null) { this.query = new Query(); } else { this.query = new Query(r.from.kind); } // select clause if (r.select.isKeyOnly()) { this.query.setKeysOnly(); } // where clause if (r.where != null) { List<Query.Filter> filters = new ArrayList<>(); for (Condition c : r.where.conditions) { filters.add(new Query.FilterPredicate(c.propertyName, c.operator, c.e.evaluate(context))); } if (filters.size() == 1) { this.query.setFilter(filters.get(0)); } else if (filters.size() > 1) { // TODO -- always AND? this.query.setFilter(new Query.CompositeFilter(Query.CompositeFilterOperator.AND, filters)); } // set ancester if (r.where.ancestor != null) { this.query.setAncestor(r.where.ancestor.ancestorKey(context)); } } // order by if (r.orderBy != null) { for (OrderByItem o : r.orderBy.items) { this.query.addSort(o.propertyName, o.direction); } } // limit if (r.limit != null) { this.fetchOptions = FetchOptions.Builder.withLimit(r.limit.limit); } if (r.offset != null) { if (this.fetchOptions == null) { this.fetchOptions = FetchOptions.Builder.withDefaults(); } this.fetchOptions.offset(r.offset.offset); } } static ParseResult parse(String queryStr) { try { CharStream input = new ANTLRStringStream(queryStr); GQLLexer lexer = new GQLLexer(input); TokenStream tokens = new CommonTokenStream(lexer); GQLParser parser = new GQLParser(tokens); return parser.query().r; } catch (RecognitionException e) { throw new GqlQueryException("GQL syntax error"); } } public Query query() { return query; } public FetchOptions fetchOptions() { if (fetchOptions == null) { fetchOptions = FetchOptions.Builder.withDefaults(); } return fetchOptions; } public static class GqlQueryException extends RuntimeException { private static final long serialVersionUID = 1L; public GqlQueryException() { super(); } public GqlQueryException(String message, Throwable cause) { super(message, cause); } public GqlQueryException(String message) { super(message); } public GqlQueryException(Throwable cause) { super(cause); } } public static class ParseResult { private Select select; private From from; private Where where; private OrderBy orderBy; private Limit limit; private Offset offset; public Select getSelect() { return select; } public ParseResult setSelect(Select select) { this.select = select; return this; } public From getFrom() { return from; } public ParseResult setFrom(From from) { this.from = from; return this; } public Where getWhere() { return where; } public ParseResult setWhere(Where where) { this.where = where; return this; } public OrderBy getOrderBy() { return orderBy; } public ParseResult setOrderBy(OrderBy orderBy) { this.orderBy = orderBy; return this; } public Limit getLimit() { return limit; } public ParseResult setLimit(Limit limit) { this.limit = limit; return this; } public Offset getOffset() { return offset; } public ParseResult setOffset(Offset offset) { this.offset = offset; return this; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((from == null) ? 0 : from.hashCode()); result = prime * result + ((limit == null) ? 0 : limit.hashCode()); result = prime * result + ((offset == null) ? 0 : offset.hashCode()); result = prime * result + ((orderBy == null) ? 0 : orderBy.hashCode()); result = prime * result + ((select == null) ? 0 : select.hashCode()); result = prime * result + ((where == null) ? 0 : where.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ParseResult other = (ParseResult) obj; if (from == null) { if (other.from != null) return false; } else if (!from.equals(other.from)) return false; if (limit == null) { if (other.limit != null) return false; } else if (!limit.equals(other.limit)) return false; if (offset == null) { if (other.offset != null) return false; } else if (!offset.equals(other.offset)) return false; if (orderBy == null) { if (other.orderBy != null) return false; } else if (!orderBy.equals(other.orderBy)) return false; if (select == null) { if (other.select != null) return false; } else if (!select.equals(other.select)) return false; if (where == null) { if (other.where != null) return false; } else if (!where.equals(other.where)) return false; return true; } @Override public String toString() { return "ParseResult [select=" + select + ", from=" + from + ", where=" + where + ", orderBy=" + orderBy + ", limit=" + limit + ", offset=" + offset + "]"; } } @SuppressWarnings("RedundantIfStatement") public static class Select { /** * select item, only * and __key__ is allowed (case sensitive) */ private final boolean keyOnly; public Select(boolean keyOnly) { super(); this.keyOnly = keyOnly; } public boolean isKeyOnly() { return this.keyOnly; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (keyOnly ? 1231 : 1237); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Select other = (Select) obj; if (keyOnly != other.keyOnly) return false; return true; } @Override public String toString() { return "Select [keyOnly=" + keyOnly + "]"; } } public static class From { private final String kind; public From(String kind) { super(); this.kind = kind; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((kind == null) ? 0 : kind.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; From other = (From) obj; if (kind == null) { if (other.kind != null) return false; } else if (!kind.equals(other.kind)) return false; return true; } @Override public String toString() { return "From [kind=" + kind + "]"; } } public static class Where { private final List<Condition> conditions; private Ancestor ancestor; public Where() { conditions = Lists.newLinkedList(); } public Where withCondition(Condition condition) { this.conditions.add(condition); return this; } public Where withAncestor(Evaluator e) { ancestor = new Ancestor(e); return this; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((ancestor == null) ? 0 : ancestor.hashCode()); result = prime * result + ((conditions == null) ? 0 : conditions.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Where other = (Where) obj; if (ancestor == null) { if (other.ancestor != null) return false; } else if (!ancestor.equals(other.ancestor)) return false; if (conditions == null) { if (other.conditions != null) return false; } else if (!conditions.equals(other.conditions)) return false; return true; } @Override public String toString() { return "Where [conditions=" + conditions + ", ancestor=" + ancestor + "]"; } } public static class Ancestor { private final Evaluator e; public Ancestor(Evaluator e) { super(); this.e = e; } public Key ancestorKey(Map<String, Object> context) { Object val = e.evaluate(context); if (val instanceof Key) { return (Key) val; } else if (val instanceof Entity) { return ((Entity) val).getKey(); } else { throw new GqlQueryException("Invalid GQL query string. ANCESTOR IS must be followed by Key or Entity"); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((e == null) ? 0 : e.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Ancestor other = (Ancestor) obj; if (e == null) { if (other.e != null) return false; } else if (!e.equals(other.e)) return false; return true; } @Override public String toString() { return "Ancestor [e=" + e + "]"; } } public static class OrderBy { private final List<OrderByItem> items; public OrderBy() { items = Lists.newLinkedList(); } public OrderBy withItem(OrderByItem item) { this.items.add(item); return this; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((items == null) ? 0 : items.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; OrderBy other = (OrderBy) obj; if (items == null) { if (other.items != null) return false; } else if (!items.equals(other.items)) return false; return true; } @Override public String toString() { return "OrderBy [items=" + items + "]"; } } public static class Limit { private final Integer limit; public Limit(Integer limit) { super(); this.limit = limit; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((limit == null) ? 0 : limit.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Limit other = (Limit) obj; if (limit == null) { if (other.limit != null) return false; } else if (!limit.equals(other.limit)) return false; return true; } @Override public String toString() { return "Limit [limit=" + limit + "]"; } } public static class Offset { private final Integer offset; public Offset(Integer offset) { super(); this.offset = offset; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((offset == null) ? 0 : offset.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Offset other = (Offset) obj; if (offset == null) { if (other.offset != null) return false; } else if (!offset.equals(other.offset)) return false; return true; } @Override public String toString() { return "Offset [offset=" + offset + "]"; } } /** * where condition * * @author Max Zhu (thebbsky@gmail.com) */ public static class Condition { private String propertyName; private FilterOperator operator; private Evaluator e; public Condition(String propertyName, FilterOperator operator, Evaluator e) { super(); Preconditions.checkNotNull(propertyName); Preconditions.checkNotNull(operator); Preconditions.checkNotNull(e); this.propertyName = propertyName; this.operator = operator; this.e = e; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((e == null) ? 0 : e.hashCode()); result = prime * result + ((operator == null) ? 0 : operator.hashCode()); result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Condition other = (Condition) obj; if (e == null) { if (other.e != null) return false; } else if (!e.equals(other.e)) return false; if (operator != other.operator) return false; if (propertyName == null) { if (other.propertyName != null) return false; } else if (!propertyName.equals(other.propertyName)) return false; return true; } @Override public String toString() { return "Condition [propertyName=" + propertyName + ", operator=" + operator + ", e=" + e + "]"; } } /** * @author Max Zhu (thebbsky@gmail.com) */ public static class OrderByItem { private String propertyName; private SortDirection direction; public OrderByItem(String propertyName) { super(); this.propertyName = propertyName; this.direction = SortDirection.ASCENDING; } public OrderByItem setDirection(boolean ascending) { this.direction = ascending ? SortDirection.ASCENDING : SortDirection.DESCENDING; return this; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((direction == null) ? 0 : direction.hashCode()); result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; OrderByItem other = (OrderByItem) obj; if (direction != other.direction) return false; if (propertyName == null) { if (other.propertyName != null) return false; } else if (!propertyName.equals(other.propertyName)) return false; return true; } @Override public String toString() { return "OrderByItem [propertyName=" + propertyName + ", direction=" + direction + "]"; } } public static interface Evaluator { public Object evaluate(Map<String, Object> context); } public static class NullEvaluator implements Evaluator { private static NullEvaluator singleton; public static synchronized NullEvaluator get() { if (singleton == null) { singleton = new NullEvaluator(); } return singleton; } private NullEvaluator() { } @Override public Object evaluate(Map<String, Object> context) { return null; } } public static class DecimalEvaluator implements Evaluator { private final BigDecimal payload; public DecimalEvaluator(String strNumber) { payload = new BigDecimal(strNumber); } @Override public Object evaluate(Map<String, Object> context) { if ((double) payload.longValue() == payload.doubleValue()) { return this.payload.longValue(); } else { return this.payload.doubleValue(); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((payload == null) ? 0 : payload.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DecimalEvaluator other = (DecimalEvaluator) obj; if (payload == null) { if (other.payload != null) return false; } else if (!payload.equals(other.payload)) return false; return true; } @Override public String toString() { return "DecimalEvaluator [payload=" + payload + "]"; } } public static class StringEvaluator implements Evaluator { private final String payload; public StringEvaluator(String rawText) { super(); // remove single quote String withoutQuote = rawText.substring(1, rawText.length() - 1); // replace \' with ' this.payload = withoutQuote.replace("\\'", "'"); } @Override public Object evaluate(Map<String, Object> context) { return this.payload; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((payload == null) ? 0 : payload.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; StringEvaluator other = (StringEvaluator) obj; if (payload == null) { if (other.payload != null) return false; } else if (!payload.equals(other.payload)) return false; return true; } @Override public String toString() { return "StringEvaluator [payload=" + payload + "]"; } } public static class BooleanEvaluator implements Evaluator { private final Boolean payload; public BooleanEvaluator(String input) { this.payload = Boolean.valueOf(input); } @Override public Object evaluate(Map<String, Object> context) { return this.payload; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((payload == null) ? 0 : payload.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BooleanEvaluator other = (BooleanEvaluator) obj; if (payload == null) { if (other.payload != null) return false; } else if (!payload.equals(other.payload)) return false; return true; } @Override public String toString() { return "BooleanEvaluator [payload=" + payload + "]"; } } @SuppressWarnings("RedundantIfStatement") public static class FunctionEvaluator implements Evaluator { public enum Type { DATETIME, DATE, TIME, KEY, USER, GEOPT } private final Type type; private final List<Evaluator> ops; public FunctionEvaluator(String type, Evaluator... ops) { this.type = Type.valueOf(type.toUpperCase()); this.ops = Lists.newArrayList(ops); } public FunctionEvaluator(String type, List<Evaluator> ops) { this.type = Type.valueOf(type.toUpperCase()); this.ops = ops; } public FunctionEvaluator withParam(Evaluator e) { this.ops.add(e); return this; } @Override public Object evaluate(Map<String, Object> context) { switch (this.type) { case DATETIME: return datetime(context); case DATE: return date(context); case TIME: return time(context); case GEOPT: return geopt(context); case KEY: return key(context); case USER: return user(context); default: // not supposed to happen throw new GqlQueryException("Invalid function type " + this.type); } } private static final String DEFAULT_AUTH_DOMAIN = "gmail.com"; private Object user(Map<String, Object> context) { if (this.ops.size() == 1) { Object val = this.ops.get(0).evaluate(context); if (val instanceof String) { return new User((String) val, DEFAULT_AUTH_DOMAIN); } else { throw new GqlQueryException("Invalid GQL query string. Function key: invalid input"); } } else { throw new GqlQueryException("Invalid GQL query string. Function key: wrong number of arguments"); } } private Object key(Map<String, Object> context) { if (this.ops.isEmpty()) { throw new GqlQueryException("Invalid GQL query string. Function key: wrong number of arguments"); } else if (this.ops.size() == 1) { // KEY('encoded key') String keyString = (String) this.ops.get(0).evaluate(context); return KeyFactory.stringToKey(keyString); } else if (this.ops.size() % 2 == 0) { // KEY('kind', 'name'/ID [, 'kind', 'name'/ID...]) try { Key key = null; Iterator<Evaluator> i = this.ops.iterator(); while (i.hasNext()) { String kind = (String) i.next().evaluate(context); Object nameId = i.next().evaluate(context); if (nameId instanceof String) { key = KeyFactory.createKey(key, kind, (String) nameId); } else if (nameId instanceof Long) { key = KeyFactory.createKey(key, kind, (Long) nameId); } } return key; } catch (ClassCastException e) { throw new GqlQueryException("Invalid GQL query string. Function key: invalid input", e); } } else { throw new GqlQueryException("Invalid GQL query string. Function key: wrong number of arguments"); } } @SuppressWarnings("UnusedParameters") private Object geopt(Map<String, Object> context) { return null; // TODO } private static DateFormat timeFmter = new SimpleDateFormat("HH:mm:ss"); private Object time(Map<String, Object> context) { if (this.ops.size() == 1) { // TIME('HH:MM:SS') Object r = this.ops.get(0).evaluate(context); if (r instanceof String) { try { return timeFmter.parse((String) r); } catch (ParseException e) { throw new GqlQueryException("Invalid GQL query string. Function time: invalid input", e); } } else { throw new GqlQueryException("Invalid GQL query string. Function time: invalid input"); } } else if (this.ops.size() == 3) { // TIME(hour, minute, second) try { int hour = ((Number) this.ops.get(0).evaluate(context)).intValue(); int minute = ((Number) this.ops.get(1).evaluate(context)).intValue(); int second = ((Number) this.ops.get(2).evaluate(context)).intValue(); Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR, hour); c.set(Calendar.MINUTE, minute); c.set(Calendar.SECOND, second); return c.getTime(); } catch (ClassCastException e) { throw new GqlQueryException("Invalid GQL query string. Function time: invalid input", e); } } else { throw new GqlQueryException("Invalid GQL query string. Function time: wrong number of arguments"); } } private static DateFormat dateFmter = new SimpleDateFormat("yyyy-MM-dd"); private Object date(Map<String, Object> context) { if (this.ops.size() == 1) { // DATE('YYYY-MM-DD') Object r = this.ops.get(0).evaluate(context); if (r instanceof String) { try { return dateFmter.parse((String) r); } catch (ParseException e) { throw new GqlQueryException("Invalid GQL query string. Function date: invalid input", e); } } else { throw new GqlQueryException("Invalid GQL query string. Function date: invalid input"); } } else if (this.ops.size() == 3) { // DATE(year, month, day) try { int year = ((Number) this.ops.get(0).evaluate(context)).intValue(); int month = ((Number) this.ops.get(1).evaluate(context)).intValue(); int day = ((Number) this.ops.get(2).evaluate(context)).intValue(); return createDate(year, month, day, 0, 0, 0); } catch (ClassCastException e) { throw new GqlQueryException("Invalid GQL query string. Function date: invalid input", e); } } else { throw new GqlQueryException("Invalid GQL query string. Function date: wrong number of arguments"); } } private static DateFormat datetimeFmter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private Object datetime(Map<String, Object> context) { if (this.ops.size() == 1) { // DATETIME('YYYY-MM-DD HH:MM:SS') Object r = this.ops.get(0).evaluate(context); if (r instanceof String) { try { return datetimeFmter.parse((String) r); } catch (ParseException e) { throw new GqlQueryException("Invalid GQL query string. Function datetime: invalid input", e); } } else { throw new GqlQueryException("Invalid GQL query string. Function datetime: invalid input"); } } else if (this.ops.size() == 6) { // DATETIME(year, month, day, hour, minute, second) try { int year = ((Number) this.ops.get(0).evaluate(context)).intValue(); int month = ((Number) this.ops.get(1).evaluate(context)).intValue(); int day = ((Number) this.ops.get(2).evaluate(context)).intValue(); int hour = ((Number) this.ops.get(3).evaluate(context)).intValue(); int minute = ((Number) this.ops.get(4).evaluate(context)).intValue(); int second = ((Number) this.ops.get(5).evaluate(context)).intValue(); return createDate(year, month, day, hour, minute, second); } catch (ClassCastException e) { throw new GqlQueryException("Invalid GQL query string. Function datetime: invalid input", e); } } else { throw new GqlQueryException("Invalid GQL query string. Function datetime: wrong number of arguments"); } } private Date createDate(int year, int month, int day, int hour, int minute, int second) { Calendar c = Calendar.getInstance(); c.clear(); c.set(Calendar.YEAR, year); c.set(Calendar.MONTH, month); c.set(Calendar.DATE, day); c.set(Calendar.HOUR, hour); c.set(Calendar.MINUTE, minute); c.set(Calendar.SECOND, second); return c.getTime(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((ops == null) ? 0 : ops.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FunctionEvaluator other = (FunctionEvaluator) obj; if (ops == null) { if (other.ops != null) return false; } else if (!ops.equals(other.ops)) return false; if (type != other.type) return false; return true; } @Override public String toString() { return "FunctionEvaluator [type=" + type + ", ops=" + ops + "]"; } } public static class ParamEvaluator implements Evaluator { private final String paramName; public ParamEvaluator(String paramName) { super(); this.paramName = paramName; } @Override public Object evaluate(Map<String, Object> context) { return context.get(paramName); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((paramName == null) ? 0 : paramName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ParamEvaluator other = (ParamEvaluator) obj; if (paramName == null) { if (other.paramName != null) return false; } else if (!paramName.equals(other.paramName)) return false; return true; } @Override public String toString() { return "ParamEvaluator [paramName=" + paramName + "]"; } } public static class ListEvaluator implements Evaluator { private final List<Evaluator> evaluators; public ListEvaluator(Evaluator... evaluators) { super(); this.evaluators = Lists.newArrayList(evaluators); } public ListEvaluator(List<Evaluator> evaluators) { super(); this.evaluators = evaluators; } @Override public Object evaluate(Map<String, Object> context) { List<Object> result = new ArrayList<Object>(evaluators.size()); for (Evaluator e : evaluators) { result.add(e.evaluate(context)); } return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((evaluators == null) ? 0 : evaluators.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ListEvaluator other = (ListEvaluator) obj; if (evaluators == null) { if (other.evaluators != null) return false; } else if (!evaluators.equals(other.evaluators)) return false; return true; } @Override public String toString() { return "ListEvaluator [evaluators=" + evaluators + "]"; } } }