content: /*
   * Copyright (C) 2011 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.taobao.atlas.dex;
  
  import com.taobao.atlas.dex.util.ByteInput;
  
  /**
   * Pull parser for encoded values.
   */
  public final class EncodedValueReader {
      public static final int ENCODED_BYTE = 0x00;
      public static final int ENCODED_SHORT = 0x02;
      public static final int ENCODED_CHAR = 0x03;
      public static final int ENCODED_INT = 0x04;
      public static final int ENCODED_LONG = 0x06;
      public static final int ENCODED_FLOAT = 0x10;
      public static final int ENCODED_DOUBLE = 0x11;
      public static final int ENCODED_STRING = 0x17;
      public static final int ENCODED_TYPE = 0x18;
      public static final int ENCODED_FIELD = 0x19;
      public static final int ENCODED_ENUM = 0x1b;
      public static final int ENCODED_METHOD = 0x1a;
      public static final int ENCODED_ARRAY = 0x1c;
      public static final int ENCODED_ANNOTATION = 0x1d;
      public static final int ENCODED_NULL = 0x1e;
      public static final int ENCODED_BOOLEAN = 0x1f;
  
      /** placeholder type if the type is not yet known */
      private static final int MUST_READ = -1;
  
      protected final ByteInput in;
      private int type = MUST_READ;
      private int annotationType;
      private int arg;
  
      public EncodedValueReader(ByteInput in) {
          this.in = in;
      }
  
      public EncodedValueReader(EncodedValue in) {
          this(in.asByteInput());
      }
  
      /**
       * Creates a new encoded value reader whose only value is the specified
       * known type. This is useful for encoded values without a type prefix,
       * such as class_def_item's encoded_array or annotation_item's
       * encoded_annotation.
       */
      public EncodedValueReader(ByteInput in, int knownType) {
          this.in = in;
          this.type = knownType;
      }
  
      public EncodedValueReader(EncodedValue in, int knownType) {
          this(in.asByteInput(), knownType);
      }
  
      /**
       * Returns the type of the next value to read.
       */
      public int peek() {
          if (type == MUST_READ) {
              int argAndType = in.readByte() & 0xff;
              type = argAndType & 0x1f;
              arg = (argAndType & 0xe0) >> 5;
          }
          return type;
      }
  
      /**
       * Begins reading the elements of an array, returning the array's size. The
       * caller must follow up by calling a read method for each element in the
       * array. For example, this reads a byte array: <pre>   {@code
       *   int arraySize = readArray();
       *   for (int i = 0, i < arraySize; i++) {
       *     readByte();
       *   }
       * }</pre>
       */
      public int readArray() {
          checkType(ENCODED_ARRAY);
          type = MUST_READ;
          return Leb128.readUnsignedLeb128(in);
      }
  
      /**
       * Begins reading the fields of an annotation, returning the number of
       * fields. The caller must follow up by making alternating calls to {@link
       * #readAnnotationName()} and another read method. For example, this reads
       * an annotation whose fields are all bytes: <pre>   {@code
       *   int fieldCount = readAnnotation();
       *   int annotationType = getAnnotationType();
       *   for (int i = 0; i < fieldCount; i++) {
       *       readAnnotationName();
       *       readByte();
       *   }
       * }</pre>
       */
      public int readAnnotation() {
          checkType(ENCODED_ANNOTATION);
          type = MUST_READ;
          annotationType = Leb128.readUnsignedLeb128(in);
          return Leb128.readUnsignedLeb128(in);
      }
  
      /**
       * Returns the type of the annotation just returned by {@link
       * #readAnnotation()}. This method's value is undefined unless the most
       * recent call was to {@link #readAnnotation()}.
       */
      public int getAnnotationType() {
          return annotationType;
      }
  
      public int readAnnotationName() {
          return Leb128.readUnsignedLeb128(in);
      }
  
      public byte readByte() {
          checkType(ENCODED_BYTE);
          type = MUST_READ;
          return (byte) EncodedValueCodec.readSignedInt(in, arg);
      }
  
      public short readShort() {
          checkType(ENCODED_SHORT);
          type = MUST_READ;
          return (short) EncodedValueCodec.readSignedInt(in, arg);
      }
  
      public char readChar() {
          checkType(ENCODED_CHAR);
          type = MUST_READ;
          return (char) EncodedValueCodec.readUnsignedInt(in, arg, false);
      }
  
      public int readInt() {
          checkType(ENCODED_INT);
          type = MUST_READ;
          return EncodedValueCodec.readSignedInt(in, arg);
      }
  
      public long readLong() {
          checkType(ENCODED_LONG);
          type = MUST_READ;
          return EncodedValueCodec.readSignedLong(in, arg);
      }
  
      public float readFloat() {
          checkType(ENCODED_FLOAT);
          type = MUST_READ;
          return Float.intBitsToFloat(EncodedValueCodec.readUnsignedInt(in, arg, true));
      }
  
      public double readDouble() {
          checkType(ENCODED_DOUBLE);
          type = MUST_READ;
          return Double.longBitsToDouble(EncodedValueCodec.readUnsignedLong(in, arg, true));
      }
  
      public int readString() {
          checkType(ENCODED_STRING);
          type = MUST_READ;
          return EncodedValueCodec.readUnsignedInt(in, arg, false);
      }
  
      public int readType() {
          checkType(ENCODED_TYPE);
          type = MUST_READ;
          return EncodedValueCodec.readUnsignedInt(in, arg, false);
      }
  
      public int readField() {
          checkType(ENCODED_FIELD);
          type = MUST_READ;
          return EncodedValueCodec.readUnsignedInt(in, arg, false);
      }
  
      public int readEnum() {
          checkType(ENCODED_ENUM);
          type = MUST_READ;
          return EncodedValueCodec.readUnsignedInt(in, arg, false);
      }
  
      public int readMethod() {
          checkType(ENCODED_METHOD);
          type = MUST_READ;
          return EncodedValueCodec.readUnsignedInt(in, arg, false);
      }
  
      public void readNull() {
          checkType(ENCODED_NULL);
          type = MUST_READ;
      }
  
      public boolean readBoolean() {
          checkType(ENCODED_BOOLEAN);
          type = MUST_READ;
          return arg != 0;
      }
  
      /**
       * Skips a single value, including its nested values if it is an array or
       * annotation.
       */
      public void skipValue() {
          switch (peek()) {
          case ENCODED_BYTE:
              readByte();
              break;
          case ENCODED_SHORT:
              readShort();
              break;
          case ENCODED_CHAR:
              readChar();
              break;
          case ENCODED_INT:
              readInt();
              break;
          case ENCODED_LONG:
              readLong();
              break;
          case ENCODED_FLOAT:
              readFloat();
              break;
          case ENCODED_DOUBLE:
              readDouble();
              break;
          case ENCODED_STRING:
              readString();
              break;
          case ENCODED_TYPE:
              readType();
              break;
          case ENCODED_FIELD:
              readField();
              break;
          case ENCODED_ENUM:
              readEnum();
              break;
          case ENCODED_METHOD:
              readMethod();
              break;
          case ENCODED_ARRAY:
              for (int i = 0, size = readArray(); i < size; i++) {
                  skipValue();
              }
              break;
          case ENCODED_ANNOTATION:
              for (int i = 0, size = readAnnotation(); i < size; i++) {
                  readAnnotationName();
                  skipValue();
              }
              break;
          case ENCODED_NULL:
              readNull();
              break;
          case ENCODED_BOOLEAN:
              readBoolean();
              break;
          default:
              throw new DexException2("Unexpected type: " + Integer.toHexString(type));
          }
      }
  
      private void checkType(int expected) {
          if (peek() != expected) {
              throw new IllegalStateException(
                      String.format("Expected %x but was %x", expected, peek()));
          }
      }
  }
  