--- /dev/null
+/*
+ * Copyright 2005-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wamblee.reflection;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.wamblee.general.ObjectElem;
+
+/**
+ * <p>
+ * Class encapsulating object traversal through the fields and properties of an
+ * object. The class accepts a visitor in its constructor whose job it is to
+ * process any visited fields of the object.
+ * </p>
+ *
+ * <p>
+ * The following fields and methods are excluded:
+ * </p>
+ * <ul>
+ * <li>All fields and methods of the {@link Object} class.</li>
+ * <li>All fields and methods of collecation classes (List, Map, Set), and of
+ * arrays.</li>
+ * </ul>
+ *
+ * @author Erik Brakkee
+ */
+public class ObjectTraversal {
+
+ /**
+ * Visitor interface to be implemented for object traversal.
+ *
+ * @author Erik Brakkee
+ *
+ */
+ public static interface ObjectVisitor {
+ /**
+ * Determines if the given class must be visited.
+ *
+ * @param aClass
+ * Class.
+ * @return True when visited, false otherwise.
+ */
+ boolean mustVisit(Class aClass);
+
+ /**
+ * Determines if a given field must be visited. By default all declared
+ * fields (including private) are visited.
+ *
+ * @param aField
+ * @return True when visited.
+ */
+ boolean mustVisit(Field aField);
+
+ /**
+ * Determines if the given property accessor must be visited.
+ *
+ * @param aMethod
+ * Method to visit.
+ * @return True when visited.
+ */
+ boolean mustVisit(Method aMethod);
+
+ /**
+ * Visit an object.
+ *
+ * @param aObject
+ * Object to process
+ * @return True if the object's fields and methods must be visited.
+ */
+ boolean visitPlainObject(Object aObject);
+
+ /**
+ * Visit a collection
+ *
+ * @param aObject
+ * Object to process.
+ * @return True if the collection's elements must be visited as well.
+ */
+ boolean visitList(List aObject);
+
+ /**
+ * Visit a collection
+ *
+ * @param aObject
+ * Object to process.
+ * @return True if the map's values must be visited as well.
+ */
+ boolean visitMap(Map aObject);
+
+ /**
+ * Visit a collection
+ *
+ * @param aObject
+ * Object to process.
+ * @return True if the collection's elements must be visited as well.
+ */
+ boolean visitSet(Set aSet);
+
+ /**
+ * Visit a collection
+ *
+ * @param aObject
+ * Object to process.
+ * @return True if the array's elements must be visited as well.
+ */
+ boolean visitArray(Object aArray);
+ }
+
+ private ObjectVisitor visitor;
+ private List<ObjectElem> excluded;
+
+ /**
+ * Constructs the traversal.
+ *
+ * @param aVisitor
+ * Visitor to use.
+ */
+ public ObjectTraversal(ObjectVisitor aVisitor) {
+ visitor = aVisitor;
+ excluded = new ArrayList<ObjectElem>();
+ }
+
+ /**
+ * Adds an object instance to exclude from traversal.
+ *
+ * @param aObject
+ * Object to add.
+ */
+ public void addExcludedObject(Object aObject) {
+ excluded.add(new ObjectElem(aObject));
+ }
+
+ public void accept(Object aObject) {
+ if (aObject == null) {
+ return;
+ }
+ if (aObject.getClass().equals(Object.class)) {
+ return;
+ }
+ if (ReflectionUtils.isPrimitive(aObject.getClass())) {
+ return;
+ }
+ if (!visitor.mustVisit(aObject.getClass())) {
+ return;
+ }
+
+ if (alreadyProcessed(aObject)) {
+ return;
+ }
+
+ if (aObject instanceof List) {
+ if (visitor.visitList((List) aObject)) {
+ processList((List) aObject);
+ }
+ return;
+ } else if (aObject instanceof Map) {
+ if (visitor.visitMap((Map) aObject)) {
+ processMap((Map) aObject);
+ }
+ return;
+ } else if (aObject instanceof Set) {
+ if (visitor.visitSet((Set) aObject)) {
+ processSet((Set) aObject);
+ }
+ return;
+ } else if (aObject.getClass().isArray()) {
+ if (visitor.visitArray(aObject)) {
+ processArray(aObject);
+ }
+ return;
+ } else {
+ if (!visitor.visitPlainObject(aObject)) {
+ return;
+ }
+ }
+
+ List<Method> methods = ReflectionUtils.getAllMethods(
+ aObject.getClass(), Object.class);
+
+ for (Method getter : methods) {
+ if ((getter.getName().startsWith("get") || getter.getName()
+ .startsWith("is")) &&
+ !Modifier.isStatic(getter.getModifiers()) &&
+ getter.getParameterTypes().length == 0 &&
+ getter.getReturnType() != Void.class) {
+
+ if (visitor.mustVisit(getter)) {
+ acceptMethod(aObject, getter);
+ }
+ }
+ }
+
+ List<Field> fields = ReflectionUtils.getAllFields(aObject.getClass(),
+ Object.class);
+ for (Field field : fields) {
+ int modifiers = field.getModifiers();
+ if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
+ field.setAccessible(true);
+ if (visitor.mustVisit(field)) {
+ acceptField(aObject, field);
+ }
+ }
+ }
+ }
+
+ private void acceptMethod(Object aObject, Method aGetter) {
+ try {
+ Object value = aGetter.invoke(aObject);
+ if (value == null) {
+ return;
+ }
+ accept(value);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ private void acceptField(Object aObject, Field aField) {
+ try {
+ Object value = aField.get(aObject);
+ if (value == null) {
+ return;
+ }
+ accept(value);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ private void acceptPlainOrCollection(Object value) {
+ if (value instanceof Set) {
+ visitor.visitSet((Set) value);
+ processSet((Set) value);
+ } else if (value instanceof List) {
+ processList((List) value);
+ } else if (value instanceof Map) {
+ processMap((Map<?, ?>) value);
+ } else if (value.getClass().isArray()) {
+ processArray(value);
+ } else {
+ accept(value);
+ }
+ }
+
+ private boolean alreadyProcessed(Object aObject) {
+ ObjectElem elem = new ObjectElem(aObject);
+ if (excluded.contains(elem)) {
+ return true;
+ }
+ excluded.add(elem);
+ return false;
+ }
+
+ private void processList(List aObject) {
+ for (Object obj : aObject) {
+ accept(obj);
+ }
+ }
+
+ private void processSet(Set aObject) {
+ for (Object obj : aObject) {
+ accept(obj);
+ }
+ }
+
+ public <Key, Value> void processMap(Map<Key, Value> aMap) {
+ Set<Entry<Key, Value>> entries = aMap.entrySet();
+
+ for (Entry<Key, Value> entry : entries) {
+ Value value = entry.getValue();
+ accept(value);
+ }
+ }
+
+ public void processArray(Object aObject) {
+ int size = Array.getLength(aObject);
+ for (int i = 0; i < size; i++) {
+ accept(Array.get(aObject, i));
+ }
+ }
+
+}