blob: 2665194fad032a213e9868f33a07ccc8a707a817 [file] [log] [blame]
package brooklyn.internal.rebind;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.javalang.Serializers;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* Convenience for writing out an object hierarchy.
*
* This is particularly useful for NotSerializableExceptions, where it does not tell you
* which object contained the unserializable field.
*
* @author aled
*/
public class Dumpers {
protected static final Logger LOG = LoggerFactory.getLogger(Dumpers.class);
private static List<String> UNTRAVERSED_PREFIXES = ImmutableList.of("java.lang", "java.io");
private static final int MAX_MEMBERS = 100;
private static final Predicate<Field> SERIALIZED_FIELD_PREDICATE = new Predicate<Field>() {
@Override public boolean apply(@Nullable Field input) {
int excludedModifiers = Modifier.TRANSIENT ^ Modifier.STATIC;
return (input.getModifiers() & excludedModifiers) == 0;
}
};
public static void logUnserializableChains(Object root) throws IllegalArgumentException, IllegalAccessException {
final Map<List<Object>, Class<?>> unserializablePaths = Maps.newLinkedHashMap();
Visitor visitor = new Visitor() {
@Override public boolean visit(Object o, Iterable<Object> refChain) {
try {
Serializers.reconstitute(o);
return true;
} catch (Exception e) {
// not serializable in some way: report
ImmutableList<Object> refChainList = ImmutableList.copyOf(refChain);
// First strip out any less specific paths
for (Iterator<List<Object>> iter = unserializablePaths.keySet().iterator(); iter.hasNext();) {
List<Object> existing = iter.next();
if (refChainList.size() >= existing.size() && refChainList.subList(0, existing.size()).equals(existing)) {
iter.remove();
}
}
// Then add this list
unserializablePaths.put(ImmutableList.copyOf(refChainList), o.getClass());
return false;
}
}
};
deepVisitInternal(root, SERIALIZED_FIELD_PREDICATE, Lists.newArrayList(), new LinkedList<Object>(), visitor);
LOG.warn("Not serializable ("+root+"):");
for (Map.Entry<List<Object>, Class<?>> entry : unserializablePaths.entrySet()) {
LOG.warn("\t"+"type="+entry.getValue()+"; chain="+entry.getKey());
}
}
public static void deepDumpSerializableness(Object o) {
deepDump(o, SERIALIZED_FIELD_PREDICATE, System.out);
}
public static void deepDump(Object o, Predicate<Field> fieldPredicate, PrintStream out) {
try {
out.println("Deep dump:");
deepDumpInternal(o, fieldPredicate, out, 1, "", Lists.newArrayList());
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
private static void deepDumpInternal(Object o, Predicate<Field> fieldPredicate, PrintStream out, int indentSize, String prefix, List<Object> visited) throws IllegalArgumentException, IllegalAccessException {
String indent = com.google.common.base.Strings.repeat(" ", indentSize*2);
Class<?> clazz = (o != null) ? o.getClass() : null;
if (o == null) {
out.println(indent+prefix+"null");
} else if (isClassUntraversable(clazz)) {
out.println(indent+prefix+"(untraversable) type="+clazz+"; val="+o.toString());
} else if (containsSame(visited, o)) {
out.println(indent+prefix+"duplicate (type="+clazz+"; val="+o.toString()+")");
} else {
visited.add(o);
out.println(indent+prefix+"type="+clazz+"; val="+o.toString());
Map<String, Object> members = findMembers(o, fieldPredicate);
for (Map.Entry<String, Object> entry : Iterables.limit(members.entrySet(), MAX_MEMBERS)) {
deepDumpInternal(entry.getValue(), fieldPredicate, out, indentSize+1, ""+entry.getKey()+": ", visited);
}
if (members.size() > MAX_MEMBERS) {
out.println(indent+prefix+"TRUNCATED ("+members.size()+" members in total)");
}
}
}
private static void deepVisitInternal(Object o, Predicate<Field> fieldPredicate, List<Object> visited, Deque<Object> refChain, Visitor visitor) throws IllegalArgumentException, IllegalAccessException {
Class<?> clazz = (o != null) ? o.getClass() : null;
refChain.addLast(o);
Iterable<Object> filteredRefChain = Iterables.filter(refChain, Predicates.not(Predicates.instanceOf(Dumpers.Entry.class)));
try {
if (o == null) {
// no-op
} else if (isClassUntraversable(clazz)) {
visitor.visit(o, filteredRefChain);
} else if (containsSame(visited, o)) {
// no-op
} else {
visited.add(o);
boolean subTreeComplete = visitor.visit(o, filteredRefChain);
if (!subTreeComplete) {
Map<String, Object> members = findMembers(o, fieldPredicate);
for (Map.Entry<String, Object> entry : members.entrySet()) {
deepVisitInternal(entry.getValue(), fieldPredicate, visited, refChain, visitor);
}
}
}
} finally {
refChain.removeLast();
}
}
public interface Visitor {
/**
* @param refChain The chain of references leading to this object (starting at the root)
* @return True if this part of the tree is complete; false if need to continue visiting children
*/
public boolean visit(Object o, Iterable<Object> refChain);
}
private static Map<String,Object> findMembers(Object o, Predicate<Field> fieldPredicate) throws IllegalArgumentException, IllegalAccessException {
Map<String,Object> result = Maps.newLinkedHashMap();
Class<?> clazz = (o != null) ? o.getClass() : null;
if (o instanceof Iterable) {
int i = 0;
for (Object member : (Iterable)o) {
result.put("member"+(i++), member);
}
} else if (o instanceof Map) {
int i = 0;
Map<?,?> m = (Map<?,?>) o;
for (Map.Entry<?,?> entry : m.entrySet()) {
result.put("member"+(i++), new Entry(entry.getKey(), entry.getValue()));
}
} else {
for (Field field : FlagUtils.getAllFields(clazz, fieldPredicate)) {
field.setAccessible(true);
String fieldName = field.getName();
Object fieldVal = field.get(o);
result.put(fieldName, fieldVal);
}
}
return result;
}
private static boolean isClassUntraversable(Class<?> clazz) {
String clazzName = clazz.getName();
for (String prefix : UNTRAVERSED_PREFIXES) {
if (clazzName.startsWith(prefix)) {
return true;
}
}
return false;
}
private static boolean containsSame(Iterable<?> vals, Object val) {
for (Object contender : vals) {
if (contender == val) return true;
}
return false;
}
private static class Entry implements Serializable {
final Object key;
final Object value;
public Entry(Object key, Object value) {
this.key = key;
this.value = value;
}
}
}