content: /*
   * 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;
    }
  }
  