7b63729e425a3d459546041371fd466ea4157561
[utils] / support / general / src / main / java / org / wamblee / reflection / ObjectTraversal.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.reflection;
17
18 import java.lang.reflect.Array;
19 import java.lang.reflect.Field;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Modifier;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.Map.Entry;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import org.wamblee.general.ObjectElem;
32
33 /**
34  * <p>
35  * Class encapsulating object traversal through the fields and properties of an
36  * object. The class accepts a visitor in its constructor whose job it is to
37  * process any visited fields of the object.
38  * </p>
39  * 
40  * <p>
41  * The following fields and methods are excluded:
42  * </p>
43  * <ul>
44  * <li>All fields and methods of the {@link Object} class.</li>
45  * <li>All fields and methods of collecation classes (List, Map, Set), and of
46  * arrays.</li>
47  * </ul>
48  * 
49  * @author Erik Brakkee
50  */
51 public class ObjectTraversal {
52     
53     public static final Logger LOGGER = Logger.getLogger(ObjectTraversal.class.getName());
54
55     /**
56      * Visitor interface to be implemented for object traversal.
57      * 
58      * @author Erik Brakkee
59      * 
60      */
61     public static interface ObjectVisitor {
62         /**
63          * Determines if the given class must be visited.
64          * 
65          * @param aClass
66          *            Class.
67          * @return True when visited, false otherwise.
68          */
69         boolean mustVisit(Class aClass);
70
71         /**
72          * Determines if a given field must be visited. By default all declared
73          * fields (including private) are visited.
74          * 
75          * @param aField
76          * @return True when visited.
77          */
78         boolean mustVisit(Field aField);
79
80         /**
81          * Determines if the given property accessor must be visited.
82          * 
83          * @param aMethod
84          *            Method to visit.
85          * @return True when visited.
86          */
87         boolean mustVisit(Method aMethod);
88
89         /**
90          * Visit an object.
91          * 
92          * @param aObject
93          *            Object to process
94          * @return True if the object's fields and methods must be visited.
95          */
96         boolean visitPlainObject(Object aObject);
97
98         /**
99          * Visit a collection
100          * 
101          * @param aObject
102          *            Object to process.
103          * @return True if the collection's elements must be visited as well.
104          */
105         boolean visitList(List aObject);
106
107         /**
108          * Visit a collection
109          * 
110          * @param aObject
111          *            Object to process.
112          * @return True if the map's values must be visited as well.
113          */
114         boolean visitMap(Map aObject);
115
116         /**
117          * Visit a collection
118          * 
119          * @param aObject
120          *            Object to process.
121          * @return True if the collection's elements must be visited as well.
122          */
123         boolean visitSet(Set aSet);
124
125         /**
126          * Visit a collection
127          * 
128          * @param aObject
129          *            Object to process.
130          * @return True if the array's elements must be visited as well.
131          */
132         boolean visitArray(Object aArray);
133     }
134
135     private int level;
136     private ObjectVisitor visitor;
137     private List<ObjectElem> excluded;
138
139     /**
140      * Constructs the traversal.
141      * 
142      * @param aVisitor
143      *            Visitor to use.
144      */
145     public ObjectTraversal(ObjectVisitor aVisitor) {
146         level = 0;
147         visitor = aVisitor;
148         excluded = new ArrayList<ObjectElem>();
149     }
150
151     /**
152      * Adds an object instance to exclude from traversal.
153      * 
154      * @param aObject
155      *            Object to add.
156      */
157     public void addExcludedObject(Object aObject) {
158         excluded.add(new ObjectElem(aObject));
159     }
160     
161     private String indent() { 
162         StringBuffer buf = new StringBuffer();
163         for (int i = 1; i < level; i++) { 
164             buf.append("    ");
165         }
166         return buf.toString();
167     }
168
169     public void accept(Object aObject) {
170         if (aObject == null) {
171             return;
172         }
173         if (aObject.getClass().equals(Object.class)) {
174             return;
175         }
176         if (ReflectionUtils.isPrimitive(aObject.getClass())) {
177             return;
178         }
179         if (!visitor.mustVisit(aObject.getClass())) {
180             return;
181         }
182
183         if (alreadyProcessed(aObject)) {
184             return;
185         }
186         if ( LOGGER.isLoggable(Level.FINEST)) {
187             level++;
188             LOGGER.finest(indent() + "obj: " + aObject);
189         }
190
191         if (aObject instanceof List) {
192             if (visitor.visitList((List) aObject)) {
193                 processList((List) aObject);
194             }
195             return;
196         } else if (aObject instanceof Map) {
197             if (visitor.visitMap((Map) aObject)) {
198                 processMap((Map) aObject);
199             }
200             return;
201         } else if (aObject instanceof Set) {
202             if (visitor.visitSet((Set) aObject)) {
203                 processSet((Set) aObject);
204             }
205             return;
206         } else if (aObject.getClass().isArray()) {
207             if (visitor.visitArray(aObject)) {
208                 processArray(aObject);
209             }
210             return;
211         } else {
212             if (!visitor.visitPlainObject(aObject)) {
213                 return;
214             }
215         }
216
217         List<Method> methods = ReflectionUtils.getAllMethods(
218             aObject.getClass(), Object.class);
219
220         for (Method getter : methods) {
221             if ((getter.getName().startsWith("get") || getter.getName()
222                 .startsWith("is")) &&
223                 !Modifier.isStatic(getter.getModifiers()) &&
224                 getter.getParameterTypes().length == 0 &&
225                 getter.getReturnType() != Void.class) {
226
227                 if (visitor.mustVisit(getter)) {
228                     if ( LOGGER.isLoggable(Level.FINEST)) { 
229                         LOGGER.finest(indent() + "method:  " + getter.getName());
230                     }
231                     acceptMethod(aObject, getter);
232                 }
233             }
234         }
235
236         List<Field> fields = ReflectionUtils.getAllFields(aObject.getClass(),
237             Object.class);
238         for (Field field : fields) {
239             int modifiers = field.getModifiers();
240             if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
241                 field.setAccessible(true);
242                 if (visitor.mustVisit(field)) {
243                     if ( LOGGER.isLoggable(Level.FINEST)) { 
244                         LOGGER.finest(indent() + "field:  " + field.getName());
245                     }
246                     acceptField(aObject, field);
247                 }
248             }
249         }
250     }
251
252     private void acceptMethod(Object aObject, Method aGetter) {
253         try {
254             Object value = aGetter.invoke(aObject);
255             if (value == null) {
256                 return;
257             }
258             accept(value);
259         } catch (InvocationTargetException e) {
260             throw new RuntimeException(e.getMessage(), e);
261         } catch (IllegalAccessException e) {
262             throw new RuntimeException(e.getMessage(), e);
263         }
264     }
265
266     private void acceptField(Object aObject, Field aField) {
267         try {
268             Object value = aField.get(aObject);
269             if (value == null) {
270                 return;
271             }
272             accept(value);
273         } catch (IllegalAccessException e) {
274             throw new RuntimeException(e.getMessage(), e);
275         }
276     }
277
278     private void acceptPlainOrCollection(Object value) {
279         if (value instanceof Set) {
280             visitor.visitSet((Set) value);
281             processSet((Set) value);
282         } else if (value instanceof List) {
283             processList((List) value);
284         } else if (value instanceof Map) {
285             processMap((Map<?, ?>) value);
286         } else if (value.getClass().isArray()) {
287             processArray(value);
288         } else {
289             accept(value);
290         }
291     }
292
293     private boolean alreadyProcessed(Object aObject) {
294         ObjectElem elem = new ObjectElem(aObject);
295         if (excluded.contains(elem)) {
296             return true;
297         }
298         excluded.add(elem);
299         return false;
300     }
301
302     private void processList(List aObject) {
303         for (Object obj : aObject) {
304             accept(obj);
305         }
306     }
307
308     private void processSet(Set aObject) {
309         for (Object obj : aObject) {
310             accept(obj);
311         }
312     }
313
314     public <Key, Value> void processMap(Map<Key, Value> aMap) {
315         Set<Entry<Key, Value>> entries = aMap.entrySet();
316
317         for (Entry<Key, Value> entry : entries) {
318             Value value = entry.getValue();
319             accept(value);
320         }
321     }
322
323     public void processArray(Object aObject) {
324         int size = Array.getLength(aObject);
325         for (int i = 0; i < size; i++) {
326             accept(Array.get(aObject, i));
327         }
328     }
329
330 }