org.srplib.reflection.objectgraph.ObjectGraph Maven / Gradle / Ivy
package org.srplib.reflection.objectgraph;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.srplib.contract.Argument;
import org.srplib.reflection.ReflectionUtils;
import org.srplib.support.Predicate;
/**
* Encapsulates object graph navigation algorithm.
*
*
* -
* iterates java container structures: (Map, Collection, array, etc.).
*
* -
* iterates declared fields of java classes using reflection API.
*
* -
* handles cyclic references.
*
*
*
* Useful where object graph navigation is required: debugging, logging, conversion, etc.
*
* @author Anton Pechinsky
*/
public class ObjectGraph implements Element {
private Object root;
private Set visitedIdentities = new HashSet();
private Predicate> filter;
/**
* Creates object graph with specified root and class filter.
*
* @param root Object root object. {@code null} is supported
* @param filter a predicate defining should or not implementation examine class internals.
*/
public ObjectGraph(Object root, Predicate> filter) {
Argument.checkNotNull(root, "root must not be null!");
Argument.checkNotNull(filter, "filter must not be null!");
this.root = root;
this.filter = filter;
}
/**
* Creates object graph with specified root.
*
* @param root Object root object. {@code null} is supported.
*/
public ObjectGraph(Object root) {
this(root, new StandardTraversableClassesFilter());
}
@Override
public void accept(Visitor visitor) {
traverse(root, visitor);
}
private void traverse(Object object, Visitor visitor) {
if (object == null) {
return;
}
if (!isTraversable(object)) {
return;
}
if (isVisited(object)) {
return;
}
rememberVisited(object);
visitor.visit(object);
if (ReflectionUtils.isMap(object.getClass())) {
traverseMap((Map) object, visitor);
}
else if (ReflectionUtils.isCollection(object.getClass())) {
traverseCollection((Collection) object, visitor);
}
else if (ReflectionUtils.isArray(object.getClass())) {
traverseArray((Object[]) object, visitor);
}
else {
traverseDeclaredFields(object.getClass(), object, visitor);
}
}
private void rememberVisited(Object object) {
int identity = identity(object);
visitedIdentities.add(identity);
}
private boolean isVisited(Object object) {
int identity = identity(object);
return visitedIdentities.contains(identity);
}
private int identity(Object object) {
return System.identityHashCode(object);
}
private void traverseMap(Map map, Visitor visitor) {
for (Object entryObject : map.entrySet()) {
Map.Entry entry = (Map.Entry) entryObject;
traverse(entry.getKey(), visitor);
traverse(entry.getValue(), visitor);
}
}
private void traverseCollection(Collection collection, Visitor visitor) {
for (Object valueItem : collection) {
traverse(valueItem, visitor);
}
}
private void traverseArray(Object[] array, Visitor visitor) {
for (Object valueItem : array) {
traverse(valueItem, visitor);
}
}
private void traverseDeclaredFields(Class objectClass, Object object, Visitor visitor) {
if (objectClass == Object.class) {
return;
}
for (Field field : objectClass.getDeclaredFields()) {
if (ReflectionUtils.isSyntheticName(field.getName())) {
continue;
}
Object fieldValue = ReflectionUtils.getFieldValue(object, field);
traverse(fieldValue, visitor);
}
traverseDeclaredFields(objectClass.getSuperclass(), object, visitor);
}
private boolean isTraversable(Object object) {
return object != null && filter.test(object.getClass());
}
}