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.ClassInstance;
  import com.android.tools.perflib.heap.Instance;
  import java.awt.image.BufferedImage;
  import java.util.Arrays;
  import java.util.List;
  
  public class AhatClassInstance extends AhatInstance {
    private FieldValue[] mFieldValues;
  
    public AhatClassInstance(long id) {
      super(id);
    }
  
    @Override void initialize(AhatSnapshot snapshot, Instance inst) {
      super.initialize(snapshot, inst);
  
      ClassInstance classInst = (ClassInstance)inst;
      List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
      mFieldValues = new FieldValue[fieldValues.size()];
      for (int i = 0; i < mFieldValues.length; i++) {
        ClassInstance.FieldValue field = fieldValues.get(i);
        String name = field.getField().getName();
        String type = field.getField().getType().toString();
        Value value = snapshot.getValue(field.getValue());
  
        mFieldValues[i] = new FieldValue(name, type, value);
  
        if (field.getValue() instanceof Instance) {
          Instance ref = (Instance)field.getValue();
          if (ref.getNextInstanceToGcRoot() == inst) {
            value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
          }
        }
      }
    }
  
    @Override public Value getField(String fieldName) {
      for (FieldValue field : mFieldValues) {
        if (fieldName.equals(field.getName())) {
          return field.getValue();
        }
      }
      return null;
    }
  
    @Override public AhatInstance getRefField(String fieldName) {
      Value value = getField(fieldName);
      return value == null ? null : value.asAhatInstance();
    }
  
    /**
     * Read an int field of an instance.
     * The field is assumed to be an int type.
     * Returns <code>def</code> if the field value is not an int or could not be
     * read.
     */
    private Integer getIntField(String fieldName, Integer def) {
      Value value = getField(fieldName);
      if (value == null || !value.isInteger()) {
        return def;
      }
      return value.asInteger();
    }
  
    /**
     * Read a long field of this instance.
     * The field is assumed to be a long type.
     * Returns <code>def</code> if the field value is not an long or could not
     * be read.
     */
    private Long getLongField(String fieldName, Long def) {
      Value value = getField(fieldName);
      if (value == null || !value.isLong()) {
        return def;
      }
      return value.asLong();
    }
  
    /**
     * Returns the list of class instance fields for this instance.
     */
    public List<FieldValue> getInstanceFields() {
      return Arrays.asList(mFieldValues);
    }
  
    /**
     * Returns true if this is an instance of a class with the given name.
     */
    private boolean isInstanceOfClass(String className) {
      AhatClassObj cls = getClassObj();
      while (cls != null) {
        if (className.equals(cls.getName())) {
          return true;
        }
        cls = cls.getSuperClassObj();
      }
      return false;
    }
  
    @Override public String asString(int maxChars) {
      if (!isInstanceOfClass("java.lang.String")) {
        return null;
      }
  
      Value value = getField("value");
      if (!value.isAhatInstance()) {
        return null;
      }
  
      AhatInstance inst = value.asAhatInstance();
      if (inst.isArrayInstance()) {
        AhatArrayInstance chars = inst.asArrayInstance();
        int numChars = chars.getLength();
        int count = getIntField("count", numChars);
        int offset = getIntField("offset", 0);
        return chars.asMaybeCompressedString(offset, count, maxChars);
      }
      return null;
    }
  
    @Override public AhatInstance getReferent() {
      if (isInstanceOfClass("java.lang.ref.Reference")) {
        return getRefField("referent");
      }
      return null;
    }
  
    @Override public String getDexCacheLocation(int maxChars) {
      if (isInstanceOfClass("java.lang.DexCache")) {
        AhatInstance location = getRefField("location");
        if (location != null) {
          return location.asString(maxChars);
        }
      }
      return null;
    }
  
    @Override public AhatInstance getAssociatedBitmapInstance() {
      if (isInstanceOfClass("android.graphics.Bitmap")) {
        return this;
      }
      return null;
    }
  
    @Override public boolean isClassInstance() {
      return true;
    }
  
    @Override public AhatClassInstance asClassInstance() {
      return this;
    }
  
    @Override public String toString() {
      return String.format("%s@%08x", getClassName(), getId());
    }
  
    /**
     * Read the given field from the given instance.
     * The field is assumed to be a byte[] field.
     * Returns null if the field value is null, not a byte[] or could not be read.
     */
    private byte[] getByteArrayField(String fieldName) {
      Value value = getField(fieldName);
      if (!value.isAhatInstance()) {
        return null;
      }
      return value.asAhatInstance().asByteArray();
    }
  
    public BufferedImage asBitmap() {
      if (!isInstanceOfClass("android.graphics.Bitmap")) {
        return null;
      }
  
      Integer width = getIntField("mWidth", null);
      if (width == null) {
        return null;
      }
  
      Integer height = getIntField("mHeight", null);
      if (height == null) {
        return null;
      }
  
      byte[] buffer = getByteArrayField("mBuffer");
      if (buffer == null) {
        return null;
      }
  
      // Convert the raw data to an image
      // Convert BGRA to ABGR
      int[] abgr = new int[height * width];
      for (int i = 0; i < abgr.length; i++) {
        abgr[i] = (
            (((int) buffer[i * 4 + 3] & 0xFF) << 24)
            + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
            + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
            + ((int) buffer[i * 4 + 2] & 0xFF));
      }
  
      BufferedImage bitmap = new BufferedImage(
          width, height, BufferedImage.TYPE_4BYTE_ABGR);
      bitmap.setRGB(0, 0, width, height, abgr, 0, width);
      return bitmap;
    }
  }
  