| /* |
| * 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. |
| * |
| * <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 <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"); |
| } |
| } |
| } |