content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed 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 com.android.ahat.heapdump;
  
  import com.android.tools.perflib.heap.ClassObj;
  import com.android.tools.perflib.heap.Instance;
  import com.android.tools.perflib.heap.RootObj;
  import java.awt.image.BufferedImage;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  
  public abstract class AhatInstance implements Diffable<AhatInstance> {
    private long mId;
    private long mSize;
    private long mTotalRetainedSize;
    private long mRetainedSizes[];      // Retained size indexed by heap index
    private boolean mIsReachable;
    private AhatHeap mHeap;
    private AhatInstance mImmediateDominator;
    private AhatInstance mNextInstanceToGcRoot;
    private String mNextInstanceToGcRootField = "???";
    private AhatClassObj mClassObj;
    private AhatInstance[] mHardReverseReferences;
    private AhatInstance[] mSoftReverseReferences;
    private Site mSite;
  
    // If this instance is a root, mRootTypes contains a set of the root types.
    // If this instance is not a root, mRootTypes is null.
    private List<String> mRootTypes;
  
    // List of instances this instance immediately dominates.
    private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
  
    private AhatInstance mBaseline;
  
    public AhatInstance(long id) {
      mId = id;
      mBaseline = this;
    }
  
    /**
     * Initializes this AhatInstance based on the given perflib instance.
     * The AhatSnapshot should be used to look up AhatInstances and AhatHeaps.
     * There is no guarantee that the AhatInstances returned by
     * snapshot.findInstance have been initialized yet.
     */
    void initialize(AhatSnapshot snapshot, Instance inst) {
      mId = inst.getId();
      mSize = inst.getSize();
      mTotalRetainedSize = inst.getTotalRetainedSize();
      mIsReachable = inst.isReachable();
  
      List<AhatHeap> heaps = snapshot.getHeaps();
      mRetainedSizes = new long[heaps.size()];
      for (AhatHeap heap : heaps) {
        mRetainedSizes[heap.getIndex()] = inst.getRetainedSize(heap.getIndex());
      }
  
      mHeap = snapshot.getHeap(inst.getHeap().getName());
  
      Instance dom = inst.getImmediateDominator();
      if (dom == null || dom instanceof RootObj) {
        mImmediateDominator = null;
      } else {
        mImmediateDominator = snapshot.findInstance(dom.getId());
        mImmediateDominator.mDominated.add(this);
      }
  
      ClassObj clsObj = inst.getClassObj();
      if (clsObj != null) {
        mClassObj = snapshot.findClassObj(clsObj.getId());
      }
  
      // A couple notes about reverse references:
      // * perflib sometimes returns unreachable reverse references. If
      //   snapshot.findInstance returns null, it means the reverse reference is
      //   not reachable, so we filter it out.
      // * We store the references as AhatInstance[] instead of
      //   ArrayList<AhatInstance> because it saves a lot of space and helps
      //   with performance when there are a lot of AhatInstances.
      ArrayList<AhatInstance> ahatRefs = new ArrayList<AhatInstance>();
      ahatRefs = new ArrayList<AhatInstance>();
      for (Instance ref : inst.getHardReverseReferences()) {
        AhatInstance ahat = snapshot.findInstance(ref.getId());
        if (ahat != null) {
          ahatRefs.add(ahat);
        }
      }
      mHardReverseReferences = new AhatInstance[ahatRefs.size()];
      ahatRefs.toArray(mHardReverseReferences);
  
      List<Instance> refs = inst.getSoftReverseReferences();
      ahatRefs.clear();
      if (refs != null) {
        for (Instance ref : refs) {
          AhatInstance ahat = snapshot.findInstance(ref.getId());
          if (ahat != null) {
            ahatRefs.add(ahat);
          }
        }
      }
      mSoftReverseReferences = new AhatInstance[ahatRefs.size()];
      ahatRefs.toArray(mSoftReverseReferences);
    }
  
    /**
     * Returns a unique identifier for the instance.
     */
    public long getId() {
      return mId;
    }
  
    /**
     * Returns the shallow number of bytes this object takes up.
     */
    public long getSize() {
      return mSize;
    }
  
    /**
     * Returns the number of bytes belonging to the given heap that this instance
     * retains.
     */
    public long getRetainedSize(AhatHeap heap) {
      int index = heap.getIndex();
      return 0 <= index && index < mRetainedSizes.length ? mRetainedSizes[heap.getIndex()] : 0;
    }
  
    /**
     * Returns the total number of bytes this instance retains.
     */
    public long getTotalRetainedSize() {
      return mTotalRetainedSize;
    }
  
    /**
     * Returns whether this object is strongly-reachable.
     */
    public boolean isReachable() {
      return mIsReachable;
    }
  
    /**
     * Returns the heap that this instance is allocated on.
     */
    public AhatHeap getHeap() {
      return mHeap;
    }
  
    /**
     * Returns true if this instance is marked as a root instance.
     */
    public boolean isRoot() {
      return mRootTypes != null;
    }
  
    /**
     * Marks this instance as being a root of the given type.
     */
    void addRootType(String type) {
      if (mRootTypes == null) {
        mRootTypes = new ArrayList<String>();
        mRootTypes.add(type);
      } else if (!mRootTypes.contains(type)) {
        mRootTypes.add(type);
      }
    }
  
    /**
     * Returns a list of string descriptions of the root types of this object.
     * Returns null if this object is not a root.
     */
    public Collection<String> getRootTypes() {
      return mRootTypes;
    }
  
    /**
     * Returns the immediate dominator of this instance.
     * Returns null if this is a root instance.
     */
    public AhatInstance getImmediateDominator() {
      return mImmediateDominator;
    }
  
    /**
     * Returns a list of those objects immediately dominated by the given
     * instance.
     */
    public List<AhatInstance> getDominated() {
      return mDominated;
    }
  
    /**
     * Returns the site where this instance was allocated.
     */
    public Site getSite() {
      return mSite;
    }
  
    /**
     * Sets the allocation site of this instance.
     */
    void setSite(Site site) {
      mSite = site;
    }
  
    /**
     * Returns true if the given instance is a class object
     */
    public boolean isClassObj() {
      // Overridden by AhatClassObj.
      return false;
    }
  
    /**
     * Returns this as an AhatClassObj if this is an AhatClassObj.
     * Returns null if this is not an AhatClassObj.
     */
    public AhatClassObj asClassObj() {
      // Overridden by AhatClassObj.
      return null;
    }
  
    /**
     * Returns the class object instance for the class of this object.
     */
    public AhatClassObj getClassObj() {
      return mClassObj;
    }
  
    /**
     * Returns the name of the class this object belongs to.
     */
    public String getClassName() {
      AhatClassObj classObj = getClassObj();
      return classObj == null ? "???" : classObj.getName();
    }
  
    /**
     * Returns true if the given instance is an array instance
     */
    public boolean isArrayInstance() {
      // Overridden by AhatArrayInstance.
      return false;
    }
  
    /**
     * Returns this as an AhatArrayInstance if this is an AhatArrayInstance.
     * Returns null if this is not an AhatArrayInstance.
     */
    public AhatArrayInstance asArrayInstance() {
      // Overridden by AhatArrayInstance.
      return null;
    }
  
    /**
     * Returns true if the given instance is a class instance
     */
    public boolean isClassInstance() {
      return false;
    }
  
    /**
     * Returns this as an AhatClassInstance if this is an AhatClassInstance.
     * Returns null if this is not an AhatClassInstance.
     */
    public AhatClassInstance asClassInstance() {
      return null;
    }
  
    /**
     * Return the referent associated with this instance.
     * This is relevent for instances of java.lang.ref.Reference.
     * Returns null if the instance has no referent associated with it.
     */
    public AhatInstance getReferent() {
      // Overridden by AhatClassInstance.
      return null;
    }
  
    /**
     * Returns a list of objects with hard references to this object.
     */
    public List<AhatInstance> getHardReverseReferences() {
      return Arrays.asList(mHardReverseReferences);
    }
  
    /**
     * Returns a list of objects with soft references to this object.
     */
    public List<AhatInstance> getSoftReverseReferences() {
      return Arrays.asList(mSoftReverseReferences);
    }
  
    /**
     * Returns the value of a field of an instance.
     * Returns null if the field value is null, the field couldn't be read, or
     * there are multiple fields with the same name.
     */
    public Value getField(String fieldName) {
      // Overridden by AhatClassInstance.
      return null;
    }
  
    /**
     * Reads a reference field of this instance.
     * Returns null if the field value is null, or if the field couldn't be read.
     */
    public AhatInstance getRefField(String fieldName) {
      // Overridden by AhatClassInstance.
      return null;
    }
  
    /**
     * Assuming inst represents a DexCache object, return the dex location for
     * that dex cache. Returns null if the given instance doesn't represent a
     * DexCache object or the location could not be found.
     * If maxChars is non-negative, the returned location is truncated to
     * maxChars in length.
     */
    public String getDexCacheLocation(int maxChars) {
      return null;
    }
  
    /**
     * Return the bitmap instance associated with this object, or null if there
     * is none. This works for android.graphics.Bitmap instances and their
     * underlying Byte[] instances.
     */
    public AhatInstance getAssociatedBitmapInstance() {
      return null;
    }
  
    /**
     * Read the string value from this instance.
     * Returns null if this object can't be interpreted as a string.
     * The returned string is truncated to maxChars characters.
     * If maxChars is negative, the returned string is not truncated.
     */
    public String asString(int maxChars) {
      // By default instances can't be interpreted as a string. This method is
      // overridden by AhatClassInstance and AhatArrayInstance for those cases
      // when an instance can be interpreted as a string.
      return null;
    }
  
    /**
     * Reads the string value from an hprof Instance.
     * Returns null if the object can't be interpreted as a string.
     */
    public String asString() {
      return asString(-1);
    }
  
    /**
     * Return the bitmap associated with the given instance, if any.
     * This is relevant for instances of android.graphics.Bitmap and byte[].
     * Returns null if there is no bitmap associated with the given instance.
     */
    public BufferedImage asBitmap() {
      return null;
    }
  
    /**
     * Returns a sample path from a GC root to this instance.
     * This instance is included as the last element of the path with an empty
     * field description.
     */
    public List<PathElement> getPathFromGcRoot() {
      List<PathElement> path = new ArrayList<PathElement>();
  
      AhatInstance dom = this;
      for (PathElement elem = new PathElement(this, ""); elem != null;
          elem = getNextPathElementToGcRoot(elem.instance)) {
        if (elem.instance.equals(dom)) {
          elem.isDominator = true;
          dom = dom.getImmediateDominator();
        }
        path.add(elem);
      }
      Collections.reverse(path);
      return path;
    }
  
    /**
     * Returns the next instance to GC root from this object and a string
     * description of which field of that object refers to the given instance.
     * Returns null if the given instance has no next instance to the gc root.
     */
    private static PathElement getNextPathElementToGcRoot(AhatInstance inst) {
      AhatInstance parent = inst.mNextInstanceToGcRoot;
      if (parent == null) {
        return null;
      }
      return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField);
    }
  
    void setNextInstanceToGcRoot(AhatInstance inst, String field) {
      mNextInstanceToGcRoot = inst;
      mNextInstanceToGcRootField = field;
    }
  
    /** Returns a human-readable identifier for this object.
     * For class objects, the string is the class name.
     * For class instances, the string is the class name followed by '@' and the
     * hex id of the instance.
     * For array instances, the string is the array type followed by the size in
     * square brackets, followed by '@' and the hex id of the instance.
     */
    @Override public abstract String toString();
  
    /**
     * Read the byte[] value from an hprof Instance.
     * Returns null if the instance is not a byte array.
     */
    byte[] asByteArray() {
      return null;
    }
  
    public void setBaseline(AhatInstance baseline) {
      mBaseline = baseline;
    }
  
    @Override public AhatInstance getBaseline() {
      return mBaseline;
    }
  
    @Override public boolean isPlaceHolder() {
      return false;
    }
  
    /**
     * Returns a new place holder instance corresponding to this instance.
     */
    AhatInstance newPlaceHolderInstance() {
      return new AhatPlaceHolderInstance(this);
    }
  }
  