App Engine Java SDK version 1.9.8
[gae.git] / java / src / main / com / google / appengine / api / datastore / Query.java
blob502b9cacb40198275e07b747f663c6f9e1c5de6d
1 // Copyright 2007 Google Inc. All rights reserved.
3 package com.google.appengine.api.datastore;
5 import static com.google.common.base.Preconditions.checkNotNull;
7 import com.google.appengine.api.NamespaceManager;
8 import com.google.common.base.Joiner;
9 import com.google.common.base.Preconditions;
10 import com.google.common.collect.ImmutableList;
11 import com.google.common.collect.Iterables;
12 import com.google.common.collect.Lists;
13 import com.google.common.collect.Maps;
15 import java.io.IOException;
16 import java.io.ObjectInputStream;
17 import java.io.Serializable;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Objects;
25 /**
26 * {@link Query} encapsulates a request for zero or more {@link Entity} objects
27 * out of the datastore. It supports querying on zero or more properties,
28 * querying by ancestor, and sorting. {@link Entity} objects which match the
29 * query can be retrieved in a single list, or with an unbounded iterator.
32 public final class Query implements Serializable {
34 @Deprecated
35 public static final String KIND_METADATA_KIND = Entities.KIND_METADATA_KIND;
37 @Deprecated
38 public static final String PROPERTY_METADATA_KIND = Entities.PROPERTY_METADATA_KIND;
40 @Deprecated
41 public static final String NAMESPACE_METADATA_KIND = Entities.NAMESPACE_METADATA_KIND;
43 static final long serialVersionUID = 7090652715949085374L;
45 /**
46 * SortDirection controls the order of a sort.
48 public enum SortDirection {
49 ASCENDING,
50 DESCENDING
53 /**
54 * Operators supported by {@link FilterPredicate}.
56 public enum FilterOperator {
57 LESS_THAN("<"),
58 LESS_THAN_OR_EQUAL("<="),
59 GREATER_THAN(">"),
60 GREATER_THAN_OR_EQUAL(">="),
61 EQUAL("="),
62 NOT_EQUAL("!="),
63 IN("IN");
65 private final String shortName;
66 private FilterOperator(String shortName) {
67 this.shortName = shortName;
70 @Override
71 public String toString() {
72 return shortName;
75 public FilterPredicate of(String propertyName, Object value) {
76 return new FilterPredicate(propertyName, this, value);
80 /**
81 * Operators supported by {@link CompositeFilter}.
83 public enum CompositeFilterOperator {
84 AND,
85 OR;
87 public static CompositeFilter and(Filter... subFilters) {
88 return and(Arrays.asList(subFilters));
91 public static CompositeFilter and(Collection<Filter> subFilters) {
92 return new CompositeFilter(AND, subFilters);
95 public static CompositeFilter or(Filter... subFilters) {
96 return or(Arrays.asList(subFilters));
99 public static CompositeFilter or(Collection<Filter> subFilters) {
100 return new CompositeFilter(OR, subFilters);
103 public CompositeFilter of(Filter... subFilters) {
104 return new CompositeFilter(this, Arrays.asList(subFilters));
107 public CompositeFilter of(Collection<Filter> subFilters) {
108 return new CompositeFilter(this, subFilters);
112 private final String kind;
113 private final List<SortPredicate> sortPredicates = Lists.newArrayList();
114 private final List<FilterPredicate> filterPredicates = Lists.newArrayList();
115 private Filter filter;
116 private Key ancestor;
117 private boolean keysOnly;
118 private final Map<String, Projection> projectionMap = Maps.newHashMap();
119 private boolean distinct;
120 private AppIdNamespace appIdNamespace;
122 private transient String fullTextSearch;
125 * Create a new kindless {@link Query} that finds {@link Entity} objects.
126 * Note that kindless queries are not yet supported in the Java dev
127 * appserver.
129 * Currently the only operations supported on a kindless query are filter by
130 * __key__, ancestor, and order by __key__ ascending.
132 public Query() {
133 this(null, null);
137 * Create a new {@link Query} that finds {@link Entity} objects with
138 * the specified {@code kind}. Note that kindless queries are not yet
139 * supported in the Java dev appserver.
141 * @param kind the kind or null to create a kindless query
143 public Query(String kind) {
144 this(kind, null);
148 * Copy constructor that performs a deep copy of the provided Query.
150 * @param query The Query to copy.
152 Query(Query query) {
153 this(query.kind, query.ancestor, query.sortPredicates, query.filter, query.filterPredicates,
154 query.keysOnly, query.appIdNamespace, query.projectionMap.values(), query.distinct,
155 query.fullTextSearch);
158 Query(String kind, Key ancestor, Filter filter, boolean keysOnly, AppIdNamespace appIdNamespace,
159 boolean distinct, String fullTextSearch) {
160 this.kind = kind;
161 this.keysOnly = keysOnly;
162 this.appIdNamespace = appIdNamespace;
163 this.fullTextSearch = fullTextSearch;
164 this.distinct = distinct;
165 this.filter = filter;
166 if (ancestor != null) {
167 setAncestor(ancestor);
171 Query(String kind, Key ancestor, Collection<SortPredicate> sortPreds,
172 Filter filter,
173 Collection<FilterPredicate> filterPreds, boolean keysOnly, AppIdNamespace appIdNamespace,
174 Collection<Projection> projections, boolean distinct, String fullTextSearch) {
175 this(kind, ancestor, filter, keysOnly, appIdNamespace, distinct, fullTextSearch);
176 this.sortPredicates.addAll(sortPreds);
177 this.filterPredicates.addAll(filterPreds);
178 for (Projection projection : projections) {
179 addProjection(projection);
183 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
184 in.defaultReadObject();
185 if (appIdNamespace == null) {
186 if (ancestor != null) {
187 appIdNamespace = ancestor.getAppIdNamespace();
188 } else {
189 appIdNamespace = new AppIdNamespace(DatastoreApiHelper.getCurrentAppId(), "");
195 * Create a new {@link Query} that finds {@link Entity} objects with
196 * the specified {@code Key} as an ancestor.
198 * @param ancestor the ancestor key or null
199 * @throws IllegalArgumentException If ancestor is not complete.
201 public Query(Key ancestor) {
202 this(null, ancestor);
206 * Create a new {@link Query} that finds {@link Entity} objects with
207 * the specified {@code kind} and the specified {@code ancestor}. Note that
208 * kindless queries are not yet supported in the Java dev appserver.
210 * @param kind the kind or null to create a kindless query
211 * @param ancestor the ancestor key or null
212 * @throws IllegalArgumentException If the ancestor is not complete.
214 public Query(String kind, Key ancestor) {
215 this(kind, ancestor, null, false, DatastoreApiHelper.getCurrentAppIdNamespace(), false, null);
219 * Only {@link Entity} objects whose kind matches this value will be
220 * returned.
222 public String getKind() {
223 return kind;
227 * Returns the AppIdNamespace that is being queried.
228 * <p>The AppIdNamespace is set at construction time of this
229 * object using the {@link NamespaceManager} to retrieve the
230 * current namespace.
232 AppIdNamespace getAppIdNamespace() {
233 return appIdNamespace;
237 * Returns the appId for this {@link Query}.
239 public String getAppId() {
240 return appIdNamespace.getAppId();
244 * Returns the namespace for this {@link Query}.
246 public String getNamespace() {
247 return appIdNamespace.getNamespace();
251 * Gets the current ancestor for this query, or null if there is no
252 * ancestor specified.
254 public Key getAncestor() {
255 return ancestor;
259 * Sets an ancestor for this query.
261 * This restricts the query to only return result entities that are
262 * descended from a given entity. In other words, all of the results
263 * will have the ancestor as their parent, or parent's parent, or
264 * etc.
266 * If null is specified, unsets any previously-set ancestor. Passing
267 * {@code null} as a parameter does not query for entities without
268 * ancestors (this type of query is not currently supported).
270 * @return {@code this} (for chaining)
272 * @throws IllegalArgumentException If the ancestor key is incomplete, or if
273 * you try to unset an ancestor and have not set a kind, or if you try to
274 * unset an ancestor and have not previously set an ancestor.
276 public Query setAncestor(Key ancestor) {
277 if (ancestor != null && !ancestor.isComplete()) {
278 throw new IllegalArgumentException(ancestor + " is incomplete.");
279 } else if (ancestor == null) {
280 if (this.ancestor == null) {
281 throw new IllegalArgumentException(
282 "Cannot clear ancestor unless ancestor has already been set");
285 if (ancestor != null) {
286 if (!ancestor.getAppIdNamespace().equals(appIdNamespace)) {
287 throw new IllegalArgumentException(
288 "Namespace of ancestor key and query must match.");
291 this.ancestor = ancestor;
292 return this;
296 * @param distinct if this query should be distinct. This may only be used
297 * when the query has a projection.
298 * @return {@code this} (for chaining)
300 public Query setDistinct(boolean distinct) {
301 this.distinct = distinct;
302 return this;
306 * @return if this query is distinct
307 * @see #setDistinct(boolean)
309 public boolean getDistinct() {
310 return distinct;
314 * @param filter the filter to use for this query, or {@code null}
315 * @return {@code this} (for chaining)
316 * @see CompositeFilter
317 * @see FilterPredicate
319 public Query setFilter(Filter filter) {
320 this.filter = filter;
321 return this;
325 * @return the filter for this query or {@code null}
326 * @see #setFilter(Filter)
328 public Filter getFilter() {
329 return filter;
333 * Add a {@link FilterPredicate} on the specified property.
335 * <p>All {@link FilterPredicate}s added using this message are combined using
336 * {@link CompositeFilterOperator#AND}.
338 * <p>Cannot be used in conjunction with {@link #setFilter(Filter)} which sets
339 * a single {@link Filter} instead of many {@link FilterPredicate}s.
341 * @param propertyName The name of the property to which the filter applies.
342 * @param operator The filter operator.
343 * @param value An instance of a supported datastore type. Note that
344 * entities with multi-value properties identified by {@code propertyName}
345 * will match this filter if the multi-value property has at least one
346 * value that matches the condition expressed by {@code operator} and
347 * {@code value}. For more information on multi-value property filtering
348 * please see the
349 * <a href="http://code.google.com/appengine/docs/java/datastore">
350 * datastore documentation</a>.
352 * @return {@code this} (for chaining)
354 * @throws NullPointerException If {@code propertyName} or {@code operator}
355 * is null.
356 * @throws IllegalArgumentException If {@code value} is not of a
357 * type supported by the datastore. See
358 * {@link DataTypeUtils#isSupportedType(Class)}. Note that unlike
359 * {@link Entity#setProperty(String, Object)}, you cannot provide
360 * a {@link Collection} containing instances of supported types
361 * to this method.
362 * @deprecated Use {@link #setFilter(Filter)}
364 @Deprecated
365 public Query addFilter(String propertyName, FilterOperator operator, Object value) {
366 filterPredicates.add(operator.of(propertyName, value));
367 return this;
371 * Returns a mutable list of the current filter predicates.
373 * @see #addFilter(String, FilterOperator, Object)
374 * @deprecated Use {@link #setFilter(Filter)} and {@link #getFilter()} instead
376 @Deprecated
377 public List<FilterPredicate> getFilterPredicates() {
378 return filterPredicates;
382 * Specify how the query results should be sorted.
384 * The first call to addSort will register the property that will
385 * serve as the primary sort key. A second call to addSort will set
386 * a secondary sort key, etc.
388 * This method will always sort in ascending order. To control the
389 * order of the sort, use {@link #addSort(String,SortDirection)}.
391 * Note that entities with multi-value properties identified by
392 * {@code propertyName} will be sorted by the smallest value in the list.
393 * For more information on sorting properties with multiple values please see
394 * the <a href="http://code.google.com/appengine/docs/java/datastore">
395 * datastore documentation</a>.
397 * @return {@code this} (for chaining)
399 * @throws NullPointerException If any argument is null.
401 public Query addSort(String propertyName) {
402 return addSort(propertyName, SortDirection.ASCENDING);
406 * Specify how the query results should be sorted.
408 * The first call to addSort will register the property that will
409 * serve as the primary sort key. A second call to addSort will set
410 * a secondary sort key, etc.
412 * Note that if {@code direction} is {@link SortDirection#ASCENDING},
413 * entities with multi-value properties identified by
414 * {@code propertyName} will be sorted by the smallest value in the list. If
415 * {@code direction} is {@link SortDirection#DESCENDING}, entities with
416 * multi-value properties identified by {@code propertyName} will be sorted
417 * by the largest value in the list. For more information on sorting
418 * properties with multiple values please see
419 * the <a href="http://code.google.com/appengine/docs/java/datastore">
420 * datastore documentation</a>.
422 * @return {@code this} (for chaining)
424 * @throws NullPointerException If any argument is null.
426 public Query addSort(String propertyName, SortDirection direction) {
427 sortPredicates.add(new SortPredicate(propertyName, direction));
428 return this;
432 * Returns a mutable list of the current sort predicates.
434 public List<SortPredicate> getSortPredicates() {
435 return sortPredicates;
439 * Makes this query fetch and return only keys, not full entities.
441 * @return {@code this} (for chaining)
443 public Query setKeysOnly() {
444 keysOnly = true;
445 return this;
449 * Clears the keys only flag.
451 * @see #setKeysOnly()
452 * @return {@code this} (for chaining)
454 public Query clearKeysOnly() {
455 keysOnly = false;
456 return this;
460 * Adds a projection for this query.
462 * <p>Projections are limited in the following ways:
463 * <ul>
464 * <li>Un-indexed properties cannot be projected and attempting to do so will
465 * result in no entities being returned.
466 * <li>Projection {@link Projection#getName() names} must be unique.
467 * <li>Properties that have an equality filter on them cannot be projected. This includes
468 * the operators {@link FilterOperator#EQUAL} and {@link FilterOperator#IN}.
469 * </ul>
471 * @see #getProjections()
472 * @param projection the projection to add
473 * @return {@code this} (for chaining)
474 * @throws IllegalArgumentException if the query already contains a projection with the same name
476 public Query addProjection(Projection projection) {
477 Preconditions.checkArgument(!projectionMap.containsKey(projection.getName()),
478 "Query already contains projection with name: " + projection.getName());
479 projectionMap.put(projection.getName(), projection);
480 return this;
484 * Returns a mutable collection properties included in the projection for this query.
486 * <p>If empty, the full or keys only entities are returned. Otherwise
487 * partial entities are returned. A non-empty projection is not compatible
488 * with setting keys-only. In this case a {@link IllegalArgumentException}
489 * will be thrown when the query is {@link DatastoreService#prepare(Query) prepared}.
491 * <p>Projection queries are similar to SQL statements of the form:
492 * <pre>SELECT prop1, prop2, ...</pre>
493 * As they return partial entities, which only contain the properties
494 * specified in the projection. However, these entities will only contain
495 * a single value for any multi-valued property and, if a multi-valued
496 * property is specified in the order, an inequality property, or the
497 * projected properties, the entity will be returned multiple times.
498 * Once for each unique combination of values.
500 * <p>Specifying a projection:
501 * <ul>
502 * <li>May change the type of any property returned in a projection.
503 * <li>May change the index requirements for the given query.
504 * <li>Will cause a partial entity to be returned.
505 * <li>Will cause only entities that contain those properties to be returned.
506 * </ul>
507 * However, projection queries are significantly faster than normal queries.
509 * @return a mutable collection properties included in the projection for this query
510 * @see #addProjection(Projection)
512 public Collection<Projection> getProjections() {
513 return projectionMap.values();
517 * Returns true if this query will fetch and return keys only, false if it
518 * will fetch and return full entities.
520 public boolean isKeysOnly() {
521 return keysOnly;
525 * Creates a query sorted in the exact opposite direction as the current one.
527 * This function requires a sort order on {@link Entity#KEY_RESERVED_PROPERTY}
528 * to guarantee that each entity is uniquely identified by the set of properties
529 * used in the sort (which is required to exactly reverse the order of a query).
530 * Advanced users can reverse the sort orders manually if they know the set of
531 * sorted properties meets this requirement without a order on {@link
532 * Entity#KEY_RESERVED_PROPERTY}.
534 * The results of the reverse query will be the same as the results of the forward
535 * query but in reverse direction.
537 * {@link Cursor Cursors} from the original query can be converted for use with
538 * the reverse query through {@link Cursor#reverse()}.
540 * @return A new query with the sort order reversed.
541 * @throws IllegalStateException if the current query is not sorted by
542 * {@link Entity#KEY_RESERVED_PROPERTY}.
544 public Query reverse() {
545 List<SortPredicate> order = new ArrayList<SortPredicate>(sortPredicates.size());
546 boolean hasKeyOrder = false;
547 for (SortPredicate sort : sortPredicates) {
548 if (Entity.KEY_RESERVED_PROPERTY.equals(sort.getPropertyName())) {
549 hasKeyOrder = true;
551 if (!distinct || projectionMap.containsKey(sort.getPropertyName())) {
552 order.add(sort.reverse());
553 } else {
554 order.add(sort);
557 Preconditions.checkState(hasKeyOrder, "A sort on " + Entity.KEY_RESERVED_PROPERTY +
558 " is required to reverse a Query");
560 return new Query(kind, ancestor, order, filter, filterPredicates, keysOnly, appIdNamespace,
561 projectionMap.values(), distinct, fullTextSearch);
565 * Returns the query's full text search string.
567 String getFullTextSearch() {
568 return fullTextSearch;
572 * Sets the query's full text search string.
574 * @return {@code this} (for chaining)
576 Query setFullTextSearch(String fullTextSearch) {
577 this.fullTextSearch = fullTextSearch;
578 return this;
581 @Override
582 public boolean equals(Object o) {
583 if (this == o) {
584 return true;
586 if (o == null || getClass() != o.getClass()) {
587 return false;
590 Query query = (Query) o;
592 if (keysOnly != query.keysOnly) {
593 return false;
596 if (!Objects.equals(ancestor, query.ancestor)) {
597 return false;
599 if (!appIdNamespace.equals(query.appIdNamespace)) {
600 return false;
602 if (!Objects.equals(filter, query.filter)) {
603 return false;
605 if (!filterPredicates.equals(query.filterPredicates)) {
606 return false;
608 if (!Objects.equals(kind, query.kind)) {
609 return false;
611 if (!sortPredicates.equals(query.sortPredicates)) {
612 return false;
614 if (!projectionMap.equals(query.projectionMap)) {
615 return false;
617 if (distinct != query.distinct) {
618 return false;
620 if (!Objects.equals(fullTextSearch, query.fullTextSearch)) {
621 return false;
624 return true;
627 @Override
628 public int hashCode() {
629 return Objects.hash(kind, sortPredicates, filterPredicates, filter, ancestor, keysOnly,
630 appIdNamespace, projectionMap, distinct, fullTextSearch);
634 * Obtains a hash code of the {@code Query} ignoring any 'value' arguments associated with
635 * any query filters.
637 int hashCodeNoFilterValues() {
638 int filterHashCode = 1;
639 for (FilterPredicate filterPred : filterPredicates) {
640 filterHashCode = 31 * filterHashCode + filterPred.hashCodeNoFilterValues();
642 filterHashCode = 31 * filterHashCode + ((filter == null) ? 0 : filter.hashCodeNoFilterValues());
643 return Objects.hash(kind, sortPredicates, filterHashCode, ancestor, keysOnly,
644 appIdNamespace, projectionMap, distinct, fullTextSearch);
648 * Outputs a SQL like string representing the query.
650 @Override
651 public String toString() {
652 StringBuilder result = new StringBuilder("SELECT ");
653 if (distinct) {
654 result.append("DISTINCT ");
656 if (!projectionMap.isEmpty()) {
657 Joiner.on(", ").appendTo(result, projectionMap.values());
658 } else if (keysOnly) {
659 result.append("__key__");
660 } else {
661 result.append('*');
664 if (kind != null) {
665 result.append(" FROM ");
666 result.append(kind);
669 if (ancestor != null || !filterPredicates.isEmpty() || filter != null) {
670 result.append(" WHERE ");
671 final String AND_SEPARATOR = " AND ";
673 if (filter != null) {
674 result.append(filter);
675 } else if (!filterPredicates.isEmpty()) {
676 Joiner.on(AND_SEPARATOR).appendTo(result, filterPredicates);
679 if (ancestor != null) {
680 if (!filterPredicates.isEmpty() || filter != null) {
681 result.append(AND_SEPARATOR);
683 result.append("__ancestor__ is ");
684 result.append(ancestor);
688 if (!sortPredicates.isEmpty()) {
689 result.append(" ORDER BY ");
690 Joiner.on(", ").appendTo(result, sortPredicates);
692 return result.toString();
696 * SortPredicate is a data container that holds a single sort
697 * predicate.
699 public static final class SortPredicate implements Serializable {
700 @SuppressWarnings("hiding")
701 static final long serialVersionUID = -623786024456258081L;
702 private final String propertyName;
703 private final SortDirection direction;
705 public SortPredicate(String propertyName, SortDirection direction) {
706 if (propertyName == null) {
707 throw new NullPointerException("Property name was null");
710 if (direction == null) {
711 throw new NullPointerException("Direction was null");
714 this.propertyName = propertyName;
715 this.direction = direction;
719 * @return A sort predicate with the direction reversed.
721 public SortPredicate reverse() {
722 return new SortPredicate(propertyName,
723 direction == SortDirection.ASCENDING ?
724 SortDirection.DESCENDING : SortDirection.ASCENDING);
728 * Gets the name of the property to sort on.
730 public String getPropertyName() {
731 return propertyName;
735 * Gets the direction of the sort.
737 public SortDirection getDirection() {
738 return direction;
741 @Override
742 public boolean equals(Object o) {
743 if (this == o) {
744 return true;
746 if (o == null || getClass() != o.getClass()) {
747 return false;
750 SortPredicate that = (SortPredicate) o;
752 if (direction != that.direction) {
753 return false;
755 if (!propertyName.equals(that.propertyName)) {
756 return false;
759 return true;
762 @Override
763 public int hashCode() {
764 int result;
765 result = propertyName.hashCode();
766 result = 31 * result + direction.hashCode();
767 return result;
770 @Override
771 public String toString() {
772 return propertyName + (direction == SortDirection.DESCENDING ? " DESC" : "");
777 * The base class for a query filter.
779 * All sub classes should be immutable.
781 public abstract static class Filter implements Serializable {
782 @SuppressWarnings("hiding")
783 static final long serialVersionUID = -845113806195204425L;
785 Filter() {}
788 * Obtains the hash code for the {@code Filter} ignoring any value parameters associated
789 * with the filter.
791 abstract int hashCodeNoFilterValues();
795 * A {@link Filter} that combines several sub filters using a {@link CompositeFilterOperator}.
797 * For example, to construct a filter of the form <code>a = 1 AND (b = 2 OR c = 3)</code> use:
798 * <pre> {@code
799 * new CompositeFilter(CompositeFilterOperator.AND, Arrays.asList(
800 * new FilterPredicate("a", FilterOperator.EQUAL, 1),
801 * new CompositeFilter(CompositeFilterOperator.OR, Arrays.<Filter>asList(
802 * new FilterPredicate("b", FilterOperator.EQUAL, 2),
803 * new FilterPredicate("c", FilterOperator.EQUAL, 3)))));
804 * }</pre>
805 * or
806 * <pre> {@code
807 * CompositeFilterOperator.and(
808 * FilterOperator.EQUAL.of("a", 1),
809 * CompositeFilterOperator.or(
810 * FilterOperator.EQUAL.of("b", 2),
811 * FilterOperator.EQUAL.of("c", 3)));
812 * }</pre>
814 public static final class CompositeFilter extends Filter {
815 @SuppressWarnings("hiding")
816 static final long serialVersionUID = 7930286402872420509L;
818 private final CompositeFilterOperator operator;
819 private final ImmutableList<Filter> subFilters;
821 public CompositeFilter(CompositeFilterOperator operator, Collection<Filter> subFilters) {
822 Preconditions.checkArgument(subFilters.size() >= 2, "At least two sub filters are required.");
823 this.operator = checkNotNull(operator);
824 this.subFilters = ImmutableList.copyOf(subFilters);
828 * @return the operator
830 public CompositeFilterOperator getOperator() {
831 return operator;
835 * @return an immutable list of sub filters
837 public List<Filter> getSubFilters() {
838 return subFilters;
841 @Override
842 public String toString() {
843 StringBuilder builder = new StringBuilder();
844 builder.append('(');
845 Joiner.on(" " + operator + " ").appendTo(builder, subFilters);
846 builder.append(')');
847 return builder.toString();
850 @Override
851 public int hashCode() {
852 return Objects.hash(operator, subFilters);
855 @Override
856 int hashCodeNoFilterValues() {
857 int result = 1;
858 result = 31 * result + operator.hashCode();
859 for (Filter filter : subFilters) {
860 result = 31 * result + filter.hashCodeNoFilterValues();
862 return result;
865 @Override
866 public boolean equals(Object obj) {
867 if (this == obj) return true;
868 if (!(obj instanceof CompositeFilter)) return false;
869 CompositeFilter other = (CompositeFilter) obj;
870 if (operator != other.operator) return false;
871 return subFilters.equals(other.subFilters);
876 * A {@link Filter} on a single property.
878 public static final class FilterPredicate extends Filter {
879 @SuppressWarnings("hiding")
880 static final long serialVersionUID = 7681475799401864259L;
881 private final String propertyName;
882 private final FilterOperator operator;
883 private final Object value;
886 * Constructs a filter predicate from the given parameters.
888 * @param propertyName the name of the property on which to filter
889 * @param operator the operator to apply
890 * @param value A single instances of a supported type or if {@code
891 * operator} is {@link FilterOperator#IN} a non-empty {@link Iterable}
892 * object containing instances of supported types.
894 * @throws IllegalArgumentException If the provided filter values are not
895 * supported.
897 * @see DataTypeUtils#isSupportedType(Class)
900 public FilterPredicate(String propertyName, FilterOperator operator, Object value) {
901 if (propertyName == null) {
902 throw new NullPointerException("Property name was null");
903 } else if (operator == null) {
904 throw new NullPointerException("Operator was null");
905 } else if (operator == FilterOperator.IN) {
906 if (!(value instanceof Collection<?>) && value instanceof Iterable<?>) {
907 List<Object> newValue = new ArrayList<Object>();
908 Iterables.addAll(newValue, (Iterable<?>) value);
909 value = newValue;
911 DataTypeUtils.checkSupportedValue(propertyName, value, true, true);
912 } else {
913 DataTypeUtils.checkSupportedValue(propertyName, value, false, false);
915 this.propertyName = propertyName;
916 this.operator = operator;
917 this.value = value;
921 * Gets the name of the property to be filtered on.
923 public String getPropertyName() {
924 return propertyName;
928 * Gets the operator describing how to apply the filter.
930 public FilterOperator getOperator() {
931 return operator;
935 * Gets the argument to the filter operator.
937 public Object getValue() {
938 return value;
941 @Override
942 public boolean equals(Object o) {
943 if (this == o) {
944 return true;
946 if (o == null || getClass() != o.getClass()) {
947 return false;
950 FilterPredicate that = (FilterPredicate) o;
952 if (operator != that.operator) {
953 return false;
955 if (!propertyName.equals(that.propertyName)) {
956 return false;
958 if (!Objects.equals(value, that.value)) {
959 return false;
962 return true;
965 @Override
966 public int hashCode() {
967 return Objects.hash(propertyName, operator, value);
970 @Override
971 int hashCodeNoFilterValues() {
972 return Objects.hash(propertyName, operator);
975 @Override
976 public String toString() {
977 return propertyName + " " + operator + " " + (value != null ? value : "NULL");