content: /*
   * Copyright (C) 2015 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.StackFrame;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  public class Site implements Diffable<Site> {
    // The site that this site was directly called from.
    // mParent is null for the root site.
    private Site mParent;
  
    private String mMethodName;
    private String mSignature;
    private String mFilename;
    private int mLineNumber;
  
    // To identify this site, we pick a stack trace that includes the site.
    // mId is the id of an object allocated at that stack trace, and mDepth
    // is the number of calls between this site and the innermost site of
    // allocation of the object with mId.
    // For the root site, mId is 0 and mDepth is 0.
    private long mId;
    private int mDepth;
  
    // The total size of objects allocated in this site (including child sites),
    // organized by heap index. Heap indices outside the range of mSizesByHeap
    // implicitly have size 0.
    private long[] mSizesByHeap;
  
    // List of child sites.
    private List<Site> mChildren;
  
    // List of all objects allocated in this site (including child sites).
    private List<AhatInstance> mObjects;
    private List<ObjectsInfo> mObjectsInfos;
    private Map<AhatHeap, Map<AhatClassObj, ObjectsInfo>> mObjectsInfoMap;
  
    private Site mBaseline;
  
    public static class ObjectsInfo implements Diffable<ObjectsInfo> {
      public AhatHeap heap;
      public AhatClassObj classObj;   // May be null.
      public long numInstances;
      public long numBytes;
      private ObjectsInfo baseline;
  
      public ObjectsInfo(AhatHeap heap, AhatClassObj classObj, long numInstances, long numBytes) {
        this.heap = heap;
        this.classObj = classObj;
        this.numInstances = numInstances;
        this.numBytes = numBytes;
        this.baseline = this;
      }
  
      /**
       * Returns the name of the class this ObjectsInfo is associated with.
       */
      public String getClassName() {
        return classObj == null ? "???" : classObj.getName();
      }
  
      public void setBaseline(ObjectsInfo baseline) {
        this.baseline = baseline;
      }
  
      @Override public ObjectsInfo getBaseline() {
        return baseline;
      }
  
      @Override public boolean isPlaceHolder() {
        return false;
      }
    }
  
    /**
     * Construct a root site.
     */
    public Site(String name) {
      this(null, name, "", "", 0, 0, 0);
    }
  
    public Site(Site parent, String method, String signature, String file,
        int line, long id, int depth) {
      mParent = parent;
      mMethodName = method;
      mSignature = signature;
      mFilename = file;
      mLineNumber = line;
      mId = id;
      mDepth = depth;
      mSizesByHeap = new long[1];
      mChildren = new ArrayList<Site>();
      mObjects = new ArrayList<AhatInstance>();
      mObjectsInfos = new ArrayList<ObjectsInfo>();
      mObjectsInfoMap = new HashMap<AhatHeap, Map<AhatClassObj, ObjectsInfo>>();
      mBaseline = this;
    }
  
    /**
     * Add an instance to this site.
     * Returns the site at which the instance was allocated.
     * @param frames - The list of frames in the stack trace, starting with the inner-most frame.
     * @param depth - The number of frames remaining before the inner-most frame is reached.
     */
    Site add(StackFrame[] frames, int depth, AhatInstance inst) {
      return add(this, frames, depth, inst);
    }
  
    private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) {
      while (true) {
        site.mObjects.add(inst);
  
        ObjectsInfo info = site.getObjectsInfo(inst.getHeap(), inst.getClassObj());
        if (inst.isReachable()) {
          AhatHeap heap = inst.getHeap();
          if (heap.getIndex() >= site.mSizesByHeap.length) {
            long[] newSizes = new long[heap.getIndex() + 1];
            for (int i = 0; i < site.mSizesByHeap.length; i++) {
              newSizes[i] = site.mSizesByHeap[i];
            }
            site.mSizesByHeap = newSizes;
          }
          site.mSizesByHeap[heap.getIndex()] += inst.getSize();
  
          info.numInstances++;
          info.numBytes += inst.getSize();
        }
  
        if (depth > 0) {
          StackFrame next = frames[depth - 1];
          Site child = null;
          for (int i = 0; i < site.mChildren.size(); i++) {
            Site curr = site.mChildren.get(i);
            if (curr.mLineNumber == next.getLineNumber()
                && curr.mMethodName.equals(next.getMethodName())
                && curr.mSignature.equals(next.getSignature())
                && curr.mFilename.equals(next.getFilename())) {
              child = curr;
              break;
            }
          }
          if (child == null) {
            child = new Site(site, next.getMethodName(), next.getSignature(),
                next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
            site.mChildren.add(child);
          }
          depth = depth - 1;
          site = child;
        } else {
          return site;
        }
      }
    }
  
    // Get the size of a site for a specific heap.
    public long getSize(AhatHeap heap) {
      int index = heap.getIndex();
      return index >= 0 && index < mSizesByHeap.length ? mSizesByHeap[index] : 0;
    }
  
    /**
     * Get the list of objects allocated under this site. Includes objects
     * allocated in children sites.
     */
    public Collection<AhatInstance> getObjects() {
      return mObjects;
    }
  
    /**
     * Returns the ObjectsInfo at this site for the given heap and class
     * objects. Creates a new empty ObjectsInfo if none existed before.
     */
    ObjectsInfo getObjectsInfo(AhatHeap heap, AhatClassObj classObj) {
      Map<AhatClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(heap);
      if (classToObjectsInfo == null) {
        classToObjectsInfo = new HashMap<AhatClassObj, ObjectsInfo>();
        mObjectsInfoMap.put(heap, classToObjectsInfo);
      }
  
      ObjectsInfo info = classToObjectsInfo.get(classObj);
      if (info == null) {
        info = new ObjectsInfo(heap, classObj, 0, 0);
        mObjectsInfos.add(info);
        classToObjectsInfo.put(classObj, info);
      }
      return info;
    }
  
    public List<ObjectsInfo> getObjectsInfos() {
      return mObjectsInfos;
    }
  
    // Get the combined size of the site for all heaps.
    public long getTotalSize() {
      long total = 0;
      for (int i = 0; i < mSizesByHeap.length; i++) {
        total += mSizesByHeap[i];
      }
      return total;
    }
  
    /**
     * Return the site this site was called from.
     * Returns null for the root site.
     */
    public Site getParent() {
      return mParent;
    }
  
    public String getMethodName() {
      return mMethodName;
    }
  
    public String getSignature() {
      return mSignature;
    }
  
    public String getFilename() {
      return mFilename;
    }
  
    public int getLineNumber() {
      return mLineNumber;
    }
  
    /**
     * Returns the id of some object allocated in this site.
     */
    public long getId() {
      return mId;
    }
  
    /**
     * Returns the number of frames between this site and the site where the
     * object with id getId() was allocated.
     */
    public int getDepth() {
      return mDepth;
    }
  
    public List<Site> getChildren() {
      return mChildren;
    }
  
    void setBaseline(Site baseline) {
      mBaseline = baseline;
    }
  
    @Override public Site getBaseline() {
      return mBaseline;
    }
  
    @Override public boolean isPlaceHolder() {
      return false;
    }
  
    /**
     * Adds a place holder instance to this site and all parent sites.
     */
    void addPlaceHolderInstance(AhatInstance placeholder) {
      for (Site site = this; site != null; site = site.mParent) {
        site.mObjects.add(placeholder);
      }
    }
  }
  