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