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;
  
  import com.android.ahat.heapdump.AhatArrayInstance;
  import com.android.ahat.heapdump.AhatClassInstance;
  import com.android.ahat.heapdump.AhatClassObj;
  import com.android.ahat.heapdump.AhatHeap;
  import com.android.ahat.heapdump.AhatInstance;
  import com.android.ahat.heapdump.AhatSnapshot;
  import com.android.ahat.heapdump.Diff;
  import com.android.ahat.heapdump.FieldValue;
  import com.android.ahat.heapdump.PathElement;
  import com.android.ahat.heapdump.Site;
  import com.android.ahat.heapdump.Value;
  import java.io.IOException;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  import java.util.Objects;
  
  
  class ObjectHandler implements AhatHandler {
  
    private static final String ARRAY_ELEMENTS_ID = "elements";
    private static final String DOMINATOR_PATH_ID = "dompath";
    private static final String ALLOCATION_SITE_ID = "frames";
    private static final String DOMINATED_OBJECTS_ID = "dominated";
    private static final String INSTANCE_FIELDS_ID = "ifields";
    private static final String STATIC_FIELDS_ID = "sfields";
    private static final String HARD_REFS_ID = "refs";
    private static final String SOFT_REFS_ID = "srefs";
  
    private AhatSnapshot mSnapshot;
  
    public ObjectHandler(AhatSnapshot snapshot) {
      mSnapshot = snapshot;
    }
  
    @Override
    public void handle(Doc doc, Query query) throws IOException {
      long id = query.getLong("id", 0);
      AhatInstance inst = mSnapshot.findInstance(id);
      if (inst == null) {
        doc.println(DocString.format("No object with id %08xl", id));
        return;
      }
      AhatInstance base = inst.getBaseline();
  
      doc.title("Object %08x", inst.getId());
      doc.big(Summarizer.summarize(inst));
  
      printAllocationSite(doc, query, inst);
      printGcRootPath(doc, query, inst);
  
      doc.section("Object Info");
      AhatClassObj cls = inst.getClassObj();
      doc.descriptions();
      doc.description(DocString.text("Class"), Summarizer.summarize(cls));
  
      DocString sizeDescription = DocString.format("%,14d ", inst.getSize());
      sizeDescription.appendDelta(false, base.isPlaceHolder(),
          inst.getSize(), base.getSize());
      doc.description(DocString.text("Size"), sizeDescription);
  
      DocString rsizeDescription = DocString.format("%,14d ", inst.getTotalRetainedSize());
      rsizeDescription.appendDelta(false, base.isPlaceHolder(),
          inst.getTotalRetainedSize(), base.getTotalRetainedSize());
      doc.description(DocString.text("Retained Size"), rsizeDescription);
  
      doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
  
      Collection<String> rootTypes = inst.getRootTypes();
      if (rootTypes != null) {
        DocString types = new DocString();
        String comma = "";
        for (String type : rootTypes) {
          types.append(comma);
          types.append(type);
          comma = ", ";
        }
        doc.description(DocString.text("Root Types"), types);
      }
  
      doc.end();
  
      printBitmap(doc, inst);
      if (inst.isClassInstance()) {
        printClassInstanceFields(doc, query, inst.asClassInstance());
      } else if (inst.isArrayInstance()) {
        printArrayElements(doc, query, inst.asArrayInstance());
      } else if (inst.isClassObj()) {
        printClassInfo(doc, query, inst.asClassObj());
      }
      printReferences(doc, query, inst);
      printDominatedObjects(doc, query, inst);
    }
  
    private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
      doc.section("Fields");
      AhatInstance base = inst.getBaseline();
      List<FieldValue> fields = inst.getInstanceFields();
      if (!base.isPlaceHolder()) {
        Diff.fields(fields, base.asClassInstance().getInstanceFields());
      }
      SubsetSelector<FieldValue> selector = new SubsetSelector(query, INSTANCE_FIELDS_ID, fields);
      printFields(doc, inst != base && !base.isPlaceHolder(), selector.selected());
      selector.render(doc);
    }
  
    private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
      doc.section("Array Elements");
      AhatInstance base = array.getBaseline();
      boolean diff = array.getBaseline() != array && !base.isPlaceHolder();
      doc.table(
          new Column("Index", Column.Align.RIGHT),
          new Column("Value"),
          new Column("Δ", Column.Align.LEFT, diff));
  
      List<Value> elements = array.getValues();
      SubsetSelector<Value> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
      int i = 0;
      for (Value current : selector.selected()) {
        DocString delta = new DocString();
        if (diff) {
          Value previous = Value.getBaseline(base.asArrayInstance().getValue(i));
          if (!Objects.equals(current, previous)) {
            delta.append("was ");
            delta.append(Summarizer.summarize(previous));
          }
        }
        doc.row(DocString.format("%d", i), Summarizer.summarize(current), delta);
        i++;
      }
      doc.end();
      selector.render(doc);
    }
  
    private static void printFields(Doc doc, boolean diff, List<FieldValue> fields) {
      doc.table(
          new Column("Type"),
          new Column("Name"),
          new Column("Value"),
          new Column("Δ", Column.Align.LEFT, diff));
  
      for (FieldValue field : fields) {
        Value current = field.getValue();
        DocString value;
        if (field.isPlaceHolder()) {
          value = DocString.removed("del");
        } else {
          value = Summarizer.summarize(current);
        }
  
        DocString delta = new DocString();
        FieldValue basefield = field.getBaseline();
        if (basefield.isPlaceHolder()) {
          delta.append(DocString.added("new"));
        } else {
          Value previous = Value.getBaseline(basefield.getValue());
          if (!Objects.equals(current, previous)) {
            delta.append("was ");
            delta.append(Summarizer.summarize(previous));
          }
        }
        doc.row(DocString.text(field.getType()), DocString.text(field.getName()), value, delta);
      }
      doc.end();
    }
  
    private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
      doc.section("Class Info");
      doc.descriptions();
      doc.description(DocString.text("Super Class"),
          Summarizer.summarize(clsobj.getSuperClassObj()));
      doc.description(DocString.text("Class Loader"),
          Summarizer.summarize(clsobj.getClassLoader()));
      doc.end();
  
      doc.section("Static Fields");
      AhatInstance base = clsobj.getBaseline();
      List<FieldValue> fields = clsobj.getStaticFieldValues();
      if (!base.isPlaceHolder()) {
        Diff.fields(fields, base.asClassObj().getStaticFieldValues());
      }
      SubsetSelector<FieldValue> selector = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
      printFields(doc, clsobj != base && !base.isPlaceHolder(), selector.selected());
      selector.render(doc);
    }
  
    private static void printReferences(Doc doc, Query query, AhatInstance inst) {
      doc.section("Objects with References to this Object");
      if (inst.getHardReverseReferences().isEmpty()) {
        doc.println(DocString.text("(none)"));
      } else {
        doc.table(new Column("Object"));
        List<AhatInstance> references = inst.getHardReverseReferences();
        SubsetSelector<AhatInstance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
        for (AhatInstance ref : selector.selected()) {
          doc.row(Summarizer.summarize(ref));
        }
        doc.end();
        selector.render(doc);
      }
  
      if (!inst.getSoftReverseReferences().isEmpty()) {
        doc.section("Objects with Soft References to this Object");
        doc.table(new Column("Object"));
        List<AhatInstance> references = inst.getSoftReverseReferences();
        SubsetSelector<AhatInstance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
        for (AhatInstance ref : selector.selected()) {
          doc.row(Summarizer.summarize(ref));
        }
        doc.end();
        selector.render(doc);
      }
    }
  
    private void printAllocationSite(Doc doc, Query query, AhatInstance inst) {
      doc.section("Allocation Site");
      Site site = inst.getSite();
      SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
    }
  
    // Draw the bitmap corresponding to this instance if there is one.
    private static void printBitmap(Doc doc, AhatInstance inst) {
      AhatInstance bitmap = inst.getAssociatedBitmapInstance();
      if (bitmap != null) {
        doc.section("Bitmap Image");
        doc.println(DocString.image(
              DocString.formattedUri("bitmap?id=%d", bitmap.getId()), "bitmap image"));
      }
    }
  
    private void printGcRootPath(Doc doc, Query query, AhatInstance inst) {
      doc.section("Sample Path from GC Root");
      List<PathElement> path = inst.getPathFromGcRoot();
  
      // Add a dummy PathElement as a marker for the root.
      final PathElement root = new PathElement(null, null);
      path.add(0, root);
  
      HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() {
        public String getHeapsDescription() {
          return "Bytes Retained by Heap (Dominators Only)";
        }
  
        public long getSize(PathElement element, AhatHeap heap) {
          if (element == root) {
            return heap.getSize();
          }
          if (element.isDominator) {
            return element.instance.getRetainedSize(heap);
          }
          return 0;
        }
  
        public List<HeapTable.ValueConfig<PathElement>> getValueConfigs() {
          HeapTable.ValueConfig<PathElement> value = new HeapTable.ValueConfig<PathElement>() {
            public String getDescription() {
              return "Path Element";
            }
  
            public DocString render(PathElement element) {
              if (element == root) {
                return DocString.link(DocString.uri("rooted"), DocString.text("ROOT"));
              } else {
                DocString label = DocString.text("→ ");
                label.append(Summarizer.summarize(element.instance));
                label.append(element.field);
                return label;
              }
            }
          };
          return Collections.singletonList(value);
        }
      };
      HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path);
    }
  
    public void printDominatedObjects(Doc doc, Query query, AhatInstance inst) {
      doc.section("Immediately Dominated Objects");
      List<AhatInstance> instances = inst.getDominated();
      if (instances != null) {
        DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
      } else {
        doc.println(DocString.text("(none)"));
      }
    }
  }
  
  