(no commit message)
[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
54         .getName());
55
56     /**
57      * Visitor interface to be implemented for object traversal.
58      * 
59      * @author Erik Brakkee
60      * 
61      */
62     public static interface ObjectVisitor {
63         /**
64          * Determines if the given class must be visited.
65          * 
66          * @param aClass
67          *            Class.
68          * @return True when visited, false otherwise.
69          */
70         boolean mustVisit(Class aClass);
71
72         /**
73          * Determines if a given field must be visited. By default all declared
74          * fields (including private) are visited.
75          * 
76          * @param aField
77          * @return True when visited.
78          */
79         boolean mustVisit(Field aField);
80
81         /**
82          * Determines if the given property accessor must be visited.
83          * 
84          * @param aMethod
85          *            Method to visit.
86          * @return True when visited.
87          */
88         boolean mustVisit(Method aMethod);
89
90         /**
91          * Visit an object.
92          * 
93          * @param aObject
94          *            Object to process
95          * @return True if the object's fields and methods must be visited.
96          */
97         boolean visitPlainObject(Object aObject);
98
99         /**
100          * Visit a collection
101          * 
102          * @param aObject
103          *            Object to process.
104          * @return True if the collection's elements must be visited as well.
105          */
106         boolean visitList(List aObject);
107
108         /**
109          * Visit a collection
110          * 
111          * @param aObject
112          *            Object to process.
113          * @return True if the map's values must be visited as well.
114          */
115         boolean visitMap(Map aObject);
116
117         /**
118          * Visit a collection
119          * 
120          * @param aObject
121          *            Object to process.
122          * @return True if the collection's elements must be visited as well.
123          */
124         boolean visitSet(Set aSet);
125
126         /**
127          * Visit a collection
128          * 
129          * @param aObject
130          *            Object to process.
131          * @return True if the array's elements must be visited as well.
132          */
133         boolean visitArray(Object aArray);
134     }
135
136     private int level;
137     private ObjectVisitor visitor;
138     private List<ObjectElem> excluded;
139
140     /**
141      * Constructs the traversal.
142      * 
143      * @param aVisitor
144      *            Visitor to use.
145      */
146     public ObjectTraversal(ObjectVisitor aVisitor) {
147         level = 0;
148         visitor = aVisitor;
149         excluded = new ArrayList<ObjectElem>();
150     }
151
152     /**
153      * Adds an object instance to exclude from traversal.
154      * 
155      * @param aObject
156      *            Object to add.
157      */
158     public void addExcludedObject(Object aObject) {
159         excluded.add(new ObjectElem(aObject));
160     }
161
162     private String indent() {
163         StringBuffer buf = new StringBuffer();
164         for (int i = 1; i < level; i++) {
165             buf.append("    ");
166         }
167         return buf.toString();
168     }
169
170     public void accept(Object aObject) {
171         if (aObject == null) {
172             return;
173         }
174         if (aObject.getClass().equals(Object.class)) {
175             return;
176         }
177         if (ReflectionUtils.isPrimitive(aObject.getClass())) {
178             return;
179         }
180         if (!visitor.mustVisit(aObject.getClass())) {
181             return;
182         }
183
184         if (alreadyProcessed(aObject)) {
185             return;
186         }
187         if (LOGGER.isLoggable(Level.FINEST)) {
188             level++;
189             LOGGER.finest(indent() + "obj: " + aObject);
190         }
191
192         if (aObject instanceof List) {
193             if (visitor.visitList((List) aObject)) {
194                 processList((List) aObject);
195             }
196             return;
197         } else if (aObject instanceof Map) {
198             if (visitor.visitMap((Map) aObject)) {
199                 processMap((Map) aObject);
200             }
201             return;
202         } else if (aObject instanceof Set) {
203             if (visitor.visitSet((Set) aObject)) {
204                 processSet((Set) aObject);
205             }
206             return;
207         } else if (aObject.getClass().isArray()) {
208             if (visitor.visitArray(aObject)) {
209                 processArray(aObject);
210             }
211             return;
212         } else {
213             if (!visitor.visitPlainObject(aObject)) {
214                 return;
215             }
216         }
217
218         List<Method> methods = ReflectionUtils.getAllMethods(
219             aObject.getClass(), Object.class);
220
221         for (Method getter : methods) {
222             if ((getter.getName().startsWith("get") || getter.getName()
223                 .startsWith("is")) &&
224                 !Modifier.isStatic(getter.getModifiers()) &&
225                 getter.getParameterTypes().length == 0 &&
226                 getter.getReturnType() != Void.class) {
227
228                 if (visitor.mustVisit(getter)) {
229                     if (LOGGER.isLoggable(Level.FINEST)) {
230                         LOGGER
231                             .finest(indent() + "method:  " + getter.getName());
232                     }
233                     acceptMethod(aObject, getter);
234                 }
235             }
236         }
237
238         List<Field> fields = ReflectionUtils.getAllFields(aObject.getClass(),
239             Object.class);
240         for (Field field : fields) {
241             int modifiers = field.getModifiers();
242             if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
243                 field.setAccessible(true);
244                 if (visitor.mustVisit(field)) {
245                     if (LOGGER.isLoggable(Level.FINEST)) {
246                         LOGGER.finest(indent() + "field:  " + field.getName());
247                     }
248                     acceptField(aObject, field);
249                 }
250             }
251         }
252     }
253
254     private void acceptMethod(Object aObject, Method aGetter) {
255         try {
256             Object value = aGetter.invoke(aObject);
257             if (value == null) {
258                 return;
259             }
260             accept(value);
261         } catch (InvocationTargetException e) {
262             throw new RuntimeException(e.getMessage(), e);
263         } catch (IllegalAccessException e) {
264             throw new RuntimeException(e.getMessage(), e);
265         }
266     }
267
268     private void acceptField(Object aObject, Field aField) {
269         try {
270             Object value = aField.get(aObject);
271             if (value == null) {
272                 return;
273             }
274             accept(value);
275         } catch (IllegalAccessException e) {
276             throw new RuntimeException(e.getMessage(), e);
277         }
278     }
279
280     private void acceptPlainOrCollection(Object aValue) {
281         if (aValue instanceof Set) {
282             visitor.visitSet((Set) aValue);
283             processSet((Set) aValue);
284         } else if (aValue instanceof List) {
285             processList((List) aValue);
286         } else if (aValue instanceof Map) {
287             processMap((Map<?, ?>) aValue);
288         } else if (aValue.getClass().isArray()) {
289             processArray(aValue);
290         } else {
291             accept(aValue);
292         }
293     }
294
295     private boolean alreadyProcessed(Object aObject) {
296         ObjectElem elem = new ObjectElem(aObject);
297         if (excluded.contains(elem)) {
298             return true;
299         }
300         excluded.add(elem);
301         return false;
302     }
303
304     private void processList(List aObject) {
305         for (Object obj : aObject) {
306             accept(obj);
307         }
308     }
309
310     private void processSet(Set aObject) {
311         for (Object obj : aObject) {
312             accept(obj);
313         }
314     }
315
316     public <Key, Value> void processMap(Map<Key, Value> aMap) {
317         Set<Entry<Key, Value>> entries = aMap.entrySet();
318
319         for (Entry<Key, Value> entry : entries) {
320             Value value = entry.getValue();
321             accept(value);
322         }
323     }
324
325     public void processArray(Object aObject) {
326         int size = Array.getLength(aObject);
327         for (int i = 0; i < size; i++) {
328             accept(Array.get(aObject, i));
329         }
330     }
331
332 }