blob: 9053043f989d884ae0adfc57ab265fcfbecabc4e [file] [log] [blame]
/*
* 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.dex.util.ExceptionWithContext;
import com.android.dx.util.AnnotatedOutput;
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 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");
}
}
}