2 * Copyright 2005-2010 the original author or authors.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.wamblee.reflection;
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;
27 import java.util.Map.Entry;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
31 import org.wamblee.general.ObjectElem;
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.
41 * The following fields and methods are excluded:
44 * <li>All fields and methods of the {@link Object} class.</li>
45 * <li>All fields and methods of collection classes (List, Map, Set), and of
49 * @author Erik Brakkee
51 public class ObjectTraversal {
53 public static final Logger LOGGER = Logger.getLogger(ObjectTraversal.class
57 * Visitor interface to be implemented for object traversal.
59 * @author Erik Brakkee
62 public static interface ObjectVisitor {
64 * Determines if the given class must be visited.
68 * @return True when visited, false otherwise.
70 boolean mustVisit(Class aClass);
73 * Determines if a given field must be visited. By default all declared
74 * fields (including private) are visited.
77 * @return True when visited.
79 boolean mustVisit(Field aField);
82 * Determines if the given property accessor must be visited.
86 * @return True when visited.
88 boolean mustVisit(Method aMethod);
95 * @return True if the object's fields and methods must be visited.
97 boolean visitPlainObject(Object aObject);
104 * @return True if the collection's elements must be visited as well.
106 boolean visitList(List aObject);
113 * @return True if the map's values must be visited as well.
115 boolean visitMap(Map aObject);
122 * @return True if the collection's elements must be visited as well.
124 boolean visitSet(Set aSet);
131 * @return True if the array's elements must be visited as well.
133 boolean visitArray(Object aArray);
137 private ObjectVisitor visitor;
138 private List<ObjectElem> excluded;
141 * Constructs the traversal.
146 public ObjectTraversal(ObjectVisitor aVisitor) {
149 excluded = new ArrayList<ObjectElem>();
153 * Adds an object instance to exclude from traversal.
158 public void addExcludedObject(Object aObject) {
159 excluded.add(new ObjectElem(aObject));
162 private String indent() {
163 StringBuffer buf = new StringBuffer();
164 for (int i = 1; i < level; i++) {
167 return buf.toString();
170 public void accept(Object aObject) {
171 if (aObject == null) {
174 if (aObject.getClass().equals(Object.class)) {
177 if (ReflectionUtils.isPrimitive(aObject.getClass())) {
180 if (!visitor.mustVisit(aObject.getClass())) {
184 if (alreadyProcessed(aObject)) {
187 if (LOGGER.isLoggable(Level.FINEST)) {
189 LOGGER.finest(indent() + "obj: " + aObject);
192 if (aObject instanceof List) {
193 if (visitor.visitList((List) aObject)) {
194 processList((List) aObject);
197 } else if (aObject instanceof Map) {
198 if (visitor.visitMap((Map) aObject)) {
199 processMap((Map) aObject);
202 } else if (aObject instanceof Set) {
203 if (visitor.visitSet((Set) aObject)) {
204 processSet((Set) aObject);
207 } else if (aObject.getClass().isArray()) {
208 if (visitor.visitArray(aObject)) {
209 processArray(aObject);
213 if (!visitor.visitPlainObject(aObject)) {
218 List<Method> methods = ReflectionUtils.getAllMethods(
219 aObject.getClass(), Object.class);
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) {
228 if (visitor.mustVisit(getter)) {
229 if (LOGGER.isLoggable(Level.FINEST)) {
231 .finest(indent() + "method: " + getter.getName());
233 acceptMethod(aObject, getter);
238 List<Field> fields = ReflectionUtils.getAllFields(aObject.getClass(),
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());
248 acceptField(aObject, field);
254 private void acceptMethod(Object aObject, Method aGetter) {
256 Object value = aGetter.invoke(aObject);
261 } catch (InvocationTargetException e) {
262 throw new RuntimeException(e.getMessage(), e);
263 } catch (IllegalAccessException e) {
264 throw new RuntimeException(e.getMessage(), e);
268 private void acceptField(Object aObject, Field aField) {
270 Object value = aField.get(aObject);
275 } catch (IllegalAccessException e) {
276 throw new RuntimeException(e.getMessage(), e);
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);
295 private boolean alreadyProcessed(Object aObject) {
296 ObjectElem elem = new ObjectElem(aObject);
297 if (excluded.contains(elem)) {
304 private void processList(List aObject) {
305 for (Object obj : aObject) {
310 private void processSet(Set aObject) {
311 for (Object obj : aObject) {
316 public <Key, Value> void processMap(Map<Key, Value> aMap) {
317 Set<Entry<Key, Value>> entries = aMap.entrySet();
319 for (Entry<Key, Value> entry : entries) {
320 Value value = entry.getValue();
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));