package org.nuunframework.universalvisitor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import org.nuunframework.universalvisitor.api.Filter;
import org.nuunframework.universalvisitor.api.Job;
import org.nuunframework.universalvisitor.api.MapReduce;
import org.nuunframework.universalvisitor.api.Mapper;
import org.nuunframework.universalvisitor.api.Metadata;
import org.nuunframework.universalvisitor.api.Reducer;
import org.nuunframework.universalvisitor.core.JobDefault;
import org.nuunframework.universalvisitor.core.MapReduceDefault;
import org.nuunframework.universalvisitor.core.NodeDefault;
/**
* UniversalVisitor is the main entrypoint. With it you can visit any object
* instance.
* <p>
*
* @author Epo Jemba
* @author Pierre Thirouin
*
*/
public class UniversalVisitor {
@SuppressWarnings("unchecked")
public <T> void visit(AnnotatedElement ae, Mapper<T> mapper) {
visit(ae, (Filter) null, new MapReduceDefault<T>(mapper));
}
@SuppressWarnings("unchecked")
public <T> void visit(Object o, Mapper<T> mapper) {
visit(o, (Filter) null, new MapReduceDefault<T>(mapper));
}
@SuppressWarnings("unchecked")
public <T> void visit(AnnotatedElement o, Filter filter, Mapper<T> mapper) {
visit(o, filter, new MapReduceDefault<T>(mapper));
}
@SuppressWarnings("unchecked")
public <T> void visit(Object o, Filter filter, Mapper<T> mapper) {
visit(o, filter, new MapReduceDefault<T>(mapper));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> void visit(Object o, Mapper<T> mapper, Reducer... reducers) {
visit(o, (Filter) null, new MapReduceDefault<T>(mapper , reducers));
}
@SuppressWarnings("unchecked")
public <T> void visit(Object o, Filter filter, Mapper<T> mapper, Reducer<T, ?> reducer) {
visit(o, filter, new MapReduceDefault<T>(mapper , reducer));
}
public void visit(Object o, MapReduce<?> ...mapReduces) {
visit(o, null, mapReduces);
}
@SuppressWarnings("rawtypes")
public void visit(AnnotatedElement ae, Filter filter, MapReduce<?> ...mapReduces) {
visit(ae,filter,new JobDefault( mapReduces));
}
@SuppressWarnings({ "rawtypes" })
public void visit(Object o, Filter filter, MapReduce ...mapReduces) {
visit(o,filter,new JobDefault( mapReduces));
}
@SuppressWarnings({ "rawtypes" })
public void visit(AnnotatedElement ae, Job job) {
visit(ae, (Filter) null, job);
}
@SuppressWarnings({ "rawtypes" })
public void visit(AnnotatedElement ae, Filter filter, Job job) {
Set<Object> cache = new HashSet<Object>();
ChainedNode node = ChainedNode.createRoot();
if (filter == null) {
filter = Filter.TRUE;
}
recursiveVisit(ae, cache, node, filter);
doMapReduce(job, node);
}
/**
* @param job
* @param node
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private void doMapReduce(Job<?> job, ChainedNode node) {
for (node = node.next; node != null; node = node.next) {
for ( MapReduce mapReduce : job.mapReduces()) {
if (mapReduce.getMapper().handle(node.annotatedElement())) {
Object t = mapReduce.getMapper().map(node);
for (Reducer<Object, Object> reducer : mapReduce.getReducers()) {
reducer.collect(t);
}
}
}
}
}
@SuppressWarnings({ "rawtypes" })
public void visit(Object o, Job job) {
visit(o, (Filter)null , job);
}
@SuppressWarnings({ "rawtypes" })
public void visit(Object o, Filter filter, Job job) {
Set<Object> cache = new HashSet<Object>();
ChainedNode node = ChainedNode.createRoot();
if (filter == null) {
filter = Filter.TRUE;
}
recursiveVisit(o, cache, node, filter);
doMapReduce(job, node);
}
private static class ChainedNode extends NodeDefault {
ChainedNode next;
protected ChainedNode(Object instance, AnnotatedElement annotatedElement, int level, ChainedNode next) {
super(instance, annotatedElement, level);
this.next = next;
}
private void next(ChainedNode node) {
if (next != null) {
throw new IllegalStateException(
"next pair can not be set twice.");
}
next = node;
}
public static ChainedNode createRoot() {
return new ChainedNode(new Object(), null, -1, null);
}
public ChainedNode append(Object o, AnnotatedElement ao, int level,Metadata metadata) {
next(new ChainedNode(o, ao, level, null).metadata(metadata));
return next;
}
@Override
public ChainedNode metadata(Metadata metadata) {
return (ChainedNode) super.metadata(metadata);
}
public ChainedNode last() {
if (next != null) {
return next.last();
} else {
return this;
}
}
@Override
public String toString() {
String indentation ="";
for (int i = 0 ; i < level ; i ++) {
indentation += "\t";
} // instance()=
String rep = String.format("%sChainedNode [ %s@%s , level=%s , annotatedElement=%s] \n%s", indentation , instance().getClass().getSimpleName(), Integer.toHexString(instance().hashCode()) , level()
,annotatedElement(),next);
return rep;
// return "ChainedNode [instance()=" + instance() + ", level()="
// + level() + ", annotatedElement()=" + annotatedElement()
// + "] ==> \n" + next;
}
}
private void recursiveVisit(AnnotatedElement ae, Set<Object> cache, ChainedNode node, Filter filter) {
int currentLevel = node.level() + 1;
if (!cache.contains(ae)) {
cache.add(ae);
if (ae == null)
{
// ignore nulls
}
else if (Constructor.class.isAssignableFrom(ae.getClass()))
{
// visitConstructor((Constructor) ae, cache, node, currentLevel,filter);
}
else if (Method.class.isAssignableFrom(ae.getClass()))
{
// visitMethod((Method) ae, cache, node, currentLevel,filter);
}
else if (Field.class.isAssignableFrom(ae.getClass()))
{
// visitField((Field) ae, cache, node, currentLevel,filter);
}
else if (Package.class.isAssignableFrom(ae.getClass()))
{
// visitPackage((Package) ae, cache, node, currentLevel,filter);
}
else if ( Class.class.isAssignableFrom(ae.getClass()) && ae.getClass().isAnnotation() )
{
visitClass((Class<?>) ae, cache, node, currentLevel,filter);
}
else
{
// visitObject(object, cache, node, currentLevel,filter);
throw new IllegalStateException("Can not visist " + ae);
}
}
}
private void doVisitPackage(AnnotatedElement ae, Package p, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter, Metadata metadata) {
ChainedNode current = node;
// annotations on package
for (Annotation annotation : p.getDeclaredAnnotations()) {
doVisitAnnotation(p, annotation, cache, current, currentLevel, filter, metadata);
current = current.last();
}
// then Append the package it self
current = current.append(ae, p, currentLevel, null);
}
private void doVisitAnnotation(AnnotatedElement ae, Annotation a, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter, Metadata metadata) {
}
private void visitClass(Class<?> cläss, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter ) {
visitClass( cläss, cache, node, currentLevel, filter , (Metadata) null);
}
private void visitClass(Class<?> cläss, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter, Metadata metadata) {
Class<? extends Object> currentClass = cläss;
ChainedNode current = node;
if (!isJdkMember(currentClass)) {
if (currentClass != null && !isJdkMember(currentClass)) {
// package
Package p = currentClass.getPackage();
doVisitPackage(currentClass, p, cache, node, currentLevel, filter , metadata);
// annotations
// extends
// implements
// declared classes
// constructors
// methods
// fields
for (Constructor<?> c : currentClass.getDeclaredConstructors()) {
if (!isJdkMember(c) && !c.isSynthetic()) {
current = current.append(currentClass, c, currentLevel, metadata);
}
}
//
for (Method m : currentClass.getDeclaredMethods()) {
if (!isJdkMember(m) && !m.isSynthetic()) {
current = current.append(currentClass, m, currentLevel, metadata);
}
}
for (Field f : currentClass.getDeclaredFields()) {
if (!isJdkMember(f) && !f.isSynthetic()) {
current = current.append(currentClass, f, currentLevel, metadata);
if (filter != null && filter.retains(f)) {
Object deeperObject = readField(f, currentClass);
recursiveVisit(deeperObject, cache, current, filter);
current = current.last();
}
}
}
}
}
}
private void recursiveVisit(Object object, Set<Object> cache, ChainedNode node, Filter filter) {
int currentLevel = node.level() + 1;
if (!cache.contains(object)) {
cache.add(object);
if (object == null)
{
// ignore nulls
}
else if (Collection.class.isAssignableFrom(object.getClass()))
{
visitAllCollection((Collection<?>) object, cache, node, currentLevel,filter);
}
else if (object.getClass().isArray())
{
visitAllArray( object, cache, node,currentLevel, filter);
}
else if (Map.class.isAssignableFrom(object.getClass()))
{
visitAllMap((Map<?, ?>) object, cache, node,currentLevel, filter);
}
else
{
visitObject(object, cache, node, currentLevel,filter);
}
}
}
private void visitObject(Object object, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter) {
visitObject(object, cache, node, currentLevel, filter, null);
}
private <T> void visitConstructor(Constructor<T> ae, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter , Metadata metadata) {
// Params
// Annotations
// Exceptions
Class<? extends Object> currentClass = ae.getClass();
ChainedNode current = node;
Class<?>[] family = getAllInterfacesAndClasses(currentClass);
for (Class<?> elementClass : family) { // We iterate over all the family
// tree of the current class
//
if (elementClass != null && !isJdkMember(elementClass)) {
for (Constructor<?> c : elementClass.getDeclaredConstructors()) {
if (!isJdkMember(c) && !c.isSynthetic()) {
current = current.append(ae, c, currentLevel, metadata);
}
}
//
for (Method m : elementClass.getDeclaredMethods()) {
if (!isJdkMember(m) && !m.isSynthetic()) {
current = current.append(ae, m, currentLevel, metadata);
}
}
for (Field f : elementClass.getDeclaredFields()) {
if (!isJdkMember(f) && !f.isSynthetic()) {
current = current.append(ae, f, currentLevel, metadata);
if (filter != null && filter.retains(f)) {
Object deeperObject = readField(f, ae);
recursiveVisit(deeperObject, cache, current, filter);
current = current.last();
}
}
}
}
}
}
private void visitObject(Object object, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter , Metadata metadata) {
Class<? extends Object> currentClass = object.getClass();
if ( ! isJdkMember(currentClass)) {
ChainedNode current = node;
Class<?>[] family = getAllInterfacesAndClasses(currentClass);
for (Class<?> elementClass : family)
{ // We iterate over all the family tree of the current class
//
if (elementClass != null && !isJdkMember(elementClass)) {
for (Constructor<?> c : elementClass.getDeclaredConstructors()) {
if (!isJdkMember(c) && ! c.isSynthetic()) {
current = current.append(object, c, currentLevel,metadata);
}
}
//
for (Method m : elementClass.getDeclaredMethods()) {
if (!isJdkMember(m) && ! m.isSynthetic() ) {
current = current.append(object, m, currentLevel,metadata);
}
}
for (Field f : elementClass.getDeclaredFields()) {
if (!isJdkMember(f) && ! f.isSynthetic() ) {
current = current.append(object, f, currentLevel,metadata);
if (filter != null && filter.retains(f)) {
Object deeperObject = readField(f, object);
recursiveVisit(deeperObject, cache, current, filter);
current = current.last();
}
}
}
}
}
}
}
private void visitAllCollection(Collection<?> collection, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter) {
ChainedNode current = node;
boolean indexable = collection instanceof List || collection instanceof Queue;
Object[] valArray = collection.toArray();
for (int i = 0; i < valArray.length; i++) {
Object value = valArray[i];
if (value != null) {
if (indexable) {
visitObject(value, cache, current, currentLevel,filter,new Metadata(i));
} else {
visitObject(value, cache, current, currentLevel,filter);
}
current = current.last();
}
}
}
private void visitAllArray(Object arrayObject, Set<Object> cache, ChainedNode node, int currentLevel, Filter filter) {
ChainedNode current = node;
int l = Array.getLength(arrayObject);
for (int i = 0; i < l; i++) {
Object value = Array.get(arrayObject, i);
if (value != null) {
visitObject(value, cache, current, currentLevel,filter,new Metadata(i));
current = current.last();
}
}
}
private void visitAllMap(Map<?, ?> values, Set<Object> cache, ChainedNode pair, int currentLevel, Filter filter) {
ChainedNode current = pair;
for (Object thisKey : values.keySet()) {
Object value = values.get(thisKey);
if (value != null) {
visitObject(thisKey, cache, current, currentLevel , filter );
current = current.last();
visitObject(value, cache, current, currentLevel , filter , new Metadata(thisKey));
current = current.last();
}
}
}
private boolean isJdkMember(Member input) {
return isJdkMember(input.getDeclaringClass());
}
private boolean isJdkMember(Class<?> input) {
return input.getPackage().getName()
.startsWith("java.") || input.getPackage().getName().startsWith("javax.") ;
}
private Object readField(Field f, Object instance) {
Object o = null;
try {
f.setAccessible(true);
o = f.get(instance);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return o;
}
/**
* Returns all the interfaces and classes implemented or extended by a class.
*
* @param clazz The class to search from.
* @return The array of classes and interfaces found.
*/
private Class<?>[] getAllInterfacesAndClasses(Class<?> clazz) {
return getAllInterfacesAndClasses(new Class[]{clazz});
}
/**
* This method walks up the inheritance hierarchy to make sure we get every
* class/interface extended or implemented by classes.
*
* @param classes The classes array used as search starting point.
* @return the found classes and interfaces.
*/
@SuppressWarnings("unchecked")
private Class<?>[] getAllInterfacesAndClasses(Class<?>[] classes) {
if (0 == classes.length) {
return classes;
} else {
List<Class<?>> extendedClasses = new ArrayList<Class<?>>();
// all interfaces hierarchy
for (Class<?> clazz : classes) {
if (clazz != null) {
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces != null) {
extendedClasses.addAll(Arrays.asList(interfaces));
}
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
extendedClasses.addAll(Arrays.asList(superclass));
}
}
}
// Class::getInterfaces() gets only interfaces/classes
// implemented/extended directly by a given class.
// We need to walk the whole way up the tree.
return concat(classes, getAllInterfacesAndClasses(extendedClasses.toArray(new Class[extendedClasses.size()])));
}
}
@SuppressWarnings("rawtypes")
private Class[] concat(Class[] A, Class[] B) {
int aLen = A.length;
int bLen = B.length;
Class[] C= new Class[aLen+bLen];
System.arraycopy(A, 0, C, 0, aLen);
System.arraycopy(B, 0, C, aLen, bLen);
return C;
}
}