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 java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.util.Arrays;
  
  /**
   * The file header and map.
   */
  public final class TableOfContents {
  
      /*
       * TODO: factor out ID constants.
       */
  
      public final Section header = new Section(0x0000);
      public final Section stringIds = new Section(0x0001);
      public final Section typeIds = new Section(0x0002);
      public final Section protoIds = new Section(0x0003);
      public final Section fieldIds = new Section(0x0004);
      public final Section methodIds = new Section(0x0005);
      public final Section classDefs = new Section(0x0006);
      public final Section mapList = new Section(0x1000);
      public final Section typeLists = new Section(0x1001);
      public final Section annotationSetRefLists = new Section(0x1002);
      public final Section annotationSets = new Section(0x1003);
      public final Section classDatas = new Section(0x2000);
      public final Section codes = new Section(0x2001);
      public final Section stringDatas = new Section(0x2002);
      public final Section debugInfos = new Section(0x2003);
      public final Section annotations = new Section(0x2004);
      public final Section encodedArrays = new Section(0x2005);
      public final Section annotationsDirectories = new Section(0x2006);
      public final Section[] sections = {
              header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList,
              typeLists, annotationSetRefLists, annotationSets, classDatas, codes, stringDatas,
              debugInfos, annotations, encodedArrays, annotationsDirectories
      };
  
      public int apiLevel;
      public int checksum;
      public byte[] signature;
      public int fileSize;
      public int linkSize;
      public int linkOff;
      public int dataSize;
      public int dataOff;
  
      public TableOfContents() {
          signature = new byte[20];
      }
  
      public void readFrom(Dex dex) throws IOException {
          readHeader(dex.open(0));
          readMap(dex.open(mapList.off));
          computeSizesFromOffsets();
      }
  
      private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException {
          byte[] magic = headerIn.readByteArray(8);
  
          if (!DexFormat.isSupportedDexMagic(magic)) {
              throw new DexException2("Unexpected magic: " + Arrays.toString(magic));
          }
  
          apiLevel = DexFormat.magicToApi(magic);
          checksum = headerIn.readInt();
          signature = headerIn.readByteArray(20);
          fileSize = headerIn.readInt();
          int headerSize = headerIn.readInt();
          if (headerSize != SizeOf.HEADER_ITEM) {
              throw new DexException2("Unexpected header: 0x" + Integer.toHexString(headerSize));
          }
          int endianTag = headerIn.readInt();
          if (endianTag != DexFormat.ENDIAN_TAG) {
              throw new DexException2("Unexpected endian tag: 0x" + Integer.toHexString(endianTag));
          }
          linkSize = headerIn.readInt();
          linkOff = headerIn.readInt();
          mapList.off = headerIn.readInt();
          if (mapList.off == 0) {
              throw new DexException2("Cannot merge dex files that do not contain a map");
          }
          stringIds.size = headerIn.readInt();
          stringIds.off = headerIn.readInt();
          typeIds.size = headerIn.readInt();
          typeIds.off = headerIn.readInt();
          protoIds.size = headerIn.readInt();
          protoIds.off = headerIn.readInt();
          fieldIds.size = headerIn.readInt();
          fieldIds.off = headerIn.readInt();
          methodIds.size = headerIn.readInt();
          methodIds.off = headerIn.readInt();
          classDefs.size = headerIn.readInt();
          classDefs.off = headerIn.readInt();
          dataSize = headerIn.readInt();
          dataOff = headerIn.readInt();
      }
  
      private void readMap(Dex.Section in) throws IOException {
          int mapSize = in.readInt();
          Section previous = null;
          for (int i = 0; i < mapSize; i++) {
              short type = in.readShort();
              in.readShort(); // unused
              Section section = getSection(type);
              int size = in.readInt();
              int offset = in.readInt();
  
              if ((section.size != 0 && section.size != size)
                      || (section.off != -1 && section.off != offset)) {
                  throw new DexException2("Unexpected map value for 0x" + Integer.toHexString(type));
              }
  
              section.size = size;
              section.off = offset;
  
              if (previous != null && previous.off > section.off) {
                  throw new DexException2("Map is unsorted at " + previous + ", " + section);
              }
  
              previous = section;
          }
          Arrays.sort(sections);
      }
  
      public void computeSizesFromOffsets() {
          int end = dataOff + dataSize;
          for (int i = sections.length - 1; i >= 0; i--) {
              Section section = sections[i];
              if (section.off == -1) {
                  continue;
              }
              if (section.off > end) {
                  throw new DexException2("Map is unsorted at " + section + ",off: "+section.off+",end:" + end);
              }
              section.byteCount = end - section.off;
              if(section.off > 0) {
                  end = section.off;
              }
          }
      }
  
      private Section getSection(short type) {
          for (Section section : sections) {
              if (section.type == type) {
                  return section;
              }
          }
          throw new IllegalArgumentException("No such map item: " + type);
      }
  
      public void writeHeader(Dex.Section out, int api) throws IOException {
          out.write(DexFormat.apiToMagic(api).getBytes("UTF-8"));
          out.writeInt(checksum);
          out.write(signature);
          out.writeInt(fileSize);
          out.writeInt(SizeOf.HEADER_ITEM);
          out.writeInt(DexFormat.ENDIAN_TAG);
          out.writeInt(linkSize);
          out.writeInt(linkOff);
          out.writeInt(mapList.off);
          out.writeInt(stringIds.size);
          out.writeInt(stringIds.off);
          out.writeInt(typeIds.size);
          out.writeInt(typeIds.off);
          out.writeInt(protoIds.size);
          out.writeInt(protoIds.off);
          out.writeInt(fieldIds.size);
          out.writeInt(fieldIds.off);
          out.writeInt(methodIds.size);
          out.writeInt(methodIds.off);
          out.writeInt(classDefs.size);
          out.writeInt(classDefs.off);
          out.writeInt(dataSize);
          out.writeInt(dataOff);
      }
  
      public void writeMap(Dex.Section out) throws IOException {
          int count = 0;
          for (Section section : sections) {
              if (section.exists()) {
                  count++;
              }
          }
  
          out.writeInt(count);
          for (Section section : sections) {
              if (section.exists()) {
                  out.writeShort(section.type);
                  out.writeShort((short) 0);
                  out.writeInt(section.size);
                  out.writeInt(section.off);
              }
          }
      }
  
      public static class Section implements Comparable<Section> {
          public final short type;
          public int size = 0;
          public int off = -1;
          public int byteCount = 0;
  
          public Section(int type) {
              this.type = (short) type;
          }
  
          public boolean exists() {
              return size > 0;
          }
  
          public int compareTo(Section section) {
              if (off != section.off) {
                  return off < section.off ? -1 : 1;
              }
              return 0;
          }
  
          @Override public String toString() {
              return String.format("Section[type=%#x,off=%#x,size=%#x]", type, off, size);
          }
      }
  }
  