InstanceUtils.java revision b357730dd691e608f8a3d155330ab3763ce912cf
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.ahat;
18
19import com.android.tools.perflib.heap.ArrayInstance;
20import com.android.tools.perflib.heap.ClassInstance;
21import com.android.tools.perflib.heap.ClassObj;
22import com.android.tools.perflib.heap.Instance;
23import com.android.tools.perflib.heap.Type;
24import java.awt.image.BufferedImage;
25
26/**
27 * Utilities for extracting information from hprof instances.
28 */
29class InstanceUtils {
30  /**
31   * Returns true if the given instance is an instance of a class with the
32   * given name.
33   */
34  public static boolean isInstanceOfClass(Instance inst, String className) {
35    ClassObj cls = (inst == null) ? null : inst.getClassObj();
36    return (cls != null && className.equals(cls.getClassName()));
37  }
38
39  /**
40   * Read the byte[] value from an hprof Instance.
41   * Returns null if the instance is not a byte array.
42   */
43  private static byte[] asByteArray(Instance inst) {
44    if (! (inst instanceof ArrayInstance)) {
45      return null;
46    }
47
48    ArrayInstance array = (ArrayInstance)inst;
49    if (array.getArrayType() != Type.BYTE) {
50      return null;
51    }
52
53    Object[] objs = array.getValues();
54    byte[] bytes = new byte[objs.length];
55    for (int i = 0; i < objs.length; i++) {
56      Byte b = (Byte)objs[i];
57      bytes[i] = b.byteValue();
58    }
59    return bytes;
60  }
61
62
63  /**
64   * Read the string value from an hprof Instance.
65   * Returns null if the object can't be interpreted as a string.
66   */
67  public static String asString(Instance inst) {
68    return asString(inst, -1);
69  }
70
71  /**
72   * Read the string value from an hprof Instance.
73   * Returns null if the object can't be interpreted as a string.
74   * The returned string is truncated to maxChars characters.
75   * If maxChars is negative, the returned string is not truncated.
76   */
77  public static String asString(Instance inst, int maxChars) {
78    if (!isInstanceOfClass(inst, "java.lang.String")) {
79      return null;
80    }
81
82    Object value = getField(inst, "value");
83    if (!(value instanceof ArrayInstance)) {
84      return null;
85    }
86
87    ArrayInstance chars = (ArrayInstance) value;
88    if (chars.getArrayType() != Type.CHAR) {
89      return null;
90    }
91
92    // TODO: When perflib provides a better way to get the length of the
93    // array, we should use that here.
94    int numChars = chars.getValues().length;
95    int count = getIntField(inst, "count", numChars);
96    if (count == 0) {
97      return "";
98    }
99    if (0 <= maxChars && maxChars < count) {
100      count = maxChars;
101    }
102
103    int offset = getIntField(inst, "offset", 0);
104    int end = offset + count - 1;
105    if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
106      return new String(chars.asCharArray(offset, count));
107    }
108    return null;
109  }
110
111  /**
112   * Read the bitmap data for the given android.graphics.Bitmap object.
113   * Returns null if the object isn't for android.graphics.Bitmap or the
114   * bitmap data couldn't be read.
115   */
116  public static BufferedImage asBitmap(Instance inst) {
117    if (!isInstanceOfClass(inst, "android.graphics.Bitmap")) {
118      return null;
119    }
120
121    Integer width = getIntField(inst, "mWidth");
122    if (width == null) {
123      return null;
124    }
125
126    Integer height = getIntField(inst, "mHeight");
127    if (height == null) {
128      return null;
129    }
130
131    byte[] buffer = getByteArrayField(inst, "mBuffer");
132    if (buffer == null) {
133      return null;
134    }
135
136    // Convert the raw data to an image
137    // Convert BGRA to ABGR
138    int[] abgr = new int[height * width];
139    for (int i = 0; i < abgr.length; i++) {
140      abgr[i] = (
141          (((int)buffer[i * 4 + 3] & 0xFF) << 24) +
142          (((int)buffer[i * 4 + 0] & 0xFF) << 16) +
143          (((int)buffer[i * 4 + 1] & 0xFF) << 8) +
144          ((int)buffer[i * 4 + 2] & 0xFF));
145    }
146
147    BufferedImage bitmap = new BufferedImage(
148        width, height, BufferedImage.TYPE_4BYTE_ABGR);
149    bitmap.setRGB(0, 0, width, height, abgr, 0, width);
150    return bitmap;
151  }
152
153  /**
154   * Read a field of an instance.
155   * Returns null if the field value is null or if the field couldn't be read.
156   */
157  public static Object getField(Instance inst, String fieldName) {
158    if (!(inst instanceof ClassInstance)) {
159      return null;
160    }
161
162    ClassInstance clsinst = (ClassInstance) inst;
163    Object value = null;
164    int count = 0;
165    for (ClassInstance.FieldValue field : clsinst.getValues()) {
166      if (fieldName.equals(field.getField().getName())) {
167        value = field.getValue();
168        count++;
169      }
170    }
171    return count == 1 ? value : null;
172  }
173
174  /**
175   * Read a reference field of an instance.
176   * Returns null if the field value is null, or if the field couldn't be read.
177   */
178  private static Instance getRefField(Instance inst, String fieldName) {
179    Object value = getField(inst, fieldName);
180    if (!(value instanceof Instance)) {
181      return null;
182    }
183    return (Instance)value;
184  }
185
186  /**
187   * Read an int field of an instance.
188   * The field is assumed to be an int type.
189   * Returns null if the field value is not an int or could not be read.
190   */
191  private static Integer getIntField(Instance inst, String fieldName) {
192    Object value = getField(inst, fieldName);
193    if (!(value instanceof Integer)) {
194      return null;
195    }
196    return (Integer)value;
197  }
198
199  /**
200   * Read an int field of an instance, returning a default value if the field
201   * was not an int or could not be read.
202   */
203  private static int getIntField(Instance inst, String fieldName, int def) {
204    Integer value = getIntField(inst, fieldName);
205    return value == null ? def : value;
206  }
207
208  /**
209   * Read the given field from the given instance.
210   * The field is assumed to be a byte[] field.
211   * Returns null if the field value is null, not a byte[] or could not be read.
212   */
213  private static byte[] getByteArrayField(Instance inst, String fieldName) {
214    Object value = getField(inst, fieldName);
215    if (!(value instanceof Instance)) {
216      return null;
217    }
218    return asByteArray((Instance)value);
219  }
220
221  // Return the bitmap instance associated with this object, or null if there
222  // is none. This works for android.graphics.Bitmap instances and their
223  // underlying Byte[] instances.
224  public static Instance getAssociatedBitmapInstance(Instance inst) {
225    ClassObj cls = inst.getClassObj();
226    if (cls == null) {
227      return null;
228    }
229
230    if ("android.graphics.Bitmap".equals(cls.getClassName())) {
231      return inst;
232    }
233
234    if (inst instanceof ArrayInstance) {
235      ArrayInstance array = (ArrayInstance)inst;
236      if (array.getArrayType() == Type.BYTE && inst.getHardReferences().size() == 1) {
237        Instance ref = inst.getHardReferences().get(0);
238        ClassObj clsref = ref.getClassObj();
239        if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
240          return ref;
241        }
242      }
243    }
244    return null;
245  }
246
247  private static boolean isJavaLangRefReference(Instance inst) {
248    ClassObj cls = (inst == null) ? null : inst.getClassObj();
249    while (cls != null) {
250      if ("java.lang.ref.Reference".equals(cls.getClassName())) {
251        return true;
252      }
253      cls = cls.getSuperClassObj();
254    }
255    return false;
256  }
257
258  public static Instance getReferent(Instance inst) {
259    if (isJavaLangRefReference(inst)) {
260      return getRefField(inst, "referent");
261    }
262    return null;
263  }
264
265  /**
266   * Assuming inst represents a DexCache object, return the dex location for
267   * that dex cache. Returns null if the given instance doesn't represent a
268   * DexCache object or the location could not be found.
269   * If maxChars is non-negative, the returned location is truncated to
270   * maxChars in length.
271   */
272  public static String getDexCacheLocation(Instance inst, int maxChars) {
273    if (isInstanceOfClass(inst, "java.lang.DexCache")) {
274      Instance location = getRefField(inst, "location");
275      if (location != null) {
276        return asString(location, maxChars);
277      }
278    }
279    return null;
280  }
281}
282