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