View Javadoc
1   /*
2    * #%L
3    * Nuiton Utils
4    * %%
5    * Copyright (C) 2004 - 2017 CodeLutin
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  package org.nuiton.util.beans;
23  
24  import com.google.common.base.Predicate;
25  import com.google.common.base.Predicates;
26  import org.apache.commons.beanutils.MethodUtils;
27  import org.apache.commons.beanutils.PropertyUtils;
28  
29  import java.beans.PropertyChangeListener;
30  import java.beans.PropertyDescriptor;
31  import java.lang.reflect.InvocationTargetException;
32  import java.lang.reflect.Method;
33  import java.util.HashSet;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.TreeMap;
37  
38  /**
39   * Usefull methods around the {@link PropertyChangeListener}.
40   *
41   * @author Tony Chemit - chemit@codelutin.com
42   * @since 1.4.1
43   */
44  public class BeanUtil {
45  
46      public static final String ADD_PROPERTY_CHANGE_LISTENER =
47              "addPropertyChangeListener";
48  
49      public static final String REMOVE_PROPERTY_CHANGE_LISTENER =
50              "removePropertyChangeListener";
51  
52      protected BeanUtil() {
53          // no instance
54      }
55  
56      /**
57       * Is a property is readable ?
58       *
59       * @since 2.5.11
60       */
61      public static final Predicate<PropertyDescriptor> IS_READ_DESCRIPTOR = new Predicate<PropertyDescriptor>() {
62          @Override
63          public boolean apply(PropertyDescriptor input) {
64              return input.getReadMethod() != null;
65          }
66  
67          @Override
68          public boolean test(PropertyDescriptor input) {
69              return apply(input);
70          }
71      };
72  
73      /**
74       * Is a property is writable ?
75       *
76       * @since 2.5.11
77       */
78      public static final Predicate<PropertyDescriptor> IS_WRITE_DESCRIPTOR = new Predicate<PropertyDescriptor>() {
79          @Override
80          public boolean apply(PropertyDescriptor input) {
81              return input.getWriteMethod() != null;
82          }
83  
84          @Override
85          public boolean test(PropertyDescriptor input) {
86              return apply(input);
87          }
88      };
89  
90      /**
91       * Is a property is readable and writable ?
92       *
93       * @since 2.5.11
94       */
95      public static final Predicate<PropertyDescriptor> IS_READ_AND_WRITE_DESCRIPTOR = Predicates.and(
96              IS_READ_DESCRIPTOR,
97              IS_WRITE_DESCRIPTOR
98      );
99  
100     /**
101      * Test if the given type is JavaBean compiliant, says that it has two
102      * public methods :
103      * <ul>
104      * <li>{@code addPropertyChangeListener}</li>
105      * <li>{@code removePropertyChangeListener}</li>
106      * </ul>
107      *
108      * @param type type to test
109      * @return {@code true} if type is Javabean compiliant, {@code false}
110      *         otherwise
111      * @since 2.0
112      */
113     public static boolean isJavaBeanCompiliant(Class<?> type) {
114 
115         try {
116             type.getMethod(ADD_PROPERTY_CHANGE_LISTENER,
117                            PropertyChangeListener.class);
118         } catch (NoSuchMethodException e) {
119             // no add method
120             return false;
121         }
122 
123         try {
124             type.getMethod(REMOVE_PROPERTY_CHANGE_LISTENER,
125                            PropertyChangeListener.class);
126         } catch (NoSuchMethodException e) {
127             // no add method
128             return false;
129         }
130 
131         return true;
132     }
133 
134     /**
135      * Add the given {@code listener} to the given {@code bean} using the
136      * normalized method named {@code addPropertyChangeListener}.
137      *
138      * @param listener the listener to add
139      * @param bean     the bean on which the listener is added
140      * @throws InvocationTargetException if could not invoke the method
141      *                                   {@code addPropertyChangeListener}
142      * @throws NoSuchMethodException     if method
143      *                                   {@code addPropertyChangeListener}
144      *                                   does not exist on given bean
145      * @throws IllegalAccessException    if an illegal access occurs when
146      *                                   invoking the method
147      *                                   {@code addPropertyChangeListener}
148      */
149     public static void addPropertyChangeListener(PropertyChangeListener listener,
150                                                  Object bean) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
151         MethodUtils.invokeExactMethod(bean,
152                                       ADD_PROPERTY_CHANGE_LISTENER,
153                                       new Object[]{listener},
154                                       new Class[]{PropertyChangeListener.class}
155         );
156     }
157 
158     /**
159      * Remove the given {@code listener} from the given {@code bean} using the
160      * normalized method named {@code removePropertyChangeListener}.
161      *
162      * @param listener the listener to remove
163      * @param bean     the bean on which the listener is removed
164      * @throws InvocationTargetException if could not invoke the method
165      *                                   {@code removePropertyChangeListener}
166      * @throws NoSuchMethodException     if method
167      *                                   {@code removePropertyChangeListener}
168      *                                   does not exist on given bean
169      * @throws IllegalAccessException    if an illegal access occurs when
170      *                                   invoking the method
171      *                                   {@code removePropertyChangeListener}
172      */
173     public static void removePropertyChangeListener(PropertyChangeListener listener,
174                                                     Object bean) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
175         MethodUtils.invokeExactMethod(bean,
176                                       REMOVE_PROPERTY_CHANGE_LISTENER,
177                                       new Object[]{listener},
178                                       new Class[]{PropertyChangeListener.class}
179         );
180     }
181 
182     /**
183      * Obtains all readable properties from a given type.
184      *
185      * @param beanType the type to seek
186      * @return the set of all readable properties for the given type
187      * @since 2.0
188      */
189     public static Set<String> getReadableProperties(Class<?> beanType) {
190         Set<Class<?>> exploredTypes = new HashSet<Class<?>>();
191         Set<String> result = new HashSet<String>();
192 
193         // get properties for the class
194         getReadableProperties(beanType, result, exploredTypes);
195 
196         // the special getClass will never be a JavaBean property...
197         result.remove("class");
198 
199         return result;
200     }
201 
202     /**
203      * Obtains all writeable properties from a given type.
204      *
205      * @param beanType the type to seek
206      * @return the set of all writeable properties for the given type
207      * @since 2.0
208      */
209     public static Set<String> getWriteableProperties(Class<?> beanType) {
210         Set<Class<?>> exploredTypes = new HashSet<Class<?>>();
211         Set<String> result = new HashSet<String>();
212 
213         // get properties for the class
214         getWriteableProperties(beanType, result, exploredTypes);
215 
216         return result;
217     }
218 
219     /**
220      * Obtains all readable properties from a given type.
221      *
222      * @param beanType the type to seek
223      * @param propertyName  FIXME
224      * @return the set of all readable properties for the given type
225      * @since 2.0
226      */
227     public static boolean isNestedReadableProperty(Class<?> beanType, String propertyName) {
228         boolean result = propertyName.contains(".");
229         if (result) {
230             int dotIndex = propertyName.indexOf(".");
231             String firstLevelProperty = propertyName.substring(0, dotIndex);
232 
233             Class<?> nestedType = getReadableType(beanType, firstLevelProperty);
234             if (nestedType == null) {
235 
236                 result = false;
237             } else {
238 
239                 String rest = propertyName.substring(dotIndex + 1);
240                 result = isNestedReadableProperty(nestedType, rest);
241             }
242 
243         } else {
244 
245             // not a nested property check it directly
246             Class<?> nestedType = getReadableType(beanType, propertyName);
247             result = nestedType != null;
248         }
249 
250         return result;
251     }
252 
253     public static Class<?> getReadableType(Class<?> beanType, String propertyName) {
254         PropertyDescriptor[] descriptors =
255                 PropertyUtils.getPropertyDescriptors(beanType);
256 
257         Class<?> result = null;
258         for (PropertyDescriptor descriptor : descriptors) {
259             String name = descriptor.getName();
260             if (descriptor.getReadMethod() != null &&
261                 propertyName.equals(name)) {
262                 result = descriptor.getReadMethod().getReturnType();
263                 break;
264             }
265         }
266 
267         if (result == null) {
268 
269             // try with super-class
270             if (beanType.getSuperclass() != null) {
271 
272                 // get properties fro super-class
273                 result = getReadableType(beanType.getSuperclass(), propertyName);
274             }
275         }
276 
277         if (result == null) {
278 
279             // try it with interfaces
280             Class<?>[] interfaces = beanType.getInterfaces();
281             for (Class<?> anInterface : interfaces) {
282 
283                 result = getReadableType(anInterface, propertyName);
284 
285                 if (result != null) {
286 
287                     // found it
288                     break;
289                 }
290             }
291         }
292         return result;
293     }
294 
295     /**
296      * Scan the given type and obtain {@link PropertyDescriptor}
297      * given the (optional) predicate (except the {@code class} property of
298      * any java object).
299      *
300      * <strong>Note:</strong> If no predicate is given, then all descriptors
301      * are returned.
302      *
303      * @param beanType  the bean type to scan
304      * @param predicate the optional predicate to keep descriptor
305      * @return set of all matching descriptors
306      * @since 2.6.11
307      */
308     public static Set<PropertyDescriptor> getDescriptors(Class<?> beanType,
309                                                          Predicate<PropertyDescriptor> predicate) {
310 
311         if (predicate == null) {
312             predicate = Predicates.alwaysTrue();
313         }
314 
315         Set<Class<?>> exploredTypes = new HashSet<Class<?>>();
316         Map<String, PropertyDescriptor> result = new TreeMap<String, PropertyDescriptor>();
317 
318         getDescriptors(beanType, predicate, result, exploredTypes);
319 
320         // the special getClass will never be a JavaBean property...
321         result.remove("class");
322 
323         return new HashSet<PropertyDescriptor>(result.values());
324     }
325 
326     public static Method getMutator(Object bean, String property) {
327         Method mutator = null;
328         if (bean == null) {
329             throw new NullPointerException("could not find bean");
330         }
331         if (property == null) {
332             throw new NullPointerException("could not find property");
333         }
334 
335         try {
336             PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(bean, property);
337             if (descriptor != null) {
338                 mutator = descriptor.getWriteMethod();
339             }
340         } catch (Exception e) {
341             throw new RuntimeException(e);
342         }
343 
344         return mutator;
345     }
346 
347     protected static void getDescriptors(Class<?> beanType,
348                                          Predicate<PropertyDescriptor> predicate,
349                                          Map<String, PropertyDescriptor> result,
350                                          Set<Class<?>> exploredTypes) {
351 
352         if (exploredTypes.contains(beanType)) {
353 
354             // already explored
355             return;
356         }
357         exploredTypes.add(beanType);
358 
359         PropertyDescriptor[] descriptors =
360                 PropertyUtils.getPropertyDescriptors(beanType);
361 
362         for (PropertyDescriptor descriptor : descriptors) {
363             String name = descriptor.getName();
364             if (!result.containsKey(name) && predicate.apply(descriptor)) {
365                 result.put(name, descriptor);
366             }
367         }
368 
369         if (beanType.getSuperclass() != null) {
370 
371             // get properties fro super-class
372             getDescriptors(beanType.getSuperclass(), predicate, result, exploredTypes);
373         }
374         Class<?>[] interfaces = beanType.getInterfaces();
375         for (Class<?> anInterface : interfaces) {
376 
377             // get properties fro super-class
378             getDescriptors(anInterface, predicate, result, exploredTypes);
379         }
380     }
381 
382     protected static void getReadableProperties(Class<?> beanType,
383                                                 Set<String> result,
384                                                 Set<Class<?>> exploredTypes) {
385 
386         if (exploredTypes.contains(beanType)) {
387 
388             // already explored
389             return;
390         }
391         exploredTypes.add(beanType);
392 
393         // get properties for the class
394         getReadableProperties(beanType, result);
395 
396         if (beanType.getSuperclass() != null) {
397 
398             // get properties fro super-class
399             getReadableProperties(beanType.getSuperclass(), result, exploredTypes);
400         }
401         Class<?>[] interfaces = beanType.getInterfaces();
402         for (Class<?> anInterface : interfaces) {
403 
404             // get properties fro super-class
405             getReadableProperties(anInterface, result, exploredTypes);
406         }
407     }
408 
409     protected static void getReadableProperties(Class<?> beanType,
410                                                 Set<String> result) {
411 
412         PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(beanType);
413         for (PropertyDescriptor descriptor : descriptors) {
414             String name = descriptor.getName();
415             if (descriptor.getReadMethod() != null) {
416                 result.add(name);
417             }
418         }
419     }
420 
421     protected static void getWriteableProperties(Class<?> beanType,
422                                                  Set<String> result,
423                                                  Set<Class<?>> exploredTypes) {
424 
425         if (exploredTypes.contains(beanType)) {
426 
427             // already explored
428             return;
429         }
430         exploredTypes.add(beanType);
431 
432         // get properties for the class
433         getWriteableProperties(beanType, result);
434 
435         if (beanType.getSuperclass() != null) {
436 
437             // get properties fro super-class
438             getWriteableProperties(beanType.getSuperclass(), result, exploredTypes);
439         }
440         Class<?>[] interfaces = beanType.getInterfaces();
441         for (Class<?> anInterface : interfaces) {
442 
443             // get properties fro super-class
444             getWriteableProperties(anInterface, result, exploredTypes);
445         }
446     }
447 
448     protected static void getWriteableProperties(Class<?> beanType,
449                                                  Set<String> result) {
450 
451         PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(beanType);
452         for (PropertyDescriptor descriptor : descriptors) {
453             String name = descriptor.getName();
454             if (descriptor.getWriteMethod() != null) {
455                 result.add(name);
456             }
457         }
458     }
459 }