| /* |
| * 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.ArrayInstance; |
| import com.android.tools.perflib.heap.Instance; |
| import java.nio.charset.StandardCharsets; |
| import java.util.AbstractList; |
| import java.util.List; |
| |
| public class AhatArrayInstance extends AhatInstance { |
| // To save space, we store byte, character, and object arrays directly as |
| // byte, character, and AhatInstance arrays respectively. This is especially |
| // important for large byte arrays, such as bitmaps. All other array types |
| // are stored as an array of objects, though we could potentially save space |
| // by specializing those too. mValues is a list view of the underlying |
| // array. |
| private List<Value> mValues; |
| private byte[] mByteArray; // null if not a byte array. |
| private char[] mCharArray; // null if not a char array. |
| |
| public AhatArrayInstance(long id) { |
| super(id); |
| } |
| |
| @Override void initialize(AhatSnapshot snapshot, Instance inst) { |
| super.initialize(snapshot, inst); |
| |
| ArrayInstance array = (ArrayInstance)inst; |
| switch (array.getArrayType()) { |
| case OBJECT: |
| Object[] objects = array.getValues(); |
| final AhatInstance[] insts = new AhatInstance[objects.length]; |
| for (int i = 0; i < objects.length; i++) { |
| if (objects[i] != null) { |
| Instance ref = (Instance)objects[i]; |
| insts[i] = snapshot.findInstance(ref.getId()); |
| if (ref.getNextInstanceToGcRoot() == inst) { |
| String field = "[" + Integer.toString(i) + "]"; |
| insts[i].setNextInstanceToGcRoot(this, field); |
| } |
| } |
| } |
| mValues = new AbstractList<Value>() { |
| @Override public int size() { |
| return insts.length; |
| } |
| |
| @Override public Value get(int index) { |
| AhatInstance obj = insts[index]; |
| return obj == null ? null : new Value(insts[index]); |
| } |
| }; |
| break; |
| |
| case CHAR: |
| final char[] chars = array.asCharArray(0, array.getLength()); |
| mCharArray = chars; |
| mValues = new AbstractList<Value>() { |
| @Override public int size() { |
| return chars.length; |
| } |
| |
| @Override public Value get(int index) { |
| return new Value(chars[index]); |
| } |
| }; |
| break; |
| |
| case BYTE: |
| final byte[] bytes = array.asRawByteArray(0, array.getLength()); |
| mByteArray = bytes; |
| mValues = new AbstractList<Value>() { |
| @Override public int size() { |
| return bytes.length; |
| } |
| |
| @Override public Value get(int index) { |
| return new Value(bytes[index]); |
| } |
| }; |
| break; |
| |
| default: |
| final Object[] values = array.getValues(); |
| mValues = new AbstractList<Value>() { |
| @Override public int size() { |
| return values.length; |
| } |
| |
| @Override public Value get(int index) { |
| Object obj = values[index]; |
| return obj == null ? null : new Value(obj); |
| } |
| }; |
| break; |
| } |
| } |
| |
| /** |
| * Returns the length of the array. |
| */ |
| public int getLength() { |
| return mValues.size(); |
| } |
| |
| /** |
| * Returns the array's values. |
| */ |
| public List<Value> getValues() { |
| return mValues; |
| } |
| |
| /** |
| * Returns the object at the given index of this array. |
| */ |
| public Value getValue(int index) { |
| return mValues.get(index); |
| } |
| |
| @Override public boolean isArrayInstance() { |
| return true; |
| } |
| |
| @Override public AhatArrayInstance asArrayInstance() { |
| return this; |
| } |
| |
| @Override public String asString(int maxChars) { |
| return asString(0, getLength(), maxChars); |
| } |
| |
| /** |
| * Returns the String value associated with this array. |
| * Only char arrays are considered as having an associated String value. |
| */ |
| String asString(int offset, int count, int maxChars) { |
| if (mCharArray == null) { |
| return null; |
| } |
| |
| if (count == 0) { |
| return ""; |
| } |
| int numChars = mCharArray.length; |
| if (0 <= maxChars && maxChars < count) { |
| count = maxChars; |
| } |
| |
| int end = offset + count - 1; |
| if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) { |
| return new String(mCharArray, offset, count); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the ascii String value associated with this array. |
| * Only byte arrays are considered as having an associated ascii String value. |
| */ |
| String asAsciiString(int offset, int count, int maxChars) { |
| if (mByteArray == null) { |
| return null; |
| } |
| |
| if (count == 0) { |
| return ""; |
| } |
| int numChars = mByteArray.length; |
| if (0 <= maxChars && maxChars < count) { |
| count = maxChars; |
| } |
| |
| int end = offset + count - 1; |
| if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) { |
| return new String(mByteArray, offset, count, StandardCharsets.US_ASCII); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the String value associated with this array. Byte arrays are |
| * considered as ascii encoded strings. |
| */ |
| String asMaybeCompressedString(int offset, int count, int maxChars) { |
| String str = asString(offset, count, maxChars); |
| if (str == null) { |
| str = asAsciiString(offset, count, maxChars); |
| } |
| return str; |
| } |
| |
| @Override public AhatInstance getAssociatedBitmapInstance() { |
| if (mByteArray != null) { |
| List<AhatInstance> refs = getHardReverseReferences(); |
| if (refs.size() == 1) { |
| AhatInstance ref = refs.get(0); |
| return ref.getAssociatedBitmapInstance(); |
| } |
| } |
| return null; |
| } |
| |
| @Override public String toString() { |
| String className = getClassName(); |
| if (className.endsWith("[]")) { |
| className = className.substring(0, className.length() - 2); |
| } |
| return String.format("%s[%d]@%08x", className, mValues.size(), getId()); |
| } |
| |
| byte[] asByteArray() { |
| return mByteArray; |
| } |
| } |