/* * 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 java.util.logging.Level; import java.util.logging.Logger; import org.wamblee.general.ObjectElem; /** *

* 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. *

* *

* The following fields and methods are excluded: *

* * * @author Erik Brakkee */ public class ObjectTraversal { public static final Logger LOGGER = Logger.getLogger(ObjectTraversal.class .getName()); /** * 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 int level; private ObjectVisitor visitor; private List excluded; /** * Constructs the traversal. * * @param aVisitor * Visitor to use. */ public ObjectTraversal(ObjectVisitor aVisitor) { level = 0; visitor = aVisitor; excluded = new ArrayList(); } /** * Adds an object instance to exclude from traversal. * * @param aObject * Object to add. */ public void addExcludedObject(Object aObject) { excluded.add(new ObjectElem(aObject)); } private String indent() { StringBuffer buf = new StringBuffer(); for (int i = 1; i < level; i++) { buf.append(" "); } return buf.toString(); } 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 (LOGGER.isLoggable(Level.FINEST)) { level++; LOGGER.finest(indent() + "obj: " + aObject); } 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 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)) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER .finest(indent() + "method: " + getter.getName()); } acceptMethod(aObject, getter); } } } List 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)) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest(indent() + "field: " + field.getName()); } 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 aValue) { if (aValue instanceof Set) { visitor.visitSet((Set) aValue); processSet((Set) aValue); } else if (aValue instanceof List) { processList((List) aValue); } else if (aValue instanceof Map) { processMap((Map) aValue); } else if (aValue.getClass().isArray()) { processArray(aValue); } else { accept(aValue); } } 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 void processMap(Map aMap) { Set> entries = aMap.entrySet(); for (Entry 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)); } } }