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.dexmerge.dx.merge;
  
  import com.taobao.atlas.dex.*;
  import com.taobao.atlas.dex.util.ByteOutput;
  import com.taobao.atlas.dexmerge.dx.util.ByteArrayAnnotatedOutput;
  
  import java.util.HashMap;
  
  import static com.taobao.atlas.dex.EncodedValueReader.*;
  
  /**
   * Maps the index offsets from one dex file to those in another. For example, if
   * you have string #5 in the old dex file, its position in the new dex file is
   * {@code strings[5]}.
   */
  public final class IndexMap {
      private final Dex target;
      public final int[] stringIds;
      public final short[] typeIds;
      public final short[] protoIds;
      public final short[] fieldIds;
      public final short[] methodIds;
      private final HashMap<Integer, Integer> typeListOffsets;
      private final HashMap<Integer, Integer> annotationOffsets;
      private final HashMap<Integer, Integer> annotationSetOffsets;
      private final HashMap<Integer, Integer> annotationSetRefListOffsets;
      private final HashMap<Integer, Integer> annotationDirectoryOffsets;
      private final HashMap<Integer, Integer> staticValuesOffsets;
  
      public IndexMap(Dex target, TableOfContents tableOfContents) {
          this.target = target;
          this.stringIds = new int[tableOfContents.stringIds.size];
          this.typeIds = new short[tableOfContents.typeIds.size];
          this.protoIds = new short[tableOfContents.protoIds.size];
          this.fieldIds = new short[tableOfContents.fieldIds.size];
          this.methodIds = new short[tableOfContents.methodIds.size];
          this.typeListOffsets = new HashMap<Integer, Integer>();
          this.annotationOffsets = new HashMap<Integer, Integer>();
          this.annotationSetOffsets = new HashMap<Integer, Integer>();
          this.annotationSetRefListOffsets = new HashMap<Integer, Integer>();
          this.annotationDirectoryOffsets = new HashMap<Integer, Integer>();
          this.staticValuesOffsets = new HashMap<Integer, Integer>();
  
          /*
           * A type list, annotation set, annotation directory, or static value at
           * offset 0 is always empty. Always map offset 0 to 0.
           */
          this.typeListOffsets.put(0, 0);
          this.annotationSetOffsets.put(0, 0);
          this.annotationDirectoryOffsets.put(0, 0);
          this.staticValuesOffsets.put(0, 0);
      }
  
      public void putTypeListOffset(int oldOffset, int newOffset) {
          if (oldOffset <= 0 || newOffset <= 0) {
              throw new IllegalArgumentException();
          }
          typeListOffsets.put(oldOffset, newOffset);
      }
  
      public void putAnnotationOffset(int oldOffset, int newOffset) {
          if (oldOffset <= 0 || newOffset <= 0) {
              throw new IllegalArgumentException();
          }
          annotationOffsets.put(oldOffset, newOffset);
      }
  
      public void putAnnotationSetOffset(int oldOffset, int newOffset) {
          if (oldOffset <= 0 || newOffset <= 0) {
              throw new IllegalArgumentException();
          }
          annotationSetOffsets.put(oldOffset, newOffset);
      }
  
      public void putAnnotationSetRefListOffset(int oldOffset, int newOffset) {
          if (oldOffset <= 0 || newOffset <= 0) {
              throw new IllegalArgumentException();
          }
          annotationSetRefListOffsets.put(oldOffset, newOffset);
      }
  
      public void putAnnotationDirectoryOffset(int oldOffset, int newOffset) {
          if (oldOffset <= 0 || newOffset <= 0) {
              throw new IllegalArgumentException();
          }
          annotationDirectoryOffsets.put(oldOffset, newOffset);
      }
  
      public void putStaticValuesOffset(int oldOffset, int newOffset) {
          if (oldOffset <= 0 || newOffset <= 0) {
              throw new IllegalArgumentException();
          }
          staticValuesOffsets.put(oldOffset, newOffset);
      }
  
      public int adjustString(int stringIndex) {
          return stringIndex == ClassDef.NO_INDEX ? ClassDef.NO_INDEX : stringIds[stringIndex];
      }
  
      public int adjustType(int typeIndex) {
          return (typeIndex == ClassDef.NO_INDEX) ? ClassDef.NO_INDEX : (typeIds[typeIndex] & 0xffff);
      }
  
      public TypeList adjustTypeList(TypeList typeList) {
          if (typeList == TypeList.EMPTY) {
              return typeList;
          }
          short[] types = typeList.getTypes().clone();
          for (int i = 0; i < types.length; i++) {
              types[i] = (short) adjustType(types[i]);
          }
          return new TypeList(target, types);
      }
  
      public int adjustProto(int protoIndex) {
          return protoIds[protoIndex] & 0xffff;
      }
  
      public int adjustField(int fieldIndex) {
          return fieldIds[fieldIndex] & 0xffff;
      }
  
      public int adjustMethod(int methodIndex) {
          return methodIds[methodIndex] & 0xffff;
      }
  
      public int adjustTypeListOffset(int typeListOffset) {
          return typeListOffsets.get(typeListOffset);
      }
  
      public int adjustAnnotation(int annotationOffset) {
          return annotationOffsets.get(annotationOffset);
      }
  
      public int adjustAnnotationSet(int annotationSetOffset) {
          return annotationSetOffsets.get(annotationSetOffset);
      }
  
      public int adjustAnnotationSetRefList(int annotationSetRefListOffset) {
          return annotationSetRefListOffsets.get(annotationSetRefListOffset);
      }
  
      public int adjustAnnotationDirectory(int annotationDirectoryOffset) {
          return annotationDirectoryOffsets.get(annotationDirectoryOffset);
      }
  
      public int adjustStaticValues(int staticValuesOffset) {
          return staticValuesOffsets.get(staticValuesOffset);
      }
  
      public MethodId adjust(MethodId methodId) {
          return new MethodId(target,
                  adjustType(methodId.getDeclaringClassIndex()),
                  adjustProto(methodId.getProtoIndex()),
                  adjustString(methodId.getNameIndex()));
      }
  
      public FieldId adjust(FieldId fieldId) {
          return new FieldId(target,
                  adjustType(fieldId.getDeclaringClassIndex()),
                  adjustType(fieldId.getTypeIndex()),
                  adjustString(fieldId.getNameIndex()));
  
      }
  
      public ProtoId adjust(ProtoId protoId) {
          return new ProtoId(target,
                  adjustString(protoId.getShortyIndex()),
                  adjustType(protoId.getReturnTypeIndex()),
                  adjustTypeListOffset(protoId.getParametersOffset()));
      }
  
      public ClassDef adjust(ClassDef classDef) {
          return new ClassDef(target, classDef.getOffset(), adjustType(classDef.getTypeIndex()),
                  classDef.getAccessFlags(), adjustType(classDef.getSupertypeIndex()),
                  adjustTypeListOffset(classDef.getInterfacesOffset()), classDef.getSourceFileIndex(),
                  classDef.getAnnotationsOffset(), classDef.getClassDataOffset(),
                  classDef.getStaticValuesOffset());
      }
  
      public SortableType adjust(SortableType sortableType) {
          return new SortableType(sortableType.getDex(),
                  sortableType.getIndexMap(), adjust(sortableType.getClassDef()));
      }
  
      public EncodedValue adjustEncodedValue(EncodedValue encodedValue) {
          ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32);
          new EncodedValueTransformer(out).transform(new EncodedValueReader(encodedValue));
          return new EncodedValue(out.toByteArray());
      }
  
      public EncodedValue adjustEncodedArray(EncodedValue encodedArray) {
          ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32);
          new EncodedValueTransformer(out).transformArray(
                  new EncodedValueReader(encodedArray, ENCODED_ARRAY));
          return new EncodedValue(out.toByteArray());
      }
  
      public Annotation adjust(Annotation annotation) {
          ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32);
          new EncodedValueTransformer(out).transformAnnotation(
                  annotation.getReader());
          return new Annotation(target, annotation.getVisibility(),
                  new EncodedValue(out.toByteArray()));
      }
  
      /**
       * Adjust an encoded value or array.
       */
      private final class EncodedValueTransformer {
          private final ByteOutput out;
  
          public EncodedValueTransformer(ByteOutput out) {
              this.out = out;
          }
  
          public void transform(EncodedValueReader reader) {
              // TODO: extract this into a helper class, EncodedValueWriter
              switch (reader.peek()) {
                  case ENCODED_BYTE:
                      EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_BYTE, reader.readByte());
                      break;
                  case ENCODED_SHORT:
                      EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_SHORT, reader.readShort());
                      break;
                  case ENCODED_INT:
                      EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_INT, reader.readInt());
                      break;
                  case ENCODED_LONG:
                      EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_LONG, reader.readLong());
                      break;
                  case ENCODED_CHAR:
                      EncodedValueCodec.writeUnsignedIntegralValue(out, ENCODED_CHAR, reader.readChar());
                      break;
                  case ENCODED_FLOAT:
                      // Shift value left 32 so that right-zero-extension works.
                      long longBits = ((long) Float.floatToIntBits(reader.readFloat())) << 32;
                      EncodedValueCodec.writeRightZeroExtendedValue(out, ENCODED_FLOAT, longBits);
                      break;
                  case ENCODED_DOUBLE:
                      EncodedValueCodec.writeRightZeroExtendedValue(
                              out, ENCODED_DOUBLE, Double.doubleToLongBits(reader.readDouble()));
                      break;
                  case ENCODED_STRING:
                      EncodedValueCodec.writeUnsignedIntegralValue(
                              out, ENCODED_STRING, adjustString(reader.readString()));
                      break;
                  case ENCODED_TYPE:
                      EncodedValueCodec.writeUnsignedIntegralValue(
                              out, ENCODED_TYPE, adjustType(reader.readType()));
                      break;
                  case ENCODED_FIELD:
                      EncodedValueCodec.writeUnsignedIntegralValue(
                              out, ENCODED_FIELD, adjustField(reader.readField()));
                      break;
                  case ENCODED_ENUM:
                      EncodedValueCodec.writeUnsignedIntegralValue(
                              out, ENCODED_ENUM, adjustField(reader.readEnum()));
                      break;
                  case ENCODED_METHOD:
                      EncodedValueCodec.writeUnsignedIntegralValue(
                              out, ENCODED_METHOD, adjustMethod(reader.readMethod()));
                      break;
                  case ENCODED_ARRAY:
                      writeTypeAndArg(ENCODED_ARRAY, 0);
                      transformArray(reader);
                      break;
                  case ENCODED_ANNOTATION:
                      writeTypeAndArg(ENCODED_ANNOTATION, 0);
                      transformAnnotation(reader);
                      break;
                  case ENCODED_NULL:
                      reader.readNull();
                      writeTypeAndArg(ENCODED_NULL, 0);
                      break;
                  case ENCODED_BOOLEAN:
                      boolean value = reader.readBoolean();
                      writeTypeAndArg(ENCODED_BOOLEAN, value ? 1 : 0);
                      break;
                  default:
                      throw new DexException2("Unexpected type: " + Integer.toHexString(reader.peek()));
              }
          }
  
          private void transformAnnotation(EncodedValueReader reader) {
              int fieldCount = reader.readAnnotation();
              Leb128.writeUnsignedLeb128(out, adjustType(reader.getAnnotationType()));
              Leb128.writeUnsignedLeb128(out, fieldCount);
              for (int i = 0; i < fieldCount; i++) {
                  Leb128.writeUnsignedLeb128(out, adjustString(reader.readAnnotationName()));
                  transform(reader);
              }
          }
  
          private void transformArray(EncodedValueReader reader) {
              int size = reader.readArray();
              Leb128.writeUnsignedLeb128(out, size);
              for (int i = 0; i < size; i++) {
                  transform(reader);
              }
          }
  
          private void writeTypeAndArg(int type, int arg) {
              out.writeByte((arg << 5) | type);
          }
      }
  }