brooklyn.entity.rebind.Dumpers.java Source code

Java tutorial

Introduction

Here is the source code for brooklyn.entity.rebind.Dumpers.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 brooklyn.entity.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 java.util.Random;

import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.javalang.Serializers;
import brooklyn.util.javalang.Serializers.ObjectReplacer;

import com.google.common.base.Objects;
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;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * 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 {

    private 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 class Pointer implements Serializable {
        private static final long serialVersionUID = 1709707205457063174L;
        private static final Random random = new Random();
        private final String id;
        private final int rand;

        public Pointer(String id) {
            this.id = id;
            this.rand = random.nextInt();
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(id, rand);
        }

        @Override
        public boolean equals(Object o) {
            return (o instanceof Pointer) && Objects.equal(id, ((Pointer) o).id)
                    && Objects.equal(rand, ((Pointer) o).rand);
        }
    }

    public static void logUnserializableChains(Object root)
            throws IllegalArgumentException, IllegalAccessException {
        logUnserializableChains(root, ObjectReplacer.NOOP);
    }

    public static void logUnserializableChains(Object root, final ObjectReplacer replacer)
            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, replacer);
                    return true;
                } catch (Throwable e) {
                    Exceptions.propagateIfFatal(e);

                    // not serializable in some way: report
                    ImmutableList<Object> refChainList = ImmutableList.copyOf(refChain);
                    // for debugging it can be useful to turn this on
                    //                    LOG.warn("Unreconstitutable object detected ("+o+"): "+e);

                    // 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()) {
            StringBuilder msg = new StringBuilder("\t" + "type=" + entry.getValue() + "; chain=" + "\n");
            for (Object chainElement : entry.getKey()) {
                // try-catch motivated by NPE in org.jclouds.domain.LoginCredentials.toString
                String chainElementStr;
                try {
                    chainElementStr = chainElement.toString();
                } catch (Exception e) {
                    Exceptions.propagateIfFatal(e);
                    LOG.error("Error calling toString on instance of " + chainElement.getClass(), e);
                    chainElementStr = "<error " + e.getClass().getSimpleName() + " in toString>";
                }
                msg.append("\t\t" + "type=").append(chainElement.getClass()).append("; val=")
                        .append(chainElementStr).append("\n");
            }
            LOG.warn(msg.toString());
        }
    }

    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 {
        private static final long serialVersionUID = -4751524179224569184L;

        @SuppressWarnings("unused")
        final Object key, value;

        public Entry(Object key, Object value) {
            this.key = key;
            this.value = value;
        }
    }
}