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
17package com.android.ahat.heapdump;
18
19import com.android.tools.perflib.heap.ArrayInstance;
20import com.android.tools.perflib.heap.Instance;
21import java.nio.charset.StandardCharsets;
22import java.util.AbstractList;
23import java.util.List;
24
25public class AhatArrayInstance extends AhatInstance {
26  // To save space, we store byte, character, and object arrays directly as
27  // byte, character, and AhatInstance arrays respectively. This is especially
28  // important for large byte arrays, such as bitmaps. All other array types
29  // are stored as an array of objects, though we could potentially save space
30  // by specializing those too. mValues is a list view of the underlying
31  // array.
32  private List<Value> mValues;
33  private byte[] mByteArray;    // null if not a byte array.
34  private char[] mCharArray;    // null if not a char array.
35
36  public AhatArrayInstance(long id) {
37    super(id);
38  }
39
40  @Override void initialize(AhatSnapshot snapshot, Instance inst) {
41    super.initialize(snapshot, inst);
42
43    ArrayInstance array = (ArrayInstance)inst;
44    switch (array.getArrayType()) {
45      case OBJECT:
46        Object[] objects = array.getValues();
47        final AhatInstance[] insts = new AhatInstance[objects.length];
48        for (int i = 0; i < objects.length; i++) {
49          if (objects[i] != null) {
50            Instance ref = (Instance)objects[i];
51            insts[i] = snapshot.findInstance(ref.getId());
52            if (ref.getNextInstanceToGcRoot() == inst) {
53              String field = "[" + Integer.toString(i) + "]";
54              insts[i].setNextInstanceToGcRoot(this, field);
55            }
56          }
57        }
58        mValues = new AbstractList<Value>() {
59          @Override public int size() {
60            return insts.length;
61          }
62
63          @Override public Value get(int index) {
64            AhatInstance obj = insts[index];
65            return obj == null ? null : new Value(insts[index]);
66          }
67        };
68        break;
69
70      case CHAR:
71        final char[] chars = array.asCharArray(0, array.getLength());
72        mCharArray = chars;
73        mValues = new AbstractList<Value>() {
74          @Override public int size() {
75            return chars.length;
76          }
77
78          @Override public Value get(int index) {
79            return new Value(chars[index]);
80          }
81        };
82        break;
83
84      case BYTE:
85        final byte[] bytes = array.asRawByteArray(0, array.getLength());
86        mByteArray = bytes;
87        mValues = new AbstractList<Value>() {
88          @Override public int size() {
89            return bytes.length;
90          }
91
92          @Override public Value get(int index) {
93            return new Value(bytes[index]);
94          }
95        };
96        break;
97
98      default:
99        final Object[] values = array.getValues();
100        mValues = new AbstractList<Value>() {
101          @Override public int size() {
102            return values.length;
103          }
104
105          @Override public Value get(int index) {
106            Object obj = values[index];
107            return obj == null ? null : new Value(obj);
108          }
109        };
110        break;
111    }
112  }
113
114  /**
115   * Returns the length of the array.
116   */
117  public int getLength() {
118    return mValues.size();
119  }
120
121  /**
122   * Returns the array's values.
123   */
124  public List<Value> getValues() {
125    return mValues;
126  }
127
128  /**
129   * Returns the object at the given index of this array.
130   */
131  public Value getValue(int index) {
132    return mValues.get(index);
133  }
134
135  @Override public boolean isArrayInstance() {
136    return true;
137  }
138
139  @Override public AhatArrayInstance asArrayInstance() {
140    return this;
141  }
142
143  @Override public String asString(int maxChars) {
144    return asString(0, getLength(), maxChars);
145  }
146
147  /**
148   * Returns the String value associated with this array.
149   * Only char arrays are considered as having an associated String value.
150   */
151  String asString(int offset, int count, int maxChars) {
152    if (mCharArray == null) {
153      return null;
154    }
155
156    if (count == 0) {
157      return "";
158    }
159    int numChars = mCharArray.length;
160    if (0 <= maxChars && maxChars < count) {
161      count = maxChars;
162    }
163
164    int end = offset + count - 1;
165    if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
166      return new String(mCharArray, offset, count);
167    }
168    return null;
169  }
170
171  /**
172   * Returns the ascii String value associated with this array.
173   * Only byte arrays are considered as having an associated ascii String value.
174   */
175  String asAsciiString(int offset, int count, int maxChars) {
176    if (mByteArray == null) {
177      return null;
178    }
179
180    if (count == 0) {
181      return "";
182    }
183    int numChars = mByteArray.length;
184    if (0 <= maxChars && maxChars < count) {
185      count = maxChars;
186    }
187
188    int end = offset + count - 1;
189    if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
190      return new String(mByteArray, offset, count, StandardCharsets.US_ASCII);
191    }
192    return null;
193  }
194
195  /**
196   * Returns the String value associated with this array. Byte arrays are
197   * considered as ascii encoded strings.
198   */
199  String asMaybeCompressedString(int offset, int count, int maxChars) {
200    String str = asString(offset, count, maxChars);
201    if (str == null) {
202      str = asAsciiString(offset, count, maxChars);
203    }
204    return str;
205  }
206
207  @Override public AhatInstance getAssociatedBitmapInstance() {
208    if (mByteArray != null) {
209      List<AhatInstance> refs = getHardReverseReferences();
210      if (refs.size() == 1) {
211        AhatInstance ref = refs.get(0);
212        return ref.getAssociatedBitmapInstance();
213      }
214    }
215    return null;
216  }
217
218  @Override public String toString() {
219    String className = getClassName();
220    if (className.endsWith("[]")) {
221      className = className.substring(0, className.length() - 2);
222    }
223    return String.format("%s[%d]@%08x", className, mValues.size(), getId());
224  }
225
226  byte[] asByteArray() {
227    return mByteArray;
228  }
229}
230