001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.beanutils2; 019 020import java.lang.reflect.InvocationTargetException; 021import java.util.Objects; 022import java.util.function.Predicate; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027/** 028 * <p> 029 * {@code Predicate} that evaluates a property value against a specified value. 030 * </p> 031 * <p> 032 * An implementation of {@link java.util.function.Predicate} that evaluates a property value on the object provided against a specified value and returns 033 * {@code true} if equal; {@code false} otherwise. The {@code BeanPropertyValueEqualsPredicate} constructor takes two parameters which determine what property 034 * will be evaluated on the target object and what its expected value should be. 035 * </p> 036 * <dl> 037 * <dt><strong> {@code public BeanPropertyValueEqualsPredicate( String propertyName, Object propertyValue )} </strong></dt> 038 * <dd>Will create a {@code Predicate} that will evaluate the target object and return 039 * {@code true</code> if the property specified by <code>propertyName} has a value which 040 * is equal to the value specified by {@code propertyValue}. Or return 041 * {@code false} otherwise. 042 * </dd> 043 * </dl> 044 * <p> 045 * <strong>Note:</strong> Property names can be a simple, nested, indexed, or mapped property as defined by 046 * {@code org.apache.commons.beanutils2.PropertyUtils}. If any object in the property path 047 * specified by {@code propertyName</code> is <code>null} then the outcome is based on the 048 * value of the {@code ignoreNull} attribute. 049 * </p> 050 * <p> 051 * A typical usage might look like: 052 * </p> 053 * <pre>{@code 054 * // create the closure 055 * BeanPropertyValueEqualsPredicate predicate = 056 * new BeanPropertyValueEqualsPredicate( "activeEmployee", Boolean.FALSE ); 057 * 058 * // filter the Collection 059 * CollectionUtils.filter( peopleCollection, predicate ); 060 * }</pre> 061 * <p> 062 * This would take a {@code Collection} of person objects and filter out any people whose 063 * {@code activeEmployee</code> property is <code>false}. Assuming... 064 * </p> 065 * <ul> 066 * <li> 067 * The top level object in the {@code peopleCollection} is an object which represents a 068 * person. 069 * </li> 070 * <li> 071 * The person object has a {@code getActiveEmployee()} method which returns 072 * the boolean value for the object's {@code activeEmployee} property. 073 * </li> 074 * </ul> 075 * <p> 076 * Another typical usage might look like: 077 * </p> 078 * <pre>{@code 079 * // create the closure 080 * BeanPropertyValueEqualsPredicate predicate = 081 * new BeanPropertyValueEqualsPredicate( "personId", "456-12-1234" ); 082 * 083 * // search the Collection 084 * CollectionUtils.find( peopleCollection, predicate ); 085 * }</pre> 086 * <p> 087 * This would search a {@code Collection} of person objects and return the first object whose 088 * {@code personId</code> property value equals <code>456-12-1234}. Assuming... 089 * </p> 090 * <ul> 091 * <li>The top level object in the {@code peopleCollection} is an object which represents a person.</li> 092 * <li>The person object has a {@code getPersonId()} method which returns the value for the object's {@code personId} property.</li> 093 * </ul> 094 * 095 * @param <T> The type of the input to the predicate. 096 * @param <V> The property value type. 097 * @see org.apache.commons.beanutils2.PropertyUtils 098 * @see java.util.function.Predicate 099 */ 100public 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 * @param object The object to be evaluated. 213 * @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}