View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils2;
19  
20  import java.lang.reflect.InvocationTargetException;
21  import java.util.Objects;
22  import java.util.function.Predicate;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  /**
28   * <p>
29   * {@code Predicate} that evaluates a property value against a specified value.
30   * </p>
31   * <p>
32   * An implementation of {@link java.util.function.Predicate} that evaluates a property value on the object provided against a specified value and returns
33   * {@code true} if equal; {@code false} otherwise. The {@code BeanPropertyValueEqualsPredicate} constructor takes two parameters which determine what property
34   * will be evaluated on the target object and what its expected value should be.
35   * </p>
36   * <dl>
37   * <dt><strong> {@code public BeanPropertyValueEqualsPredicate( String propertyName, Object propertyValue )} </strong></dt>
38   * <dd>Will create a {@code Predicate} that will evaluate the target object and return
39   * {@code true</code> if the property specified by <code>propertyName} has a value which
40   *       is equal to the value specified by {@code propertyValue}. Or return
41   *       {@code false} otherwise.
42   *    </dd>
43   * </dl>
44   * <p>
45   * <strong>Note:</strong> Property names can be a simple, nested, indexed, or mapped property as defined by
46   * {@code org.apache.commons.beanutils2.PropertyUtils}.  If any object in the property path
47   * specified by {@code propertyName</code> is <code>null} then the outcome is based on the
48   * value of the {@code ignoreNull} attribute.
49   * </p>
50   * <p>
51   * A typical usage might look like:
52   * </p>
53   * <pre>{@code
54   * // create the closure
55   * BeanPropertyValueEqualsPredicate predicate =
56   *    new BeanPropertyValueEqualsPredicate( "activeEmployee", Boolean.FALSE );
57   *
58   * // filter the Collection
59   * CollectionUtils.filter( peopleCollection, predicate );
60   * }</pre>
61   * <p>
62   * This would take a {@code Collection} of person objects and filter out any people whose
63   * {@code activeEmployee</code> property is <code>false}. Assuming...
64   * </p>
65   * <ul>
66   *    <li>
67   *       The top level object in the {@code peopleCollection} is an object which represents a
68   *       person.
69   *    </li>
70   *    <li>
71   *       The person object has a {@code getActiveEmployee()} method which returns
72   *       the boolean value for the object's {@code activeEmployee} property.
73   *    </li>
74   * </ul>
75   * <p>
76   * Another typical usage might look like:
77   * </p>
78   * <pre>{@code
79   * // create the closure
80   * BeanPropertyValueEqualsPredicate predicate =
81   *    new BeanPropertyValueEqualsPredicate( "personId", "456-12-1234" );
82   *
83   * // search the Collection
84   * CollectionUtils.find( peopleCollection, predicate );
85   * }</pre>
86   * <p>
87   * This would search a {@code Collection} of person objects and return the first object whose
88   * {@code personId</code> property value equals <code>456-12-1234}. Assuming...
89   * </p>
90   * <ul>
91   * <li>The top level object in the {@code peopleCollection} is an object which represents a person.</li>
92   * <li>The person object has a {@code getPersonId()} method which returns the value for the object's {@code personId} property.</li>
93   * </ul>
94   *
95   * @param <T> The type of the input to the predicate.
96   * @param <V> The property value type.
97   * @see org.apache.commons.beanutils2.PropertyUtils
98   * @see java.util.function.Predicate
99   */
100 public class BeanPropertyValueEqualsPredicate<T, V> implements Predicate<T> {
101 
102     /** For logging. Each subclass gets its own log instance. */
103     private final Log log = LogFactory.getLog(this.getClass());
104 
105     /**
106      * The name of the property which will be evaluated when this {@code Predicate} is executed.
107      */
108     private final String propertyName;
109 
110     /**
111      * The value that the property specified by {@code propertyName} will be compared to when this {@code Predicate} executes.
112      */
113     private final V propertyValue;
114 
115     /**
116      * <p>
117      * Should {@code null} objects in the property path be ignored?
118      * </p>
119      * <p>
120      * Determines whether {@code null} objects in the property path will generate an
121      * {@code IllegalArgumentException</code> or not. If set to <code>true} then if any objects
122      * in the property path evaluate to {@code null} then the
123      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
124      * not re-thrown and {@code false</code> will be returned.  If set to <code>false} then if
125      * any objects in the property path evaluate to {@code null} then the
126      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and re-thrown.
127      * </p>
128      */
129     private final boolean ignoreNull;
130 
131     /**
132      * Constructor which takes the name of the property, its expected value to be used in evaluation, and assumes {@code ignoreNull</code> to be <code>false}.
133      *
134      * @param propertyName  The name of the property that will be evaluated against the expected value.
135      * @param propertyValue The value to use in object evaluation.
136      * @throws IllegalArgumentException If the property name provided is null or empty.
137      */
138     public BeanPropertyValueEqualsPredicate(final String propertyName, final V propertyValue) {
139         this(propertyName, propertyValue, false);
140     }
141 
142     /**
143      * Constructor which takes the name of the property, its expected value to be used in evaluation, and a boolean which determines whether {@code null}
144      * objects in the property path will generate an {@code IllegalArgumentException} or not.
145      *
146      * @param propertyName  The name of the property that will be evaluated against the expected value.
147      * @param propertyValue The value to use in object evaluation.
148      * @param ignoreNull    Determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
149      * @throws IllegalArgumentException If the property name provided is null or empty.
150      */
151     public BeanPropertyValueEqualsPredicate(final String propertyName, final V propertyValue, final boolean ignoreNull) {
152         if (propertyName == null || propertyName.isEmpty()) {
153             throw new IllegalArgumentException("propertyName cannot be null or empty");
154         }
155         this.propertyName = propertyName;
156         this.propertyValue = propertyValue;
157         this.ignoreNull = ignoreNull;
158     }
159 
160     /**
161      * Utility method which evaluates whether the actual property value equals the expected property value.
162      *
163      * @param expected The expected value.
164      * @param actual   The actual value.
165      * @return True if they are equal; false otherwise.
166      */
167     protected boolean evaluateValue(final V expected, final Object actual) {
168         return Objects.equals(expected, actual);
169     }
170 
171     /**
172      * Returns the name of the property which will be evaluated when this {@code Predicate} is executed.
173      *
174      * @return The name of the property which will be evaluated when this {@code Predicate} is executed.
175      */
176     public String getPropertyName() {
177         return propertyName;
178     }
179 
180     /**
181      * Returns the value that the property specified by {@code propertyName} will be compared to when this {@code Predicate} executes.
182      *
183      * @return The value that the property specified by {@code propertyName} will be compared to when this {@code Predicate} executes.
184      */
185     public V getPropertyValue() {
186         return propertyValue;
187     }
188 
189     /**
190      * Returns the flag which determines whether {@code null} objects in the property path will generate an
191      * {@code IllegalArgumentException</code> or not. If set to <code>true} then
192      * if any objects in the property path evaluate to {@code null} then the
193      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
194      * not re-thrown and {@code false</code> will be returned.  If set to <code>false} then if
195      * any objects in the property path evaluate to {@code null} then the
196      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and re-thrown.
197      *
198      * @return The flag which determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
199      */
200     public boolean isIgnoreNull() {
201         return ignoreNull;
202     }
203 
204     /**
205      * Evaluates the object provided against the criteria specified when this {@code BeanPropertyValueEqualsPredicate} was constructed. Equality is based on
206      * either reference or logical equality as defined by the property object's equals method. If any object in the property path leading up to the target
207      * property is {@code null} then the outcome will be based on the value of the {@code ignoreNull} attribute. By default,
208      * {@code ignoreNull</code> is <code>false} and would result in an
209      * {@code IllegalArgumentException} if an object in the property path leading up to the
210      * target property is {@code null}.
211      *
212      * &#64;param object The object to be evaluated.
213      * &#64;return True if the object provided meets all the criteria for this {@code Predicate};
214      * false otherwise.
215      * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
216      * NoSuchMethodException is thrown when trying to access the property specified on the object
217      * provided. Or if an object in the property path provided is {@code null} and
218      * {@code ignoreNull</code> is set to <code>false}.
219      */
220     @Override
221     public boolean test(final T object) {
222 
223         boolean evaluation = false;
224 
225         try {
226             evaluation = evaluateValue(propertyValue, PropertyUtils.getProperty(object, propertyName));
227         } catch (final IllegalArgumentException e) {
228             final String errorMsg = "Problem during evaluation. Null value encountered in property path...";
229 
230             if (!ignoreNull) {
231                 throw new IllegalArgumentException(errorMsg, e);
232             }
233             log.warn(errorMsg, e);
234         } catch (final IllegalAccessException e) {
235             final String errorMsg = "Unable to access the property provided.";
236             throw new IllegalArgumentException(errorMsg, e);
237         } catch (final InvocationTargetException e) {
238             final String errorMsg = "Exception occurred in property's getter";
239             throw new IllegalArgumentException(errorMsg, e);
240         } catch (final NoSuchMethodException e) {
241             final String errorMsg = "Property not found.";
242             throw new IllegalArgumentException(errorMsg, e);
243         }
244 
245         return evaluation;
246     }
247 }