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 collecation classes (List, Map, Set), and of
49 * @author Erik Brakkee
51 public class ObjectTraversal {
53 public static final Logger LOGGER = Logger.getLogger(ObjectTraversal.class.getName());
56 * Visitor interface to be implemented for object traversal.
58 * @author Erik Brakkee
61 public static interface ObjectVisitor {
63 * Determines if the given class must be visited.
67 * @return True when visited, false otherwise.
69 boolean mustVisit(Class aClass);
72 * Determines if a given field must be visited. By default all declared
73 * fields (including private) are visited.
76 * @return True when visited.
78 boolean mustVisit(Field aField);
81 * Determines if the given property accessor must be visited.
85 * @return True when visited.
87 boolean mustVisit(Method aMethod);
94 * @return True if the object's fields and methods must be visited.
96 boolean visitPlainObject(Object aObject);
103 * @return True if the collection's elements must be visited as well.
105 boolean visitList(List aObject);
112 * @return True if the map's values must be visited as well.
114 boolean visitMap(Map aObject);
121 * @return True if the collection's elements must be visited as well.
123 boolean visitSet(Set aSet);
130 * @return True if the array's elements must be visited as well.
132 boolean visitArray(Object aArray);
136 private ObjectVisitor visitor;
137 private List<ObjectElem> excluded;
140 * Constructs the traversal.
145 public ObjectTraversal(ObjectVisitor aVisitor) {
148 excluded = new ArrayList<ObjectElem>();
152 * Adds an object instance to exclude from traversal.
157 public void addExcludedObject(Object aObject) {
158 excluded.add(new ObjectElem(aObject));
161 private String indent() {
162 StringBuffer buf = new StringBuffer();
163 for (int i = 1; i < level; i++) {
166 return buf.toString();
169 public void accept(Object aObject) {
170 if (aObject == null) {
173 if (aObject.getClass().equals(Object.class)) {
176 if (ReflectionUtils.isPrimitive(aObject.getClass())) {
179 if (!visitor.mustVisit(aObject.getClass())) {
183 if (alreadyProcessed(aObject)) {
186 if ( LOGGER.isLoggable(Level.FINEST)) {
188 LOGGER.finest(indent() + "obj: " + aObject);
191 if (aObject instanceof List) {
192 if (visitor.visitList((List) aObject)) {
193 processList((List) aObject);
196 } else if (aObject instanceof Map) {
197 if (visitor.visitMap((Map) aObject)) {
198 processMap((Map) aObject);
201 } else if (aObject instanceof Set) {
202 if (visitor.visitSet((Set) aObject)) {
203 processSet((Set) aObject);
206 } else if (aObject.getClass().isArray()) {
207 if (visitor.visitArray(aObject)) {
208 processArray(aObject);
212 if (!visitor.visitPlainObject(aObject)) {
217 List<Method> methods = ReflectionUtils.getAllMethods(
218 aObject.getClass(), Object.class);
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) {
227 if (visitor.mustVisit(getter)) {
228 if ( LOGGER.isLoggable(Level.FINEST)) {
229 LOGGER.finest(indent() + "method: " + getter.getName());
231 acceptMethod(aObject, getter);
236 List<Field> fields = ReflectionUtils.getAllFields(aObject.getClass(),
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());
246 acceptField(aObject, field);
252 private void acceptMethod(Object aObject, Method aGetter) {
254 Object value = aGetter.invoke(aObject);
259 } catch (InvocationTargetException e) {
260 throw new RuntimeException(e.getMessage(), e);
261 } catch (IllegalAccessException e) {
262 throw new RuntimeException(e.getMessage(), e);
266 private void acceptField(Object aObject, Field aField) {
268 Object value = aField.get(aObject);
273 } catch (IllegalAccessException e) {
274 throw new RuntimeException(e.getMessage(), e);
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()) {
293 private boolean alreadyProcessed(Object aObject) {
294 ObjectElem elem = new ObjectElem(aObject);
295 if (excluded.contains(elem)) {
302 private void processList(List aObject) {
303 for (Object obj : aObject) {
308 private void processSet(Set aObject) {
309 for (Object obj : aObject) {
314 public <Key, Value> void processMap(Map<Key, Value> aMap) {
315 Set<Entry<Key, Value>> entries = aMap.entrySet();
317 for (Entry<Key, Value> entry : entries) {
318 Value value = entry.getValue();
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));