code: package objectexplorer;
  
  import com.google.common.base.*;
  import objectexplorer.ObjectExplorer.Feature;
  import com.google.common.collect.HashMultiset;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableMultiset;
  import com.google.common.collect.ImmutableSet;
  import com.google.common.collect.Multiset;
  
  import java.util.EnumSet;
  
  /**
   * A tool that can qualitatively measure the footprint
   * ({@literal e.g.}, number of objects, references,
   * primitives) of a graph structure.
   */
  public class ObjectGraphMeasurer {
    /**
     * The footprint of an object graph.
     */
    public static class Footprint {
      private final int objects;
      private final int references;
      private final ImmutableMultiset<Class<?>> primitives;
  
      private static final ImmutableSet<Class<?>> primitiveTypes = ImmutableSet.<Class<?>>of(
          boolean.class, byte.class, char.class, short.class,
          int.class, float.class, long.class, double.class);
  
      /**
       * Constructs a Footprint, by specifying the number of objects,
       * references, and primitives (represented as a {@link Multiset}).
       *
       * @param objects the number of objects
       * @param references the number of references
       * @param primitives the number of primitives (represented by the
       * respective primitive classes, e.g. {@code int.class} etc)
       */
      public Footprint(int objects, int references, Multiset<Class<?>> primitives) {
        Preconditions.checkArgument(objects >= 0, "Negative number of objects");
        Preconditions.checkArgument(references >= 0, "Negative number of references");
        Preconditions.checkArgument(primitiveTypes.containsAll(primitives.elementSet()),
        "Unexpected primitive type");
        this.objects = objects;
        this.references = references;
        this.primitives = ImmutableMultiset.copyOf(primitives);
      }
  
      /**
       * Returns the number of objects of this footprint.
       */
      public int getObjects() {
        return objects;
      }
  
      /**
       * Returns the number of references of this footprint.
       */
      public int getReferences() {
        return references;
      }
  
      /**
       * Returns the number of primitives of this footprint
       * (represented by the respective primitive classes,
       * {@literal e.g.} {@code int.class} etc).
       */
      public ImmutableMultiset<Class<?>> getPrimitives() {
        return primitives;
      }
  
      @Override
      public String toString() {
        return MoreObjects.toStringHelper(this)
        .add("Objects", objects)
        .add("References", references)
        .add("Primitives", primitives)
        .toString();
      }
    }
  
    /**
     * Measures the footprint of the specified object graph.
     * The object graph is defined by a root object and whatever object can be
     * reached through that, excluding static fields, {@code Class} objects,
     * and fields defined in {@code enum}s (all these are considered shared
     * values, which should not contribute to the cost of any single object
     * graph).
     *
     * <p>Equivalent to {@code measure(rootObject, Predicates.alwaysTrue())}.
     *
     * @param rootObject the root object of the object graph
     * @return the footprint of the object graph
     */
    public static Footprint measure(Object rootObject) {
      return measure(rootObject, Predicates.alwaysTrue());
    }
  
    /**
     * Measures the footprint of the specified object graph.
     * The object graph is defined by a root object and whatever object can be
     * reached through that, excluding static fields, {@code Class} objects,
     * and fields defined in {@code enum}s (all these are considered shared
     * values, which should not contribute to the cost of any single object
     * graph), and any object for which the user-provided predicate returns
     * {@code false}.
     *
     * @param rootObject the root object of the object graph
     * @param objectAcceptor a predicate that returns {@code true} for objects
     * to be explored (and treated as part of the footprint), or {@code false}
     * to forbid the traversal to traverse the given object
     * @return the footprint of the object graph
     */
    public static Footprint measure(Object rootObject, Predicate<Object> objectAcceptor) {
      Preconditions.checkNotNull(objectAcceptor, "predicate");
  
      Predicate<Chain> completePredicate = Predicates.and(ImmutableList.of(
          ObjectExplorer.notEnumFieldsOrClasses,
          Predicates.compose(objectAcceptor, ObjectExplorer.chainToObject),
          new ObjectExplorer.AtMostOncePredicate()
      ));
  
      return ObjectExplorer.exploreObject(rootObject, new ObjectGraphVisitor(completePredicate),
          EnumSet.of(Feature.VISIT_PRIMITIVES, Feature.VISIT_NULL));
    }
  
    private static class ObjectGraphVisitor implements ObjectVisitor<Footprint> {
      private int objects;
      // -1 to account for the root, which has no reference leading to it
      private int references = -1;
      private final Multiset<Class<?>> primitives = HashMultiset.create();
      private final Predicate<Chain> predicate;
  
      ObjectGraphVisitor(Predicate<Chain> predicate) {
        this.predicate = predicate;
      }
  
      public Traversal visit(Chain chain) {
        if (chain.isPrimitive()) {
          primitives.add(chain.getValueType());
          return Traversal.SKIP;
        } else {
          references++;
        }
        if (predicate.apply(chain) && chain.getValue() != null) {
          objects++;
          return Traversal.EXPLORE;
        }
        return Traversal.SKIP;
      }
  
      public Footprint result() {
        return new Footprint(objects, references, ImmutableMultiset.copyOf(primitives));
      }
    }
  }
  