content: package com.taobao.atlas.dexmerge.dx.merge;
  
  /**
   * Created by lilong on 16/10/13.
   */
  /*
   * 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.
   */
  
  import com.taobao.atlas.dex.*;
  
  import java.io.IOException;
  import java.util.*;
  
  /**
   * Combine two dex files into one.
   */
  public final class DexTransform {
  
      private final Dex dex;
      private final IndexMap               indexMap;
      private final WriterSizes            writerSizes;
      private final Dex                    dexOut;
  
      private final Dex.Section            headerOut;
  
      /** All IDs and definitions sections */
      private final Dex.Section            idsDefsOut;
  
      private final Dex.Section            mapListOut;
  
      private final Dex.Section            typeListOut;
  
      private final Dex.Section            classDataOut;
  
      private final Dex.Section            codeOut;
  
      private final Dex.Section            stringDataOut;
  
      private final Dex.Section            debugInfoOut;
  
      private final Dex.Section            encodedArrayOut;
  
      /** annotations directory on a type */
      private final Dex.Section            annotationsDirectoryOut;
  
      /** sets of annotations on a member, parameter or type */
      private final Dex.Section            annotationSetOut;
  
      /** parameter lists */
      private final Dex.Section            annotationSetRefListOut;
  
      /** individual annotations, each containing zero or more fields */
      private final Dex.Section            annotationOut;
  
      private final TableOfContents contentsOut;
  
      private final InstructionTransformer instructionTransformer;
      private TreeMap<String, DexItem> stringValueOffMaps      = new TreeMap<String, DexItem>(); // 字符串的maps,key为value,value为index
      private Map<String, DexItem> methodCodeOffMaps       = new HashMap<String, DexItem>(); // 方法名和字节码的off的对应关系
      private List<Integer> debugInfoRemoveMethodss = new ArrayList<Integer>();
      private Map<Integer,Integer>     removeClassDefs = new HashMap<Integer, Integer>();
      private final boolean onlyShrink;
  
      public DexTransform(Dex dex) throws IOException {
          this(dex, new WriterSizes(dex, true),false);
      }
  
      private DexTransform(Dex dex, WriterSizes writerSizes,boolean onlyShrink) throws IOException{
          this.dex = dex;
          this.writerSizes = writerSizes;
          this.onlyShrink = onlyShrink;
  
          dexOut = new Dex(writerSizes.size());
  
          indexMap = new IndexMap(dexOut, dex.getTableOfContents());
  
          instructionTransformer = new InstructionTransformer();
  
          headerOut = dexOut.appendSection(writerSizes.header, "header");
          idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs");
  
          contentsOut = dexOut.getTableOfContents();
          contentsOut.dataOff = dexOut.getNextSectionStart();
  
          contentsOut.mapList.off = dexOut.getNextSectionStart();
          contentsOut.mapList.size = 1;
          mapListOut = dexOut.appendSection(writerSizes.mapList, "map list");
  
          contentsOut.typeLists.off = dexOut.getNextSectionStart();
          typeListOut = dexOut.appendSection(writerSizes.typeList, "type list");
  
          contentsOut.annotationSetRefLists.off = dexOut.getNextSectionStart();
          annotationSetRefListOut = dexOut.appendSection(writerSizes.annotationsSetRefList, "annotation set ref list");
  
          contentsOut.annotationSets.off = dexOut.getNextSectionStart();
          annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets");
  
          contentsOut.classDatas.off = dexOut.getNextSectionStart();
          classDataOut = dexOut.appendSection(writerSizes.classData, "class data");
  
          contentsOut.codes.off = dexOut.getNextSectionStart();
          codeOut = dexOut.appendSection(writerSizes.code, "code");
  
          contentsOut.stringDatas.off = dexOut.getNextSectionStart();
          stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data");
  
          contentsOut.debugInfos.off = dexOut.getNextSectionStart();
          debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info");
  
          contentsOut.annotations.off = dexOut.getNextSectionStart();
          annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation");
  
          contentsOut.encodedArrays.off = dexOut.getNextSectionStart();
          encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array");
  
          contentsOut.annotationsDirectories.off = dexOut.getNextSectionStart();
          annotationsDirectoryOut = dexOut.appendSection(writerSizes.annotationsDirectory, "annotations directory");
  
          contentsOut.dataSize = dexOut.getNextSectionStart() - contentsOut.dataOff;
          if(!onlyShrink) {
              initIndexMaps();
          }
      }
  
      /**
       * 初始化所有的Maps
       */
      private void initIndexMaps() {
          intStringIndexMaps(dex);
          initCodeMaps(dex);
      }
  
      private void intStringIndexMaps(Dex dex) {
          TableOfContents.Section stringIds = dex.getTableOfContents().stringIds;
          Dex.Section stringDataSection = dex.open(stringIds.off);
          for (int i = 0; i < stringIds.size; i++) {
              int position = stringDataSection.getPosition();
              int off = stringDataSection.readInt();
              String value = null;
              if (off < 0) {// 如果为负数,则表示取下一个dex的值
                  value =  String.valueOf(off);
                  stringValueOffMaps.put(value, new DexItem(off, i, off));
              } else {
                  stringDataSection.getData().position(position);
                  value = stringDataSection.readString();
                  stringValueOffMaps.put(value, new DexItem(off, i, off));
              }
          }
      }
  
      private void initCodeMaps(Dex dex) {
          Iterable<ClassDef> classDefs = dex.classDefs();
          for (ClassDef classDef : classDefs) {
              if(classDef.getClassDataOffset() > 0) {
                  ClassData classData = dex.readClassData(classDef);
                  ClassData.Method[] directMethods = classData.getDirectMethods();
                  ClassData.Method[] virtualMethods = classData.getVirtualMethods();
                  initMethodsMaps(dex, directMethods);
                  initMethodsMaps(dex, virtualMethods);
              }
  
          }
      }
  
      private void initMethodsMaps(Dex dex, ClassData.Method[] methods) {
          for (ClassData.Method method : methods) {
              MethodId methodId = dex.methodIds().get(method.getMethodIndex());
              String className = dex.typeNames().get(methodId.getDeclaringClassIndex());
              String methodName = dex.strings().get(methodId.getNameIndex())
                      + dex.readTypeList(dex.protoIds().get(methodId.getProtoIndex()).getParametersOffset());
              methodCodeOffMaps.put(className + "." + methodName,
                      new DexItem(method.getCodeOffset(), method.getMethodIndex(), method.getCodeOffset()));
          }
      }
  
      private Set<String> getMethodMaps(Dex dex){
          Set<String> methods = new HashSet<String>();
          Iterable<ClassDef> classDefs = dex.classDefs();
          for (ClassDef classDef : classDefs) {
              if(classDef.getClassDataOffset() > 0) {
                  ClassData classData = dex.readClassData(classDef);
                  ClassData.Method[] directMethods = classData.getDirectMethods();
                  ClassData.Method[] virtualMethods = classData.getVirtualMethods();
                  initMethodsMaps(dex, directMethods, methods);
                  initMethodsMaps(dex, virtualMethods, methods);
              }
  
          }
          return methods;
      }
  
      private void initMethodsMaps(Dex dex, ClassData.Method[] methods, Set<String> methodSets) {
          for (ClassData.Method method : methods) {
              MethodId methodId = dex.methodIds().get(method.getMethodIndex());
              String className = dex.typeNames().get(methodId.getDeclaringClassIndex());
              String methodName = dex.strings().get(methodId.getNameIndex())
                      + dex.readTypeList(dex.protoIds().get(methodId.getProtoIndex()).getParametersOffset());
              methodSets.add(className + "." + methodName);
          }
      }
  
      /**
       * 根据基线的dex,去除重复的string值
       *
       * @param baseDex
       * @return
       */
      public DexTransform uninoStringValues(Dex baseDex) {
          TableOfContents.Section stringIds = baseDex.getTableOfContents().stringIds;
          Dex.Section stringSection = baseDex.open(stringIds.off);
          for (int i = 0; i < stringIds.size; i++) {
              String value = stringSection.readString();
              if (stringValueOffMaps.containsKey(value)) {
                  DexItem dexItem = stringValueOffMaps.get(value);
                  dexItem.newOffset = (i + 1) * -1;
                  stringValueOffMaps.put(value, dexItem);
              }
          }
          return this;
      }
  
      public DexTransform markClassDef(Dex baseDex,List<String>classNames) {
          for (ClassDef classDef:dex.classDefs()) {
              int typeId = classDef.getTypeIndex();
              String value = dex.typeNames().get(typeId);
              if (classNames.contains(value)) {
                  for (ClassDef baseClassDef : baseDex.classDefs()) {
                      if (baseDex.typeNames().get(baseClassDef.getTypeIndex()).equals(value)) {
                          removeClassDefs.put(typeId, baseClassDef.getClassDataOffset() * -1);
                      }
                  }
              }
          }
          return this;
      }
  
      /**
       * 移除没有变动的方法的code和debug info信息
       *
       * @param modifyMethods
       * @return
       */
      public DexTransform removeMethodCodes(HashMap<String, ArrayList<String>> modifyMethods) {
          assert null != modifyMethods;
          List<String> fullModifyMethods = new ArrayList<String>();
          for (Map.Entry<String, ArrayList<String>> entry : modifyMethods.entrySet()) {
              String className = entry.getKey();
              for (String modifyMethodName : entry.getValue()) {
                  String fullMehtodName = className + "." + modifyMethodName;
                  fullModifyMethods.add(fullMehtodName);
              }
          }
          for (Map.Entry<String, DexItem> entry : methodCodeOffMaps.entrySet()) {
              String name = entry.getKey();
              if (!fullModifyMethods.contains(name)) {
                  DexItem dexItem = entry.getValue();
                  dexItem.newOffset = 1;
                  methodCodeOffMaps.put(name, dexItem);
              }
          }
  
          return this;
      }
  
      public DexTransform removeMethodCodes(HashMap<String, ArrayList<String>> modifyMethods,Dex baseDex) {
          assert null != modifyMethods;
          List<String> fullModifyMethods = new ArrayList<String>();
          for (Map.Entry<String, ArrayList<String>> entry : modifyMethods.entrySet()) {
              String className = entry.getKey();
              for (String modifyMethodName : entry.getValue()) {
                  String fullMehtodName = className + "." + modifyMethodName;
                  fullModifyMethods.add(fullMehtodName);
              }
          }
          Set<String>  baseMethods =  getMethodMaps(baseDex);
          for (Map.Entry<String, DexItem> entry : methodCodeOffMaps.entrySet()) {
              String name = entry.getKey();
              if (!fullModifyMethods.contains(name) &&  baseMethods.contains(name)) {
                  DexItem dexItem = entry.getValue();
                  dexItem.newOffset = 1;
                  methodCodeOffMaps.put(name, dexItem);
              }
          }
  
          return this;
      }
  
      /**
       * 要单独移除debugInfo的类
       *
       * @param removeClasses
       * @return
       */
      public DexTransform removeDebugInfos(List<String> removeClasses) {
          assert null != removeClasses;
          for (Map.Entry<String, DexItem> entry : methodCodeOffMaps.entrySet()) {
              String name = entry.getKey();
              String className = name.substring(0, name.indexOf("."));
              if (removeClasses.contains(className)) {
                  debugInfoRemoveMethodss.add(entry.getValue().index);
              }
          }
          return this;
      }
  
      protected Dex transfromDex() throws IOException {
          transformStringIds();
          transformTypeIds();
          transformTypeLists();
          transformProtoIds();
          transformFieldIds();
          transformMethodIds();
          transformAnnotations();
  
          transformAnnotationSets(dex, indexMap);
          transformAnnotationSetRefLists(dex, indexMap);
          transformAnnotationDirectories(dex, indexMap);
          transformStaticValues(dex, indexMap);
  
          // 处理ClassDef的值
          transformClassDefs();
  
          // write the header
          contentsOut.header.off = 0;
          contentsOut.header.size = 1;
          contentsOut.fileSize = dexOut.getLength();
          contentsOut.computeSizesFromOffsets();
          contentsOut.writeHeader(headerOut, dex.getTableOfContents().apiLevel);
          contentsOut.writeMap(mapListOut);
  
          // generate and write the hashes
          dexOut.writeHashes();
  
          return dexOut;
      }
  
      /**
       * 处理StringIds的操作
       */
      private void transformStringIds() {
          if(onlyShrink){
              contentsOut.stringIds.off = idsDefsOut.getPosition();
              TableOfContents.Section stringIds = dex.getTableOfContents().stringIds;
              Dex.Section stringDataSection = dex.open(stringIds.off);
              for (int i = 0; i < stringIds.size; i++) {
                  int position = stringDataSection.getPosition();
                  int off = stringDataSection.readInt();
                  if (off < 0) {// 如果为负数,则表示取下一个dex的值
                      idsDefsOut.writeInt(off);
                  } else {
                      stringDataSection.getData().position(position);
                      contentsOut.stringDatas.size++;
                      idsDefsOut.writeInt(stringDataOut.getPosition());
                      stringDataOut.writeStringData(stringDataSection.readString());
                  }
                  indexMap.stringIds[i] = (short) i;
              }
              contentsOut.stringIds.size = stringIds.size;
  
          }else {
              contentsOut.stringIds.off = idsDefsOut.getPosition();
              int index = 0;
              for (Map.Entry<String, DexItem> entry : stringValueOffMaps.entrySet()) {
                  String value = entry.getKey();
                  DexItem item = entry.getValue();
                  if (item.newOffset > 0) {
                      contentsOut.stringDatas.size++;
                      idsDefsOut.writeInt(stringDataOut.getPosition());
                      stringDataOut.writeStringData(value);
                  } else {
                      idsDefsOut.writeInt(item.newOffset);
                  }
                  indexMap.stringIds[index] = (short) index;
                  index++;
  
              }
              contentsOut.stringIds.size = stringValueOffMaps.size();
          }
      }
  
      public Dex process() throws IOException {
          Dex result = transfromDex();
          DexTransform dexShrink = new DexTransform(result, new WriterSizes(this),true);
          result = dexShrink.transfromDex();
          return result;
      }
  
      /**
       * Reads an IDs section of two dex files and writes an IDs section of a
       * merged dex file. Populates maps from old to new indices in the merge.
       */
      abstract class IdTransform<T extends Comparable<T>> {
  
          private final Dex.Section out;
  
          protected IdTransform(Dex.Section out){
              this.out = out;
          }
  
          public void execute() {
              TableOfContents.Section dexSection = getSection(dex.getTableOfContents());
              int size = dexSection.size;
              if (dexSection.exists()) {
                  Dex.Section inSection = dex.open(dexSection.off);
                  getSection(contentsOut).off = out.getPosition();
                  for (int i = 0; i < size; i++) {
                      updateIndex(inSection.getPosition(), indexMap, i, i);
                      T value = read(inSection, indexMap, i);
                      write(value);
                  }
              } else {
                  getSection(contentsOut).off = 0;
              }
              getSection(contentsOut).size = dexSection.size;
          }
  
          abstract TableOfContents.Section getSection(TableOfContents tableOfContents);
  
          abstract T read(Dex.Section in, IndexMap indexMap, int index);
  
          abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex);
  
          abstract void write(T value);
  
      }
  
      private void transformTypeIds() {
          new IdTransform<Integer>(idsDefsOut) {
  
              @Override
              TableOfContents.Section getSection(TableOfContents tableOfContents) {
                  return tableOfContents.typeIds;
              }
  
              @Override
              Integer read(Dex.Section in, IndexMap indexMap, int index) {
                  int stringIndex = in.readInt();
                  return indexMap.adjustString(stringIndex);
              }
  
              @Override
              void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
                  indexMap.typeIds[oldIndex] = (short) newIndex;
              }
  
              @Override
              void write(Integer value) {
                  idsDefsOut.writeInt(value);
              }
          }.execute();
      }
  
      private void transformTypeLists() {
          new IdTransform<TypeList>(typeListOut) {
  
              @Override
              TableOfContents.Section getSection(TableOfContents tableOfContents) {
                  return tableOfContents.typeLists;
              }
  
              @Override
              TypeList read(Dex.Section in, IndexMap indexMap, int index) {
                  return indexMap.adjustTypeList(in.readTypeList());
              }
  
              @Override
              void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
                  indexMap.putTypeListOffset(offset, typeListOut.getPosition());
              }
  
              @Override
              void write(TypeList value) {
                  typeListOut.writeTypeList(value);
              }
          }.execute();
      }
  
      private void transformProtoIds() {
          new IdTransform<ProtoId>(idsDefsOut) {
  
              @Override
              TableOfContents.Section getSection(TableOfContents tableOfContents) {
                  return tableOfContents.protoIds;
              }
  
              @Override
              ProtoId read(Dex.Section in, IndexMap indexMap, int index) {
                  return indexMap.adjust(in.readProtoId());
              }
  
              @Override
              void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
                  indexMap.protoIds[oldIndex] = (short) newIndex;
              }
  
              @Override
              void write(ProtoId value) {
                  value.writeTo(idsDefsOut);
              }
          }.execute();
      }
  
      private void transformFieldIds() {
          new IdTransform<FieldId>(idsDefsOut) {
  
              @Override
              TableOfContents.Section getSection(TableOfContents tableOfContents) {
                  return tableOfContents.fieldIds;
              }
  
              @Override
              FieldId read(Dex.Section in, IndexMap indexMap, int index) {
                  return indexMap.adjust(in.readFieldId());
              }
  
              @Override
              void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
                  indexMap.fieldIds[oldIndex] = (short) newIndex;
              }
  
              @Override
              void write(FieldId value) {
                  value.writeTo(idsDefsOut);
              }
          }.execute();
      }
  
      private void transformMethodIds() {
          new IdTransform<MethodId>(idsDefsOut) {
  
              @Override
              TableOfContents.Section getSection(TableOfContents tableOfContents) {
                  return tableOfContents.methodIds;
              }
  
              @Override
              MethodId read(Dex.Section in, IndexMap indexMap, int index) {
                  return indexMap.adjust(in.readMethodId());
              }
  
              @Override
              void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
                  indexMap.methodIds[oldIndex] = (short) newIndex;
              }
  
              @Override
              void write(MethodId methodId) {
                  methodId.writeTo(idsDefsOut);
              }
          }.execute();
      }
  
      private void transformAnnotations() {
          new IdTransform<Annotation>(annotationOut) {
  
              @Override
              TableOfContents.Section getSection(TableOfContents tableOfContents) {
                  return tableOfContents.annotations;
              }
  
              @Override
              Annotation read(Dex.Section in, IndexMap indexMap, int index) {
                  return indexMap.adjust(in.readAnnotation());
              }
  
              @Override
              void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
                  indexMap.putAnnotationOffset(offset, annotationOut.getPosition());
              }
  
              @Override
              void write(Annotation value) {
                  value.writeTo(annotationOut);
              }
          }.execute();
      }
  
      private void transformClassDefs() {
          TableOfContents.Section dexSection = dex.getTableOfContents().classDefs;
          if (dexSection.exists()) {
              contentsOut.classDefs.off = idsDefsOut.getPosition();
              contentsOut.classDefs.size = dexSection.size;
              // 书写classData
              for (ClassDef oldClassDef : dex.classDefs()) {
                  ClassDef classDef = indexMap.adjust(oldClassDef);
                  transformClassDef(dex, classDef, indexMap);
              }
          }
      }
  
      private void transformAnnotationSets(Dex in, IndexMap indexMap) {
          TableOfContents.Section section = in.getTableOfContents().annotationSets;
          if (section.exists()) {
              Dex.Section setIn = in.open(section.off);
              for (int i = 0; i < section.size; i++) {
                  transformAnnotationSet(indexMap, setIn);
              }
          }
      }
  
      private void transformAnnotationSetRefLists(Dex in, IndexMap indexMap) {
          TableOfContents.Section section = in.getTableOfContents().annotationSetRefLists;
          if (section.exists()) {
              Dex.Section setIn = in.open(section.off);
              for (int i = 0; i < section.size; i++) {
                  transformAnnotationSetRefList(indexMap, setIn);
              }
          }
      }
  
      private void transformAnnotationDirectories(Dex in, IndexMap indexMap) {
          TableOfContents.Section section = in.getTableOfContents().annotationsDirectories;
          if (section.exists()) {
              Dex.Section directoryIn = in.open(section.off);
              for (int i = 0; i < section.size; i++) {
                  transformAnnotationDirectory(directoryIn, indexMap);
              }
          }
      }
  
      private void transformStaticValues(Dex in, IndexMap indexMap) {
          TableOfContents.Section section = in.getTableOfContents().encodedArrays;
          if (section.exists()) {
              Dex.Section staticValuesIn = in.open(section.off);
              for (int i = 0; i < section.size; i++) {
                  transformStaticValues(staticValuesIn, indexMap);
              }
          }
      }
  
      /**
       * Reads a class_def_item beginning at {@code in} and writes the index and
       * data.
       */
      private void transformClassDef(Dex in, ClassDef classDef, IndexMap indexMap) {
          idsDefsOut.assertFourByteAligned();
          idsDefsOut.writeInt(classDef.getTypeIndex());
          idsDefsOut.writeInt(classDef.getAccessFlags());
          idsDefsOut.writeInt(classDef.getSupertypeIndex());
          idsDefsOut.writeInt(classDef.getInterfacesOffset());
  
          int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex());
          idsDefsOut.writeInt(sourceFileIndex);
  
          int annotationsOff = classDef.getAnnotationsOffset();
          idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff));
  
          int classDataOff = classDef.getClassDataOffset();
          if (removeClassDefs.containsKey(classDef.getTypeIndex())&&!onlyShrink) {
              idsDefsOut.writeInt(removeClassDefs.get(classDef.getTypeIndex()));
          }else if (onlyShrink && classDataOff < 0){
              idsDefsOut.writeInt(classDataOff);
  
          }else if (classDataOff == 0) {
              idsDefsOut.writeInt(0);
          } else {
              idsDefsOut.writeInt(classDataOut.getPosition());
              ClassData classData = in.readClassData(classDef);
              transformClassData(in, classData, indexMap);
          }
  
          int staticValuesOff = classDef.getStaticValuesOffset();
          idsDefsOut.writeInt(indexMap.adjustStaticValues(staticValuesOff));
      }
  
      /**
       * Transform all annotations on a class.
       */
      private void transformAnnotationDirectory(Dex.Section directoryIn, IndexMap indexMap) {
          contentsOut.annotationsDirectories.size++;
          annotationsDirectoryOut.assertFourByteAligned();
          indexMap.putAnnotationDirectoryOffset(directoryIn.getPosition(), annotationsDirectoryOut.getPosition());
  
          int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt());
          annotationsDirectoryOut.writeInt(classAnnotationsOffset);
  
          int fieldsSize = directoryIn.readInt();
          annotationsDirectoryOut.writeInt(fieldsSize);
  
          int methodsSize = directoryIn.readInt();
          annotationsDirectoryOut.writeInt(methodsSize);
  
          int parameterListSize = directoryIn.readInt();
          annotationsDirectoryOut.writeInt(parameterListSize);
  
          for (int i = 0; i < fieldsSize; i++) {
              // field index
              annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt()));
  
              // annotations offset
              annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt()));
          }
  
          for (int i = 0; i < methodsSize; i++) {
              // method index
              annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
  
              // annotation set offset
              annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt()));
          }
  
          for (int i = 0; i < parameterListSize; i++) {
              // method index
              annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt()));
  
              // annotations offset
              annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSetRefList(directoryIn.readInt()));
          }
      }
  
      /**
       * Transform all annotations on a single type, member or parameter.
       */
      private void transformAnnotationSet(IndexMap indexMap, Dex.Section setIn) {
          contentsOut.annotationSets.size++;
          annotationSetOut.assertFourByteAligned();
          indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition());
  
          int size = setIn.readInt();
          annotationSetOut.writeInt(size);
  
          for (int j = 0; j < size; j++) {
              annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt()));
          }
      }
  
      /**
       * Transform all annotation set ref lists.
       */
      private void transformAnnotationSetRefList(IndexMap indexMap, Dex.Section refListIn) {
          contentsOut.annotationSetRefLists.size++;
          annotationSetRefListOut.assertFourByteAligned();
          indexMap.putAnnotationSetRefListOffset(refListIn.getPosition(), annotationSetRefListOut.getPosition());
  
          int parameterCount = refListIn.readInt();
          annotationSetRefListOut.writeInt(parameterCount);
          for (int p = 0; p < parameterCount; p++) {
              annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt()));
          }
      }
  
      private void transformClassData(Dex in, ClassData classData, IndexMap indexMap) {
          contentsOut.classDatas.size++;
  
          ClassData.Field[] staticFields = classData.getStaticFields();
          ClassData.Field[] instanceFields = classData.getInstanceFields();
          ClassData.Method[] directMethods = classData.getDirectMethods();
          ClassData.Method[] virtualMethods = classData.getVirtualMethods();
  
          classDataOut.writeUleb128(staticFields.length);
          classDataOut.writeUleb128(instanceFields.length);
          classDataOut.writeUleb128(directMethods.length);
          classDataOut.writeUleb128(virtualMethods.length);
  
          transformFields(indexMap, staticFields);
          transformFields(indexMap, instanceFields);
          transformMethods(in, indexMap, directMethods);
          transformMethods(in, indexMap, virtualMethods);
      }
  
      private void transformFields(IndexMap indexMap, ClassData.Field[] fields) {
          int lastOutFieldIndex = 0;
          for (ClassData.Field field : fields) {
              int outFieldIndex = indexMap.adjustField(field.getFieldIndex());
              classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex);
              lastOutFieldIndex = outFieldIndex;
              classDataOut.writeUleb128(field.getAccessFlags());
          }
      }
  
      private void transformMethods(Dex in, IndexMap indexMap, ClassData.Method[] methods) {
          int lastOutMethodIndex = 0;
          for (ClassData.Method method : methods) {
              int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex());
              classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex);
              lastOutMethodIndex = outMethodIndex;
  
              classDataOut.writeUleb128(method.getAccessFlags());
  
              if(onlyShrink) {
                  if (method.getCodeOffset() <= 1) {
                      classDataOut.writeUleb128(method.getCodeOffset());
                  } else {
                      codeOut.alignToFourBytesWithZeroFill();
                      classDataOut.writeUleb128(codeOut.getPosition());
                      transformCode(in, in.readCode(method), indexMap,method.getMethodIndex());
                  }
              }else{
                  MethodId methodId = dex.methodIds().get(method.getMethodIndex());
                  String className = dex.typeNames().get(methodId.getDeclaringClassIndex());
                  String methodName = dex.strings().get(methodId.getNameIndex())
                          + dex.readTypeList(dex.protoIds().get(methodId.getProtoIndex()).getParametersOffset());
                  String fullMethodName = className + "." + methodName;
                  DexItem dexItem = methodCodeOffMaps.get(fullMethodName);
  
                  if (dexItem.newOffset <= 1) {
                      classDataOut.writeUleb128(dexItem.newOffset);
                  } else {
                      codeOut.alignToFourBytesWithZeroFill();
                      classDataOut.writeUleb128(codeOut.getPosition());
                      transformCode(in, in.readCode(method), indexMap, method.getMethodIndex());
                  }
              }
          }
      }
  
      private void transformCode(Dex in, Code code, IndexMap indexMap, int methodIndex) {
          contentsOut.codes.size++;
          codeOut.assertFourByteAligned();
  
          codeOut.writeUnsignedShort(code.getRegistersSize());
          codeOut.writeUnsignedShort(code.getInsSize());
          codeOut.writeUnsignedShort(code.getOutsSize());
  
          Code.Try[] tries = code.getTries();
          Code.CatchHandler[] catchHandlers = code.getCatchHandlers();
          codeOut.writeUnsignedShort(tries.length);
  
          int debugInfoOffset = code.getDebugInfoOffset();
  
          // 判断是否移除debugInfo
          if (debugInfoRemoveMethodss.contains(methodIndex) || debugInfoOffset <= 0) {
              codeOut.writeInt(0);
          } else {
              codeOut.writeInt(debugInfoOut.getPosition());
              transformDebugInfoItem(in.open(debugInfoOffset), indexMap);
          }
  
          short[] instructions = code.getInstructions();
          short[] newInstructions = instructionTransformer.transform(indexMap, instructions);
          codeOut.writeInt(newInstructions.length);
          codeOut.write(newInstructions);
  
          if (tries.length > 0) {
              if (newInstructions.length % 2 == 1) {
                  codeOut.writeShort((short) 0); // padding
              }
  
              /*
               * We can't write the tries until we've written the catch handlers.
               * Unfortunately they're in the opposite order in the dex file so we
               * need to transform them out-of-order.
               */
              Dex.Section triesSection = dexOut.open(codeOut.getPosition());
              codeOut.skip(tries.length * SizeOf.TRY_ITEM);
              int[] offsets = transformCatchHandlers(indexMap, catchHandlers);
              transformTries(triesSection, tries, offsets);
          }
      }
  
      /**
       * Writes the catch handlers to {@code codeOut} and returns their indices.
       */
      private int[] transformCatchHandlers(IndexMap indexMap, Code.CatchHandler[] catchHandlers) {
          int baseOffset = codeOut.getPosition();
          codeOut.writeUleb128(catchHandlers.length);
          int[] offsets = new int[catchHandlers.length];
          for (int i = 0; i < catchHandlers.length; i++) {
              offsets[i] = codeOut.getPosition() - baseOffset;
              transformEncodedCatchHandler(catchHandlers[i], indexMap);
          }
          return offsets;
      }
  
      private void transformTries(Dex.Section out, Code.Try[] tries, int[] catchHandlerOffsets) {
          for (Code.Try tryItem : tries) {
              out.writeInt(tryItem.getStartAddress());
              out.writeUnsignedShort(tryItem.getInstructionCount());
              out.writeUnsignedShort(catchHandlerOffsets[tryItem.getCatchHandlerIndex()]);
          }
      }
  
      private static final byte DBG_END_SEQUENCE         = 0x00;
      private static final byte DBG_ADVANCE_PC           = 0x01;
      private static final byte DBG_ADVANCE_LINE         = 0x02;
      private static final byte DBG_START_LOCAL          = 0x03;
      private static final byte DBG_START_LOCAL_EXTENDED = 0x04;
      private static final byte DBG_END_LOCAL            = 0x05;
      private static final byte DBG_RESTART_LOCAL        = 0x06;
      private static final byte DBG_SET_PROLOGUE_END     = 0x07;
      private static final byte DBG_SET_EPILOGUE_BEGIN   = 0x08;
      private static final byte DBG_SET_FILE             = 0x09;
  
      private void transformDebugInfoItem(Dex.Section in, IndexMap indexMap) {
          contentsOut.debugInfos.size++;
          int lineStart = in.readUleb128();
          debugInfoOut.writeUleb128(lineStart);
  
          int parametersSize = in.readUleb128();
          debugInfoOut.writeUleb128(parametersSize);
  
          for (int p = 0; p < parametersSize; p++) {
              int parameterName = in.readUleb128p1();
              debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName));
          }
  
          int addrDiff; // uleb128 address delta.
          int lineDiff; // sleb128 line delta.
          int registerNum; // uleb128 register number.
          int nameIndex; // uleb128p1 string index. Needs indexMap adjustment.
          int typeIndex; // uleb128p1 type index. Needs indexMap adjustment.
          int sigIndex; // uleb128p1 string index. Needs indexMap adjustment.
  
          while (true) {
              int opcode = in.readByte();
              debugInfoOut.writeByte(opcode);
  
              switch (opcode) {
                  case DBG_END_SEQUENCE:
                      return;
  
                  case DBG_ADVANCE_PC:
                      addrDiff = in.readUleb128();
                      debugInfoOut.writeUleb128(addrDiff);
                      break;
  
                  case DBG_ADVANCE_LINE:
                      lineDiff = in.readSleb128();
                      debugInfoOut.writeSleb128(lineDiff);
                      break;
  
                  case DBG_START_LOCAL:
                  case DBG_START_LOCAL_EXTENDED:
                      registerNum = in.readUleb128();
                      debugInfoOut.writeUleb128(registerNum);
                      nameIndex = in.readUleb128p1();
                      debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
                      typeIndex = in.readUleb128p1();
                      debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex));
                      if (opcode == DBG_START_LOCAL_EXTENDED) {
                          sigIndex = in.readUleb128p1();
                          debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex));
                      }
                      break;
  
                  case DBG_END_LOCAL:
                  case DBG_RESTART_LOCAL:
                      registerNum = in.readUleb128();
                      debugInfoOut.writeUleb128(registerNum);
                      break;
  
                  case DBG_SET_FILE:
                      nameIndex = in.readUleb128p1();
                      debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex));
                      break;
  
                  case DBG_SET_PROLOGUE_END:
                  case DBG_SET_EPILOGUE_BEGIN:
                  default:
                      break;
              }
          }
      }
  
      private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) {
          int catchAllAddress = catchHandler.getCatchAllAddress();
          int[] typeIndexes = catchHandler.getTypeIndexes();
          int[] addresses = catchHandler.getAddresses();
  
          if (catchAllAddress != -1) {
              codeOut.writeSleb128(-typeIndexes.length);
          } else {
              codeOut.writeSleb128(typeIndexes.length);
          }
  
          for (int i = 0; i < typeIndexes.length; i++) {
              codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i]));
              codeOut.writeUleb128(addresses[i]);
          }
  
          if (catchAllAddress != -1) {
              codeOut.writeUleb128(catchAllAddress);
          }
      }
  
      private void transformStaticValues(Dex.Section in, IndexMap indexMap) {
          contentsOut.encodedArrays.size++;
          indexMap.putStaticValuesOffset(in.getPosition(), encodedArrayOut.getPosition());
          indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut);
      }
  
      /**
       * Byte counts for the sections written when creating a dex. Target sizes
       * are defined in one of two ways:
       * <ul>
       * <li>By pessimistically guessing how large the union of dex files will be.
       *     We're pessimistic because we can't predict the amount of duplication
       *     between dex files, nor can we predict the length of ULEB-encoded
       *     offsets or indices.
       * <li>By exactly measuring an existing dex.
       * </ul>
       */
      private static class WriterSizes {
  
          private int header = SizeOf.HEADER_ITEM;
          private int idsDefs;
          private int mapList;
          private int typeList;
          private int classData;
          private int code;
          private int stringData;
          private int debugInfo;
          private int encodedArray;
          private int annotationsDirectory;
          private int annotationsSet;
          private int annotationsSetRefList;
          private int annotation;
  
          /**
           * Compute sizes for merging several dexes.
           */
          public WriterSizes(Dex dex, boolean exact){
              plus(dex.getTableOfContents(), exact);
              fourByteAlign();
          }
  
          public WriterSizes(DexTransform dexTransform){
              header = dexTransform.headerOut.used();
              idsDefs = dexTransform.idsDefsOut.used();
              mapList = dexTransform.mapListOut.used();
              typeList = dexTransform.typeListOut.used();
              classData = dexTransform.classDataOut.used();
              code = dexTransform.codeOut.used();
              stringData = dexTransform.stringDataOut.used();
              debugInfo = dexTransform.debugInfoOut.used();
              encodedArray = dexTransform.encodedArrayOut.used();
              annotationsDirectory = dexTransform.annotationsDirectoryOut.used();
              annotationsSet = dexTransform.annotationSetOut.used();
              annotationsSetRefList = dexTransform.annotationSetRefListOut.used();
              annotation = dexTransform.annotationOut.used();
              fourByteAlign();
          }
  
          private void plus(TableOfContents contents, boolean exact) {
              idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM + contents.typeIds.size * SizeOf.TYPE_ID_ITEM
                      + contents.protoIds.size * SizeOf.PROTO_ID_ITEM + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM
                      + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM
                      + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM;
              mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM);
              typeList += fourByteAlign(contents.typeLists.byteCount); // We count each dex's
              // typelists section as realigned on 4 bytes, because each typelist of each dex's
              // typelists section is aligned on 4 bytes. If we didn't, there is a case where each
              // size of both dex's typelists section is a multiple of 2 but not a multiple of 4,
              // and the sum of both sizes is a multiple of 4 but would not be sufficient to write
              // each typelist aligned on 4 bytes.
              stringData += contents.stringDatas.byteCount;
              annotationsDirectory += contents.annotationsDirectories.byteCount;
              annotationsSet += contents.annotationSets.byteCount;
              annotationsSetRefList += contents.annotationSetRefLists.byteCount;
  
              if (exact) {
                  code += contents.codes.byteCount;
                  classData += contents.classDatas.byteCount;
                  encodedArray += contents.encodedArrays.byteCount;
                  annotation += contents.annotations.byteCount;
                  debugInfo += contents.debugInfos.byteCount;
              } else {
                  // at most 1/4 of the bytes in a code section are uleb/sleb
                  code += (int) Math.ceil(contents.codes.byteCount * 1.25);
                  // at most 1/3 of the bytes in a class data section are uleb/sleb
                  classData += (int) Math.ceil(contents.classDatas.byteCount * 1.34);
                  // all of the bytes in an encoding arrays section may be uleb/sleb
                  encodedArray += contents.encodedArrays.byteCount * 2;
                  // all of the bytes in an annotations section may be uleb/sleb
                  annotation += (int) Math.ceil(contents.annotations.byteCount * 2);
                  // all of the bytes in a debug info section may be uleb/sleb
                  debugInfo += contents.debugInfos.byteCount * 2;
              }
          }
  
          private void fourByteAlign() {
              header = fourByteAlign(header);
              idsDefs = fourByteAlign(idsDefs);
              mapList = fourByteAlign(mapList);
              typeList = fourByteAlign(typeList);
              classData = fourByteAlign(classData);
              code = fourByteAlign(code);
              stringData = fourByteAlign(stringData);
              debugInfo = fourByteAlign(debugInfo);
              encodedArray = fourByteAlign(encodedArray);
              annotationsDirectory = fourByteAlign(annotationsDirectory);
              annotationsSet = fourByteAlign(annotationsSet);
              annotationsSetRefList = fourByteAlign(annotationsSetRefList);
              annotation = fourByteAlign(annotation);
          }
  
          private static int fourByteAlign(int position) {
              return (position + 3) & ~3;
          }
  
          public int size() {
              return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo + encodedArray
                      + annotationsDirectory + annotationsSet + annotationsSetRefList + annotation;
          }
      }
  
      class DexItem {
  
          int offset;
          int index;
          int newOffset;
  
          public DexItem(int offset, int index, int newOffset){
              this.offset = offset;
              this.index = index;
              this.newOffset = newOffset;
          }
      }
  }
  
  