/* * 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.android.dx.dex.file; import com.android.dx.util.AnnotatedOutput; import com.android.dx.util.ExceptionWithContext; import com.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. * * Note: 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 TYPE_SORTER = new Comparator() { 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 items; /** {@code non-null;} items that have been explicitly interned */ private final HashMap 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(100); this.interns = new HashMap(100); this.sort = sort; this.writeSize = -1; } /** {@inheritDoc} */ @Override public Collection 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 = ""; } 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 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 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 index = new TreeMap(); 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 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"); } } }