1 /* 2 * Copyright (C) 2016 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 17 package com.android.ahat.heapdump; 18 19 import java.nio.charset.StandardCharsets; 20 import java.util.AbstractList; 21 import java.util.Collections; 22 import java.util.List; 23 24 /** 25 * An array instance from a parsed heap dump. 26 * It is used for both object and primitive arrays. The class provides methods 27 * for accessing the length and elements of the array in addition to those 28 * methods inherited from {@link AhatInstance}. 29 */ 30 public class AhatArrayInstance extends AhatInstance { 31 // To save space, we store arrays as primitive arrays or AhatInstance arrays 32 // and provide a wrapper over the arrays to expose a list of Values. 33 // This is especially important for large byte arrays, such as bitmaps. 34 // We keep a separate pointer to the underlying array in the case of byte or 35 // char arrays because they are sometimes useful to have. 36 // TODO: Have different subtypes of AhatArrayInstance to avoid the overhead 37 // of these extra pointers and cost in getReferences when the array type is 38 // not relevant? 39 private List<Value> mValues; 40 private byte[] mByteArray; // null if not a byte array. 41 private char[] mCharArray; // null if not a char array. 42 AhatArrayInstance(long id)43 AhatArrayInstance(long id) { 44 super(id); 45 } 46 47 /** 48 * Initialize the array elements for a primitive boolean array. 49 */ initialize(final boolean[] bools)50 void initialize(final boolean[] bools) { 51 mValues = new AbstractList<Value>() { 52 @Override public int size() { 53 return bools.length; 54 } 55 56 @Override public Value get(int index) { 57 return Value.pack(bools[index]); 58 } 59 }; 60 } 61 62 /** 63 * Initialize the array elements for a primitive char array. 64 */ initialize(final char[] chars)65 void initialize(final char[] chars) { 66 mCharArray = chars; 67 mValues = new AbstractList<Value>() { 68 @Override public int size() { 69 return chars.length; 70 } 71 72 @Override public Value get(int index) { 73 return Value.pack(chars[index]); 74 } 75 }; 76 } 77 78 /** 79 * Initialize the array elements for a primitive float array. 80 */ initialize(final float[] floats)81 void initialize(final float[] floats) { 82 mValues = new AbstractList<Value>() { 83 @Override public int size() { 84 return floats.length; 85 } 86 87 @Override public Value get(int index) { 88 return Value.pack(floats[index]); 89 } 90 }; 91 } 92 93 /** 94 * Initialize the array elements for a primitive double array. 95 */ initialize(final double[] doubles)96 void initialize(final double[] doubles) { 97 mValues = new AbstractList<Value>() { 98 @Override public int size() { 99 return doubles.length; 100 } 101 102 @Override public Value get(int index) { 103 return Value.pack(doubles[index]); 104 } 105 }; 106 } 107 108 /** 109 * Initialize the array elements for a primitive byte array. 110 */ initialize(final byte[] bytes)111 void initialize(final byte[] bytes) { 112 mByteArray = bytes; 113 mValues = new AbstractList<Value>() { 114 @Override public int size() { 115 return bytes.length; 116 } 117 118 @Override public Value get(int index) { 119 return Value.pack(bytes[index]); 120 } 121 }; 122 } 123 124 /** 125 * Initialize the array elements for a primitive short array. 126 */ initialize(final short[] shorts)127 void initialize(final short[] shorts) { 128 mValues = new AbstractList<Value>() { 129 @Override public int size() { 130 return shorts.length; 131 } 132 133 @Override public Value get(int index) { 134 return Value.pack(shorts[index]); 135 } 136 }; 137 } 138 139 /** 140 * Initialize the array elements for a primitive int array. 141 */ initialize(final int[] ints)142 void initialize(final int[] ints) { 143 mValues = new AbstractList<Value>() { 144 @Override public int size() { 145 return ints.length; 146 } 147 148 @Override public Value get(int index) { 149 return Value.pack(ints[index]); 150 } 151 }; 152 } 153 154 /** 155 * Initialize the array elements for a primitive long array. 156 */ initialize(final long[] longs)157 void initialize(final long[] longs) { 158 mValues = new AbstractList<Value>() { 159 @Override public int size() { 160 return longs.length; 161 } 162 163 @Override public Value get(int index) { 164 return Value.pack(longs[index]); 165 } 166 }; 167 } 168 169 /** 170 * Initialize the array elements for an instance array. 171 */ initialize(final AhatInstance[] insts)172 void initialize(final AhatInstance[] insts) { 173 mValues = new AbstractList<Value>() { 174 @Override public int size() { 175 return insts.length; 176 } 177 178 @Override public Value get(int index) { 179 return Value.pack(insts[index]); 180 } 181 }; 182 } 183 184 @Override getExtraJavaSize()185 long getExtraJavaSize() { 186 int length = getLength(); 187 if (length == 0) { 188 return 0; 189 } 190 191 return Value.getType(mValues.get(0)).size * getLength(); 192 } 193 194 /** 195 * Returns the number of elements in the array. 196 * 197 * @return number of elements in the array. 198 */ getLength()199 public int getLength() { 200 return mValues.size(); 201 } 202 203 /** 204 * Returns a list of all of the array's elements in order. 205 * The returned list does not support modification. 206 * 207 * @return list of the array's elements. 208 */ getValues()209 public List<Value> getValues() { 210 return mValues; 211 } 212 213 /** 214 * Returns the value at the given index of this array. 215 * 216 * @param index the index of the value to retrieve 217 * @return the value at the given index 218 * @throws IndexOutOfBoundsException if the index is out of range 219 */ getValue(int index)220 public Value getValue(int index) { 221 return mValues.get(index); 222 } 223 224 @Override getReferences()225 Iterable<Reference> getReferences() { 226 // The list of references will be empty if this is a primitive array. 227 List<Reference> refs = Collections.emptyList(); 228 if (!mValues.isEmpty()) { 229 Value first = mValues.get(0); 230 if (first == null || first.isAhatInstance()) { 231 refs = new AbstractList<Reference>() { 232 @Override 233 public int size() { 234 return mValues.size(); 235 } 236 237 @Override 238 public Reference get(int index) { 239 Value value = mValues.get(index); 240 if (value != null) { 241 assert value.isAhatInstance(); 242 String field = "[" + Integer.toString(index) + "]"; 243 return new Reference(AhatArrayInstance.this, field, value.asAhatInstance(), true); 244 } 245 return null; 246 } 247 }; 248 } 249 } 250 return new SkipNullsIterator(refs); 251 } 252 isArrayInstance()253 @Override public boolean isArrayInstance() { 254 return true; 255 } 256 asArrayInstance()257 @Override public AhatArrayInstance asArrayInstance() { 258 return this; 259 } 260 asString(int maxChars)261 @Override public String asString(int maxChars) { 262 return asString(0, getLength(), maxChars); 263 } 264 265 /** 266 * Returns the String value associated with this array. 267 * Only char arrays are considered as having an associated String value. 268 */ asString(int offset, int count, int maxChars)269 String asString(int offset, int count, int maxChars) { 270 if (mCharArray == null) { 271 return null; 272 } 273 274 if (count == 0) { 275 return ""; 276 } 277 int numChars = mCharArray.length; 278 if (0 <= maxChars && maxChars < count) { 279 count = maxChars; 280 } 281 282 int end = offset + count - 1; 283 if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) { 284 return new String(mCharArray, offset, count); 285 } 286 return null; 287 } 288 289 /** 290 * Returns the ascii String value associated with this array. 291 * Only byte arrays are considered as having an associated ascii String value. 292 */ asAsciiString(int offset, int count, int maxChars)293 String asAsciiString(int offset, int count, int maxChars) { 294 if (mByteArray == null) { 295 return null; 296 } 297 298 if (count == 0) { 299 return ""; 300 } 301 int numChars = mByteArray.length; 302 if (0 <= maxChars && maxChars < count) { 303 count = maxChars; 304 } 305 306 int end = offset + count - 1; 307 if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) { 308 return new String(mByteArray, offset, count, StandardCharsets.US_ASCII); 309 } 310 return null; 311 } 312 313 /** 314 * Returns the String value associated with this array. Byte arrays are 315 * considered as ascii encoded strings. 316 */ asMaybeCompressedString(int offset, int count, int maxChars)317 String asMaybeCompressedString(int offset, int count, int maxChars) { 318 String str = asString(offset, count, maxChars); 319 if (str == null) { 320 str = asAsciiString(offset, count, maxChars); 321 } 322 return str; 323 } 324 getAssociatedBitmapInstance()325 @Override public AhatInstance getAssociatedBitmapInstance() { 326 if (mByteArray != null) { 327 List<AhatInstance> refs = getHardReverseReferences(); 328 if (refs.size() == 1) { 329 AhatInstance ref = refs.get(0); 330 return ref.getAssociatedBitmapInstance(); 331 } 332 } 333 return null; 334 } 335 toString()336 @Override public String toString() { 337 String className = getClassName(); 338 if (className.endsWith("[]")) { 339 className = className.substring(0, className.length() - 2); 340 } 341 return String.format("%s[%d]@%08x", className, mValues.size(), getId()); 342 } 343 asByteArray()344 byte[] asByteArray() { 345 return mByteArray; 346 } 347 } 348