/*
* 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;
}
}