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
.Objects
;
10 import com
.google
.common
.base
.Preconditions
;
11 import com
.google
.common
.collect
.ImmutableList
;
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
;
25 * {@link Query} encapsulates a request for zero or more {@link Entity} objects
26 * out of the datastore. It supports querying on zero or more properties,
27 * querying by ancestor, and sorting. {@link Entity} objects which match the
28 * query can be retrieved in a single list, or with an unbounded iterator.
31 public final class Query
implements Serializable
{
34 public static final String KIND_METADATA_KIND
= Entities
.KIND_METADATA_KIND
;
37 public static final String PROPERTY_METADATA_KIND
= Entities
.PROPERTY_METADATA_KIND
;
40 public static final String NAMESPACE_METADATA_KIND
= Entities
.NAMESPACE_METADATA_KIND
;
42 static final long serialVersionUID
= 7090652715949085374L;
45 * SortDirection controls the order of a sort.
47 public enum SortDirection
{
53 * Operators supported by {@link FilterPredicate}.
55 public enum FilterOperator
{
57 LESS_THAN_OR_EQUAL("<="),
59 GREATER_THAN_OR_EQUAL(">="),
64 private final String shortName
;
65 private FilterOperator(String shortName
) {
66 this.shortName
= shortName
;
70 public String
toString() {
74 public FilterPredicate
of(String propertyName
, Object value
) {
75 return new FilterPredicate(propertyName
, this, value
);
80 * Operators supported by {@link CompositeFilter}.
82 public enum CompositeFilterOperator
{
86 public static CompositeFilter
and(Filter
... subFilters
) {
87 return and(Arrays
.asList(subFilters
));
90 public static CompositeFilter
and(Collection
<Filter
> subFilters
) {
91 return new CompositeFilter(AND
, subFilters
);
94 public static CompositeFilter
or(Filter
... subFilters
) {
95 return or(Arrays
.asList(subFilters
));
98 public static CompositeFilter
or(Collection
<Filter
> subFilters
) {
99 return new CompositeFilter(OR
, subFilters
);
102 public CompositeFilter
of(Filter
... subFilters
) {
103 return new CompositeFilter(this, Arrays
.asList(subFilters
));
106 public CompositeFilter
of(Collection
<Filter
> subFilters
) {
107 return new CompositeFilter(this, subFilters
);
111 private final String kind
;
112 private final List
<SortPredicate
> sortPredicates
= Lists
.newArrayList();
113 private final List
<FilterPredicate
> filterPredicates
= Lists
.newArrayList();
114 private Filter filter
;
115 private Key ancestor
;
116 private boolean keysOnly
;
117 private final Map
<String
, Projection
> projectionMap
= Maps
.newHashMap();
118 private boolean distinct
;
119 private AppIdNamespace appIdNamespace
;
121 private transient String fullTextSearch
;
124 * Create a new kindless {@link Query} that finds {@link Entity} objects.
125 * Note that kindless queries are not yet supported in the Java dev
128 * Currently the only operations supported on a kindless query are filter by
129 * __key__, ancestor, and order by __key__ ascending.
136 * Create a new {@link Query} that finds {@link Entity} objects with
137 * the specified {@code kind}. Note that kindless queries are not yet
138 * supported in the Java dev appserver.
140 * @param kind the kind or null to create a kindless query
142 public Query(String kind
) {
147 * Copy constructor that performs a deep copy of the provided Query.
149 * @param query The Query to copy.
152 this(query
.kind
, query
.ancestor
, query
.sortPredicates
, query
.filter
, query
.filterPredicates
,
153 query
.keysOnly
, query
.appIdNamespace
, query
.projectionMap
.values(), query
.distinct
,
154 query
.fullTextSearch
);
157 Query(String kind
, Key ancestor
, Filter filter
, boolean keysOnly
, AppIdNamespace appIdNamespace
,
158 boolean distinct
, String fullTextSearch
) {
160 this.keysOnly
= keysOnly
;
161 this.appIdNamespace
= appIdNamespace
;
162 this.fullTextSearch
= fullTextSearch
;
163 this.distinct
= distinct
;
164 this.filter
= filter
;
165 if (ancestor
!= null) {
166 setAncestor(ancestor
);
170 Query(String kind
, Key ancestor
, Collection
<SortPredicate
> sortPreds
,
172 Collection
<FilterPredicate
> filterPreds
, boolean keysOnly
, AppIdNamespace appIdNamespace
,
173 Collection
<Projection
> projections
, boolean distinct
, String fullTextSearch
) {
174 this(kind
, ancestor
, filter
, keysOnly
, appIdNamespace
, distinct
, fullTextSearch
);
175 this.sortPredicates
.addAll(sortPreds
);
176 this.filterPredicates
.addAll(filterPreds
);
177 for (Projection projection
: projections
) {
178 addProjection(projection
);
182 private void readObject(ObjectInputStream in
) throws IOException
, ClassNotFoundException
{
183 in
.defaultReadObject();
184 if (appIdNamespace
== null) {
185 if (ancestor
!= null) {
186 appIdNamespace
= ancestor
.getAppIdNamespace();
188 appIdNamespace
= new AppIdNamespace(DatastoreApiHelper
.getCurrentAppId(), "");
194 * Create a new {@link Query} that finds {@link Entity} objects with
195 * the specified {@code Key} as an ancestor.
197 * @param ancestor the ancestor key or null
198 * @throws IllegalArgumentException If ancestor is not complete.
200 public Query(Key ancestor
) {
201 this(null, ancestor
);
205 * Create a new {@link Query} that finds {@link Entity} objects with
206 * the specified {@code kind} and the specified {@code ancestor}. Note that
207 * kindless queries are not yet supported in the Java dev appserver.
209 * @param kind the kind or null to create a kindless query
210 * @param ancestor the ancestor key or null
211 * @throws IllegalArgumentException If the ancestor is not complete.
213 public Query(String kind
, Key ancestor
) {
214 this(kind
, ancestor
, null, false, DatastoreApiHelper
.getCurrentAppIdNamespace(), false, null);
218 * Only {@link Entity} objects whose kind matches this value will be
221 public String
getKind() {
226 * Returns the AppIdNamespace that is being queried.
227 * <p>The AppIdNamespace is set at construction time of this
228 * object using the {@link NamespaceManager} to retrieve the
231 AppIdNamespace
getAppIdNamespace() {
232 return appIdNamespace
;
236 * Gets the current ancestor for this query, or null if there is no
237 * ancestor specified.
239 public Key
getAncestor() {
244 * Sets an ancestor for this query.
246 * This restricts the query to only return result entities that are
247 * descended from a given entity. In other words, all of the results
248 * will have the ancestor as their parent, or parent's parent, or
251 * If null is specified, unsets any previously-set ancestor. Passing
252 * {@code null} as a parameter does not query for entities without
253 * ancestors (this type of query is not currently supported).
255 * @return {@code this} (for chaining)
257 * @throws IllegalArgumentException If the ancestor key is incomplete, or if
258 * you try to unset an ancestor and have not set a kind, or if you try to
259 * unset an ancestor and have not previously set an ancestor.
261 public Query
setAncestor(Key ancestor
) {
262 if (ancestor
!= null && !ancestor
.isComplete()) {
263 throw new IllegalArgumentException(ancestor
+ " is incomplete.");
264 } else if (ancestor
== null) {
265 if (this.ancestor
== null) {
266 throw new IllegalArgumentException(
267 "Cannot clear ancestor unless ancestor has already been set");
270 if (ancestor
!= null) {
271 if (!ancestor
.getAppIdNamespace().equals(appIdNamespace
)) {
272 throw new IllegalArgumentException(
273 "Namespace of ancestor key and query must match.");
276 this.ancestor
= ancestor
;
281 * @param distinct if this query should be distinct. This may only be used
282 * when the query has a projection.
283 * @return {@code this} (for chaining)
285 public Query
setDistinct(boolean distinct
) {
286 this.distinct
= distinct
;
291 * @return if this query is distinct
292 * @see #setDistinct(boolean)
294 public boolean getDistinct() {
299 * @param filter the filter to use for this query, or {@code null}
300 * @return {@code this} (for chaining)
301 * @see CompositeFilter
302 * @see FilterPredicate
304 public Query
setFilter(Filter filter
) {
305 this.filter
= filter
;
310 * @return the filter for this query or {@code null}
311 * @see #setFilter(Filter)
313 public Filter
getFilter() {
318 * Add a {@link FilterPredicate} on the specified property.
320 * <p>All {@link FilterPredicate}s added using this message are combined using
321 * {@link CompositeFilterOperator#AND}.
323 * <p>Cannot be used in conjunction with {@link #setFilter(Filter)} which sets
324 * a single {@link Filter} instead of many {@link FilterPredicate}s.
326 * @param propertyName The name of the property to which the filter applies.
327 * @param operator The filter operator.
328 * @param value An instance of a supported datastore type. Note that
329 * entities with multi-value properties identified by {@code propertyName}
330 * will match this filter if the multi-value property has at least one
331 * value that matches the condition expressed by {@code operator} and
332 * {@code value}. For more information on multi-value property filtering
334 * <a href="http://code.google.com/appengine/docs/java/datastore">
335 * datastore documentation</a>.
337 * @return {@code this} (for chaining)
339 * @throws NullPointerException If {@code propertyName} or {@code operator}
341 * @throws IllegalArgumentException If {@code value} is not of a
342 * type supported by the datastore. See
343 * {@link DataTypeUtils#isSupportedType(Class)}. Note that unlike
344 * {@link Entity#setProperty(String, Object)}, you cannot provide
345 * a {@link Collection} containing instances of supported types
347 * @deprecated Use {@link #setFilter(Filter)}
350 public Query
addFilter(String propertyName
, FilterOperator operator
, Object value
) {
351 filterPredicates
.add(operator
.of(propertyName
, value
));
356 * Returns a mutable list of the current filter predicates.
358 * @see #addFilter(String, FilterOperator, Object)
359 * @deprecated Use {@link #setFilter(Filter)} and {@link #getFilter()} instead
362 public List
<FilterPredicate
> getFilterPredicates() {
363 return filterPredicates
;
367 * Specify how the query results should be sorted.
369 * The first call to addSort will register the property that will
370 * serve as the primary sort key. A second call to addSort will set
371 * a secondary sort key, etc.
373 * This method will always sort in ascending order. To control the
374 * order of the sort, use {@link #addSort(String,SortDirection)}.
376 * Note that entities with multi-value properties identified by
377 * {@code propertyName} will be sorted by the smallest value in the list.
378 * For more information on sorting properties with multiple values please see
379 * the <a href="http://code.google.com/appengine/docs/java/datastore">
380 * datastore documentation</a>.
382 * @return {@code this} (for chaining)
384 * @throws NullPointerException If any argument is null.
386 public Query
addSort(String propertyName
) {
387 return addSort(propertyName
, SortDirection
.ASCENDING
);
391 * Specify how the query results should be sorted.
393 * The first call to addSort will register the property that will
394 * serve as the primary sort key. A second call to addSort will set
395 * a secondary sort key, etc.
397 * Note that if {@code direction} is {@link SortDirection#ASCENDING},
398 * entities with multi-value properties identified by
399 * {@code propertyName} will be sorted by the smallest value in the list. If
400 * {@code direction} is {@link SortDirection#DESCENDING}, entities with
401 * multi-value properties identified by {@code propertyName} will be sorted
402 * by the largest value in the list. For more information on sorting
403 * properties with multiple values please see
404 * the <a href="http://code.google.com/appengine/docs/java/datastore">
405 * datastore documentation</a>.
407 * @return {@code this} (for chaining)
409 * @throws NullPointerException If any argument is null.
411 public Query
addSort(String propertyName
, SortDirection direction
) {
412 sortPredicates
.add(new SortPredicate(propertyName
, direction
));
417 * Returns a mutable list of the current sort predicates.
419 public List
<SortPredicate
> getSortPredicates() {
420 return sortPredicates
;
424 * Makes this query fetch and return only keys, not full entities.
426 * @return {@code this} (for chaining)
428 public Query
setKeysOnly() {
434 * Clears the keys only flag.
436 * @see #setKeysOnly()
437 * @return {@code this} (for chaining)
439 public Query
clearKeysOnly() {
445 * Adds a projection for this query.
447 * <p>Projections are limited in the following ways:
449 * <li>Un-indexed properties cannot be projected and attempting to do so will
450 * result in no entities being returned.
451 * <li>Projection {@link Projection#getName() names} must be unique.
452 * <li>Properties that have an equality filter on them cannot be projected. This includes
453 * the operators {@link FilterOperator#EQUAL} and {@link FilterOperator#IN}.
456 * @see #getProjections()
457 * @param projection the projection to add
458 * @return {@code this} (for chaining)
459 * @throws IllegalArgumentException if the query already contains a projection with the same name
461 public Query
addProjection(Projection projection
) {
462 Preconditions
.checkArgument(!projectionMap
.containsKey(projection
.getName()),
463 "Query already contains projection with name: " + projection
.getName());
464 projectionMap
.put(projection
.getName(), projection
);
469 * Returns a mutable collection properties included in the projection for this query.
471 * <p>If empty, the full or keys only entities are returned. Otherwise
472 * partial entities are returned. A non-empty projection is not compatible
473 * with setting keys-only. In this case a {@link IllegalArgumentException}
474 * will be thrown when the query is {@link DatastoreService#prepare(Query) prepared}.
476 * <p>Projection queries are similar to SQL statements of the form:
477 * <pre>SELECT prop1, prop2, ...</pre>
478 * As they return partial entities, which only contain the properties
479 * specified in the projection. However, these entities will only contain
480 * a single value for any multi-valued property and, if a multi-valued
481 * property is specified in the order, an inequality property, or the
482 * projected properties, the entity will be returned multiple times.
483 * Once for each unique combination of values.
485 * <p>Specifying a projection:
487 * <li>May change the type of any property returned in a projection.
488 * <li>May change the index requirements for the given query.
489 * <li>Will cause a partial entity to be returned.
490 * <li>Will cause only entities that contain those properties to be returned.
492 * However, projection queries are significantly faster than normal queries.
494 * @return a mutable collection properties included in the projection for this query
495 * @see #addProjection(Projection)
497 public Collection
<Projection
> getProjections() {
498 return projectionMap
.values();
502 * Returns true if this query will fetch and return keys only, false if it
503 * will fetch and return full entities.
505 public boolean isKeysOnly() {
510 * Creates a query sorted in the exact opposite direction as the current one.
512 * This function requires a sort order on {@link Entity#KEY_RESERVED_PROPERTY}
513 * to guarantee that each entity is uniquely identified by the set of properties
514 * used in the sort (which is required to exactly reverse the order of a query).
515 * Advanced users can reverse the sort orders manually if they know the set of
516 * sorted properties meets this requirement without a order on {@link
517 * Entity#KEY_RESERVED_PROPERTY}.
519 * The results of the reverse query will be the same as the results of the forward
520 * query but in reverse direction.
522 * {@link Cursor Cursors} from the original query can be converted for use with
523 * the reverse query through {@link Cursor#reverse()}.
525 * @return A new query with the sort order reversed.
526 * @throws IllegalStateException if the current query is not sorted by
527 * {@link Entity#KEY_RESERVED_PROPERTY}.
529 public Query
reverse() {
530 List
<SortPredicate
> order
= new ArrayList
<SortPredicate
>(sortPredicates
.size());
531 boolean hasKeyOrder
= false;
532 for (SortPredicate sort
: sortPredicates
) {
533 if (Entity
.KEY_RESERVED_PROPERTY
.equals(sort
.getPropertyName())) {
536 if (!distinct
|| projectionMap
.containsKey(sort
.getPropertyName())) {
537 order
.add(sort
.reverse());
542 Preconditions
.checkState(hasKeyOrder
, "A sort on " + Entity
.KEY_RESERVED_PROPERTY
+
543 " is required to reverse a Query");
545 return new Query(kind
, ancestor
, order
, filter
, filterPredicates
, keysOnly
, appIdNamespace
,
546 projectionMap
.values(), distinct
, fullTextSearch
);
550 * Returns the query's full text search string.
552 String
getFullTextSearch() {
553 return fullTextSearch
;
557 * Sets the query's full text search string.
559 * @return {@code this} (for chaining)
561 Query
setFullTextSearch(String fullTextSearch
) {
562 this.fullTextSearch
= fullTextSearch
;
567 public boolean equals(Object o
) {
571 if (o
== null || getClass() != o
.getClass()) {
575 Query query
= (Query
) o
;
577 if (keysOnly
!= query
.keysOnly
) {
581 if (!Objects
.equal(ancestor
, query
.ancestor
)) {
584 if (!appIdNamespace
.equals(query
.appIdNamespace
)) {
587 if (!Objects
.equal(filter
, query
.filter
)) {
590 if (!filterPredicates
.equals(query
.filterPredicates
)) {
593 if (!kind
.equals(query
.kind
)) {
596 if (!sortPredicates
.equals(query
.sortPredicates
)) {
599 if (!projectionMap
.equals(query
.projectionMap
)) {
602 if (distinct
!= query
.distinct
) {
605 if (!Objects
.equal(fullTextSearch
, query
.fullTextSearch
)) {
613 public int hashCode() {
614 return Objects
.hashCode(kind
, sortPredicates
, filterPredicates
, filter
, ancestor
, keysOnly
,
615 appIdNamespace
, projectionMap
, distinct
, fullTextSearch
);
619 * Obtains a hash code of the {@code Query} ignoring any 'value' arguments associated with
622 int hashCodeNoFilterValues() {
623 int filterHashCode
= 1;
624 for (FilterPredicate filterPred
: filterPredicates
) {
625 filterHashCode
= 31 * filterHashCode
+ filterPred
.hashCodeNoFilterValues();
627 filterHashCode
= 31 * filterHashCode
+ ((filter
== null) ?
0 : filter
.hashCodeNoFilterValues());
628 return Objects
.hashCode(kind
, sortPredicates
, filterHashCode
, ancestor
, keysOnly
,
629 appIdNamespace
, projectionMap
, distinct
, fullTextSearch
);
633 * Outputs a SQL like string representing the query.
636 public String
toString() {
637 StringBuilder result
= new StringBuilder("SELECT ");
639 result
.append("DISTINCT ");
641 if (!projectionMap
.isEmpty()) {
642 Joiner
.on(", ").appendTo(result
, projectionMap
.values());
643 } else if (keysOnly
) {
644 result
.append("__key__");
650 result
.append(" FROM ");
654 if (ancestor
!= null || !filterPredicates
.isEmpty() || filter
!= null) {
655 result
.append(" WHERE ");
656 final String AND_SEPARATOR
= " AND ";
658 if (filter
!= null) {
659 result
.append(filter
);
660 } else if (!filterPredicates
.isEmpty()) {
661 Joiner
.on(AND_SEPARATOR
).appendTo(result
, filterPredicates
);
664 if (ancestor
!= null) {
665 if (!filterPredicates
.isEmpty() || filter
!= null) {
666 result
.append(AND_SEPARATOR
);
668 result
.append("__ancestor__ is ");
669 result
.append(ancestor
);
673 if (!sortPredicates
.isEmpty()) {
674 result
.append(" ORDER BY ");
675 Joiner
.on(", ").appendTo(result
, sortPredicates
);
677 return result
.toString();
681 * SortPredicate is a data container that holds a single sort
684 public static final class SortPredicate
implements Serializable
{
685 @SuppressWarnings("hiding")
686 static final long serialVersionUID
= -623786024456258081L;
687 private final String propertyName
;
688 private final SortDirection direction
;
690 public SortPredicate(String propertyName
, SortDirection direction
) {
691 if (propertyName
== null) {
692 throw new NullPointerException("Property name was null");
695 if (direction
== null) {
696 throw new NullPointerException("Direction was null");
699 this.propertyName
= propertyName
;
700 this.direction
= direction
;
704 * @return A sort predicate with the direction reversed.
706 public SortPredicate
reverse() {
707 return new SortPredicate(propertyName
,
708 direction
== SortDirection
.ASCENDING ?
709 SortDirection
.DESCENDING
: SortDirection
.ASCENDING
);
713 * Gets the name of the property to sort on.
715 public String
getPropertyName() {
720 * Gets the direction of the sort.
722 public SortDirection
getDirection() {
727 public boolean equals(Object o
) {
731 if (o
== null || getClass() != o
.getClass()) {
735 SortPredicate that
= (SortPredicate
) o
;
737 if (direction
!= that
.direction
) {
740 if (!propertyName
.equals(that
.propertyName
)) {
748 public int hashCode() {
750 result
= propertyName
.hashCode();
751 result
= 31 * result
+ direction
.hashCode();
756 public String
toString() {
757 return propertyName
+ (direction
== SortDirection
.DESCENDING ?
" DESC" : "");
762 * The base class for a query filter.
764 * All sub classes should be immutable.
766 public abstract static class Filter
implements Serializable
{
767 @SuppressWarnings("hiding")
768 static final long serialVersionUID
= -845113806195204425L;
773 * Obtains the hash code for the {@code Filter} ignoring any value parameters associated
776 abstract int hashCodeNoFilterValues();
780 * A {@link Filter} that combines several sub filters using a {@link CompositeFilterOperator}.
782 * For example, to construct a filter of the form <code>a = 1 AND (b = 2 OR c = 3)</code> use:
784 * new CompositeFilter(CompositeFilterOperator.AND, Arrays.asList(
785 * new FilterPredicate("a", FilterOperator.EQUAL, 1),
786 * new CompositeFilter(CompositeFilterOperator.OR, Arrays.<Filter>asList(
787 * new FilterPredicate("b", FilterOperator.EQUAL, 2),
788 * new FilterPredicate("c", FilterOperator.EQUAL, 3)))));
792 * CompositeFilterOperator.and(
793 * FilterOperator.EQUAL.of("a", 1),
794 * CompositeFilterOperator.or(
795 * FilterOperator.EQUAL.of("b", 2),
796 * FilterOperator.EQUAL.of("c", 3)));
799 public static final class CompositeFilter
extends Filter
{
800 @SuppressWarnings("hiding")
801 static final long serialVersionUID
= 7930286402872420509L;
803 private final CompositeFilterOperator operator
;
804 private final ImmutableList
<Filter
> subFilters
;
806 public CompositeFilter(CompositeFilterOperator operator
, Collection
<Filter
> subFilters
) {
807 Preconditions
.checkArgument(subFilters
.size() >= 2, "At least two sub filters are required.");
808 this.operator
= checkNotNull(operator
);
809 this.subFilters
= ImmutableList
.copyOf(subFilters
);
813 * @return the operator
815 public CompositeFilterOperator
getOperator() {
820 * @return an immutable list of sub filters
822 public List
<Filter
> getSubFilters() {
827 public String
toString() {
828 StringBuilder builder
= new StringBuilder();
830 Joiner
.on(" " + operator
+ " ").appendTo(builder
, subFilters
);
832 return builder
.toString();
836 public int hashCode() {
837 return Objects
.hashCode(operator
, subFilters
);
841 int hashCodeNoFilterValues() {
843 result
= 31 * result
+ operator
.hashCode();
844 for (Filter filter
: subFilters
) {
845 result
= 31 * result
+ filter
.hashCodeNoFilterValues();
851 public boolean equals(Object obj
) {
852 if (this == obj
) return true;
853 if (!(obj
instanceof CompositeFilter
)) return false;
854 CompositeFilter other
= (CompositeFilter
) obj
;
855 if (operator
!= other
.operator
) return false;
856 return subFilters
.equals(other
.subFilters
);
861 * A {@link Filter} on a single property.
863 public static final class FilterPredicate
extends Filter
{
864 @SuppressWarnings("hiding")
865 static final long serialVersionUID
= 7681475799401864259L;
866 private final String propertyName
;
867 private final FilterOperator operator
;
868 private final Object value
;
871 * Constructs a filter predicate from the given parameters.
873 * @param propertyName the name of the property on which to filter
874 * @param operator the operator to apply
875 * @param value A single instances of a supported type or if {@code
876 * operator} is {@link FilterOperator#IN} a non-empty {@link Iterable}
877 * object containing instances of supported types.
879 * @throws IllegalArgumentException If the provided filter values are not
882 * @see DataTypeUtils#isSupportedType(Class)
885 public FilterPredicate(String propertyName
, FilterOperator operator
, Object value
) {
886 if (propertyName
== null) {
887 throw new NullPointerException("Property name was null");
888 } else if (operator
== null) {
889 throw new NullPointerException("Operator was null");
890 } else if (operator
== FilterOperator
.IN
) {
891 if (!(value
instanceof Collection
<?
>) && value
instanceof Iterable
<?
>) {
892 List
<Object
> newValue
= new ArrayList
<Object
>();
893 for (Object val
: (Iterable
<?
>) value
) {
898 DataTypeUtils
.checkSupportedValue(propertyName
, value
, true, true);
900 DataTypeUtils
.checkSupportedValue(propertyName
, value
, false, false);
902 this.propertyName
= propertyName
;
903 this.operator
= operator
;
908 * Gets the name of the property to be filtered on.
910 public String
getPropertyName() {
915 * Gets the operator describing how to apply the filter.
917 public FilterOperator
getOperator() {
922 * Gets the argument to the filter operator.
924 public Object
getValue() {
929 public boolean equals(Object o
) {
933 if (o
== null || getClass() != o
.getClass()) {
937 FilterPredicate that
= (FilterPredicate
) o
;
939 if (operator
!= that
.operator
) {
942 if (!propertyName
.equals(that
.propertyName
)) {
945 if (!Objects
.equal(value
, that
.value
)) {
953 public int hashCode() {
954 return Objects
.hashCode(propertyName
, operator
, value
);
958 int hashCodeNoFilterValues() {
959 return Objects
.hashCode(propertyName
, operator
);
963 public String
toString() {
964 return propertyName
+ " " + operator
.toString() + " " +
965 (value
!= null ? value
.toString() : "NULL");