code: package objectexplorer;
  
  import com.google.common.base.Preconditions;
  import java.lang.reflect.Field;
  import java.util.ArrayDeque;
  import java.util.Deque;
  import java.util.Iterator;
  import javax.annotation.Nonnull;
  import javax.annotation.Nullable;
  
  /**
   * A chain of references, which starts at a root object and leads to a
   * particular value (either an object or a primitive).
   *
   * @author andreou
   */
  public abstract class Chain {
    private final Object value;
    private final Chain parent;
  
    Chain(Chain parent, Object value) {
      this.parent = parent;
      this.value = value;
    }
  
    static Chain root(Object value) {
      return new Chain(null, Preconditions.checkNotNull(value)) {
        @Override
        public Class<?> getValueType() {
          return getValue().getClass();
        }
      };
    }
  
    FieldChain appendField(Field field, Object value) {
      return new FieldChain(this, Preconditions.checkNotNull(field), value);
    }
  
    ArrayIndexChain appendArrayIndex(int arrayIndex, Object value) {
      return new ArrayIndexChain(this, arrayIndex, value);
    }
  
    /**
     * Returns whether this chain has a parent. This returns false only when
     * this chain represents the root object itself.
     */
    public boolean hasParent() {
      return parent != null;
    }
  
    /**
     * Returns the parent chain, from which this chain was created.
     * @throws IllegalStateException if {@code !hasParent()}, then an
     */
    public @Nonnull Chain getParent() {
      Preconditions.checkState(parent != null, "This is the root value, it has no parent");
      return parent;
    }
  
    /**
     * Returns the value that this chain leads to. If the value is a primitive,
     * a wrapper object is returned instead.
     */
    public @Nullable Object getValue() {
      return value;
    }
  
    public abstract @Nonnull Class<?> getValueType();
  
    /**
     * Returns whether the connection of the parent chain and this chain is
     * through a field (of the getParent().getValue().getClass() class).
     */
    public boolean isThroughField() {
      return false;
    }
  
    /**
     * Returns whether the connection of the parent chain and this chain is
     * through an array index, i.e. the parent leads to an array, and this
     * chain leads to an element of that array.
     */
    public boolean isThroughArrayIndex() {
      return false;
    }
  
    /**
     * Returns whether the value of this chain represents a primitive.
     */
    public boolean isPrimitive() {
      return getValueType().isPrimitive();
    }
  
    /**
     * Returns the root object of this chain.
     */
    public @Nonnull Object getRoot() {
      Chain current = this;
      while (current.hasParent()) {
        current = current.getParent();
      }
      return current.getValue();
    }
  
    Deque<Chain> reverse() {
      Deque<Chain> reverseChain = new ArrayDeque<Chain>(8);
      Chain current = this;
      reverseChain.addFirst(current);
      while (current.hasParent()) {
        current = current.getParent();
        reverseChain.addFirst(current);
      }
      return reverseChain;
    }
  
    @Override public String toString() {
      StringBuilder sb = new StringBuilder(32);
  
      Iterator<Chain> it = reverse().iterator();
      sb.append(it.next().getValue());
      while (it.hasNext()) {
        sb.append("->");
        Chain current = it.next();
        if (current.isThroughField()) {
          sb.append(((FieldChain)current).getField().getName());
        } else if (current.isThroughArrayIndex()) {
          sb.append("[").append(((ArrayIndexChain)current).getArrayIndex()).append("]");
        }
      }
      return sb.toString();
    }
  
    static class FieldChain extends Chain {
      private final Field field;
  
      FieldChain(Chain parent, Field referringField, Object value) {
        super(parent, value);
        this.field = referringField;
      }
  
      @Override
      public boolean isThroughField() {
        return true;
      }
  
      @Override
      public boolean isThroughArrayIndex() {
        return false;
      }
  
      @Override
      public Class<?> getValueType() {
        return field.getType();
      }
  
      public Field getField() {
        return field;
      }
    }
  
    static class ArrayIndexChain extends Chain {
      private final int index;
  
      ArrayIndexChain(Chain parent, int index, Object value) {
        super(parent, value);
        this.index = index;
      }
  
      @Override
      public boolean isThroughField() {
        return false;
      }
  
      @Override
      public boolean isThroughArrayIndex() {
        return true;
      }
  
      @Override
      public Class<?> getValueType() {
        return getParent().getValue().getClass().getComponentType();
      }
  
      public int getArrayIndex() {
        return index;
      }
    }
  }
  