content: /*
   * Copyright (C) 2007 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.android.dx.dex.file;
  
  import com.taobao.android.dex.util.ExceptionWithContext;
  import com.taobao.android.dx.util.AnnotatedOutput;
  import com.taobao.android.dx.util.Hex;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.TreeMap;
  
  /**
   * A section of a {@code .dex} file which consists of a sequence of
   * {@link OffsettedItem} objects, which may each be of a different concrete
   * class and/or size.
   *
   * <b>Note:</b> It is invalid for an item in an instance of this class to
   * have a larger alignment requirement than the alignment of this instance.
   */
  public final class MixedItemSection extends Section {
      static enum SortType {
          /** no sorting */
          NONE,
  
          /** sort by type only */
          TYPE,
  
          /** sort in class-major order, with instances sorted per-class */
          INSTANCE;
      };
  
      /** {@code non-null;} sorter which sorts instances by type */
      private static final Comparator<OffsettedItem> TYPE_SORTER =
          new Comparator<OffsettedItem>() {
          public int compare(OffsettedItem item1, OffsettedItem item2) {
              ItemType type1 = item1.itemType();
              ItemType type2 = item2.itemType();
              return type1.compareTo(type2);
          }
      };
  
      /** {@code non-null;} the items in this part */
      private final ArrayList<OffsettedItem> items;
  
      /** {@code non-null;} items that have been explicitly interned */
      private final HashMap<OffsettedItem, OffsettedItem> interns;
  
      /** {@code non-null;} how to sort the items */
      private final SortType sort;
  
      /**
       * {@code >= -1;} the current size of this part, in bytes, or {@code -1}
       * if not yet calculated
       */
      private int writeSize;
  
      /**
       * Constructs an instance. The file offset is initially unknown.
       *
       * @param name {@code null-ok;} the name of this instance, for annotation
       * purposes
       * @param file {@code non-null;} file that this instance is part of
       * @param alignment {@code > 0;} alignment requirement for the final output;
       * must be a power of 2
       * @param sort how the items should be sorted in the final output
       */
      public MixedItemSection(String name, DexFile file, int alignment,
              SortType sort) {
          super(name, file, alignment);
  
          this.items = new ArrayList<OffsettedItem>(100);
          this.interns = new HashMap<OffsettedItem, OffsettedItem>(100);
          this.sort = sort;
          this.writeSize = -1;
      }
  
      /** {@inheritDoc} */
      @Override
      public Collection<? extends Item> items() {
          return items;
      }
  
      /** {@inheritDoc} */
      @Override
      public int writeSize() {
          throwIfNotPrepared();
          return writeSize;
      }
  
      /** {@inheritDoc} */
      @Override
      public int getAbsoluteItemOffset(Item item) {
          OffsettedItem oi = (OffsettedItem) item;
          return oi.getAbsoluteOffset();
      }
  
      /**
       * Gets the size of this instance, in items.
       *
       * @return {@code >= 0;} the size
       */
      public int size() {
          return items.size();
      }
  
      /**
       * Writes the portion of the file header that refers to this instance.
       *
       * @param out {@code non-null;} where to write
       */
      public void writeHeaderPart(AnnotatedOutput out) {
          throwIfNotPrepared();
  
          if (writeSize == -1) {
              throw new RuntimeException("write size not yet set");
          }
  
          int sz = writeSize;
          int offset = (sz == 0) ? 0 : getFileOffset();
          String name = getName();
  
          if (name == null) {
              name = "<unnamed>";
          }
  
          int spaceCount = 15 - name.length();
          char[] spaceArr = new char[spaceCount];
          Arrays.fill(spaceArr, ' ');
          String spaces = new String(spaceArr);
  
          if (out.annotates()) {
              out.annotate(4, name + "_size:" + spaces + Hex.u4(sz));
              out.annotate(4, name + "_off: " + spaces + Hex.u4(offset));
          }
  
          out.writeInt(sz);
          out.writeInt(offset);
      }
  
      /**
       * Adds an item to this instance. This will in turn tell the given item
       * that it has been added to this instance. It is invalid to add the
       * same item to more than one instance, nor to add the same items
       * multiple times to a single instance.
       *
       * @param item {@code non-null;} the item to add
       */
      public void add(OffsettedItem item) {
          throwIfPrepared();
  
          try {
              if (item.getAlignment() > getAlignment()) {
                  throw new IllegalArgumentException(
                          "incompatible item alignment");
              }
          } catch (NullPointerException ex) {
              // Elucidate the exception.
              throw new NullPointerException("item == null");
          }
  
          items.add(item);
      }
  
      /**
       * Interns an item in this instance, returning the interned instance
       * (which may not be the one passed in). This will add the item if no
       * equal item has been added.
       *
       * @param item {@code non-null;} the item to intern
       * @return {@code non-null;} the equivalent interned instance
       */
      public synchronized <T extends OffsettedItem> T intern(T item) {
          throwIfPrepared();
  
          OffsettedItem result = interns.get(item);
  
          if (result != null) {
              return (T) result;
          }
  
          add(item);
          interns.put(item, item);
          return item;
      }
  
      /**
       * Gets an item which was previously interned.
       *
       * @param item {@code non-null;} the item to look for
       * @return {@code non-null;} the equivalent already-interned instance
       */
      public <T extends OffsettedItem> T get(T item) {
          throwIfNotPrepared();
  
          OffsettedItem result = interns.get(item);
  
          if (result != null) {
              return (T) result;
          }
  
          throw new NoSuchElementException(item.toString());
      }
  
      /**
       * Writes an index of contents of the items in this instance of the
       * given type. If there are none, this writes nothing. If there are any,
       * then the index is preceded by the given intro string.
       *
       * @param out {@code non-null;} where to write to
       * @param itemType {@code non-null;} the item type of interest
       * @param intro {@code non-null;} the introductory string for non-empty indices
       */
      public void writeIndexAnnotation(AnnotatedOutput out, ItemType itemType,
              String intro) {
          throwIfNotPrepared();
  
          TreeMap<String, OffsettedItem> index =
              new TreeMap<String, OffsettedItem>();
  
          for (OffsettedItem item : items) {
              if (item.itemType() == itemType) {
                  String label = item.toHuman();
                  index.put(label, item);
              }
          }
  
          if (index.size() == 0) {
              return;
          }
  
          out.annotate(0, intro);
  
          for (Map.Entry<String, OffsettedItem> entry : index.entrySet()) {
              String label = entry.getKey();
              OffsettedItem item = entry.getValue();
              out.annotate(0, item.offsetString() + ' ' + label + '\n');
          }
      }
  
      /** {@inheritDoc} */
      @Override
      protected void prepare0() {
          DexFile file = getFile();
  
          /*
           * It's okay for new items to be added as a result of an
           * addContents() call; we just have to deal with the possibility.
           */
  
          int i = 0;
          for (;;) {
              int sz = items.size();
              if (i >= sz) {
                  break;
              }
  
              for (/*i*/; i < sz; i++) {
                  OffsettedItem one = items.get(i);
                  one.addContents(file);
              }
          }
      }
  
      /**
       * Places all the items in this instance at particular offsets. This
       * will call {@link OffsettedItem#place} on each item. If an item
       * does not know its write size before the call to {@code place},
       * it is that call which is responsible for setting the write size.
       * This method may only be called once per instance; subsequent calls
       * will throw an exception.
       */
      public void placeItems() {
          throwIfNotPrepared();
  
          switch (sort) {
              case INSTANCE: {
                  Collections.sort(items);
                  break;
              }
              case TYPE: {
                  Collections.sort(items, TYPE_SORTER);
                  break;
              }
          }
  
          int sz = items.size();
          int outAt = 0;
          for (int i = 0; i < sz; i++) {
              OffsettedItem one = items.get(i);
              try {
                  int placedAt = one.place(this, outAt);
  
                  if (placedAt < outAt) {
                      throw new RuntimeException("bogus place() result for " +
                              one);
                  }
  
                  outAt = placedAt + one.writeSize();
              } catch (RuntimeException ex) {
                  throw ExceptionWithContext.withContext(ex,
                          "...while placing " + one);
              }
          }
  
          writeSize = outAt;
      }
  
      /** {@inheritDoc} */
      @Override
      protected void writeTo0(AnnotatedOutput out) {
          boolean annotates = out.annotates();
          boolean first = true;
          DexFile file = getFile();
          int at = 0;
  
          for (OffsettedItem one : items) {
              if (annotates) {
                  if (first) {
                      first = false;
                  } else {
                      out.annotate(0, "\n");
                  }
              }
  
              int alignMask = one.getAlignment() - 1;
              int writeAt = (at + alignMask) & ~alignMask;
  
              if (at != writeAt) {
                  out.writeZeroes(writeAt - at);
                  at = writeAt;
              }
  
              one.writeTo(file, out);
              at += one.writeSize();
          }
  
          if (at != writeSize) {
              throw new RuntimeException("output size mismatch");
          }
      }
  }
  