blob: 4823f9f5fb42e0abff4532e7e82c9cead81edc47 [file] [log] [blame]
/*
* Copyright (C) 2008 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.rop.cst.Constant;
import com.android.dx.rop.cst.CstArray;
import com.android.dx.rop.cst.CstLiteralBits;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.cst.Zeroes;
import com.android.dx.util.ByteArrayAnnotatedOutput;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.Hex;
import com.android.dx.util.Writers;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
/**
* Representation of all the parts of a Dalvik class that are generally
* "inflated" into an in-memory representation at runtime. Instances of
* this class are represented in a compact streamable form in a
* {@code dex} file, as opposed to a random-access form.
*/
public final class ClassDataItem extends OffsettedItem {
/** {@code non-null;} what class this data is for, just for listing generation */
private final CstType thisClass;
/** {@code non-null;} list of static fields */
private final ArrayList<EncodedField> staticFields;
/** {@code non-null;} list of initial values for static fields */
private final HashMap<EncodedField, Constant> staticValues;
/** {@code non-null;} list of instance fields */
private final ArrayList<EncodedField> instanceFields;
/** {@code non-null;} list of direct methods */
private final ArrayList<EncodedMethod> directMethods;
/** {@code non-null;} list of virtual methods */
private final ArrayList<EncodedMethod> virtualMethods;
/** {@code null-ok;} static initializer list; set in {@link #addContents} */
private CstArray staticValuesConstant;
/**
* {@code null-ok;} encoded form, ready for writing to a file; set during
* {@link #place0}
*/
private byte[] encodedForm;
/**
* Constructs an instance. Its sets of members are initially
* empty.
*
* @param thisClass {@code non-null;} what class this data is for, just
* for listing generation
*/
public ClassDataItem(CstType thisClass) {
super(1, -1);
if (thisClass == null) {
throw new NullPointerException("thisClass == null");
}
this.thisClass = thisClass;
this.staticFields = new ArrayList<EncodedField>(20);
this.staticValues = new HashMap<EncodedField, Constant>(40);
this.instanceFields = new ArrayList<EncodedField>(20);
this.directMethods = new ArrayList<EncodedMethod>(20);
this.virtualMethods = new ArrayList<EncodedMethod>(20);
this.staticValuesConstant = null;
}
/** {@inheritDoc} */
@Override
public ItemType itemType() {
return ItemType.TYPE_CLASS_DATA_ITEM;
}
/** {@inheritDoc} */
@Override
public String toHuman() {
return toString();
}
/**
* Returns whether this instance is empty.
*
* @return {@code true} if this instance is empty or
* {@code false} if at least one element has been added to it
*/
public boolean isEmpty() {
return staticFields.isEmpty() && instanceFields.isEmpty()
&& directMethods.isEmpty() && virtualMethods.isEmpty();
}
/**
* Adds a static field.
*
* @param field {@code non-null;} the field to add
* @param value {@code null-ok;} initial value for the field, if any
*/
public void addStaticField(EncodedField field, Constant value) {
if (field == null) {
throw new NullPointerException("field == null");
}
if (staticValuesConstant != null) {
throw new UnsupportedOperationException(
"static fields already sorted");
}
staticFields.add(field);
staticValues.put(field, value);
}
/**
* Adds an instance field.
*
* @param field {@code non-null;} the field to add
*/
public void addInstanceField(EncodedField field) {
if (field == null) {
throw new NullPointerException("field == null");
}
instanceFields.add(field);
}
/**
* Adds a direct ({@code static} and/or {@code private}) method.
*
* @param method {@code non-null;} the method to add
*/
public void addDirectMethod(EncodedMethod method) {
if (method == null) {
throw new NullPointerException("method == null");
}
directMethods.add(method);
}
/**
* Adds a virtual method.
*
* @param method {@code non-null;} the method to add
*/
public void addVirtualMethod(EncodedMethod method) {
if (method == null) {
throw new NullPointerException("method == null");
}
virtualMethods.add(method);
}
/**
* Gets all the methods in this class. The returned list is not linked
* in any way to the underlying lists contained in this instance, but
* the objects contained in the list are shared.
*
* @return {@code non-null;} list of all methods
*/
public ArrayList<EncodedMethod> getMethods() {
int sz = directMethods.size() + virtualMethods.size();
ArrayList<EncodedMethod> result = new ArrayList<EncodedMethod>(sz);
result.addAll(directMethods);
result.addAll(virtualMethods);
return result;
}
/**
* Prints out the contents of this instance, in a debugging-friendly
* way.
*
* @param out {@code non-null;} where to output to
* @param verbose whether to be verbose with the output
*/
public void debugPrint(Writer out, boolean verbose) {
PrintWriter pw = Writers.printWriterFor(out);
int sz = staticFields.size();
for (int i = 0; i < sz; i++) {
pw.println(" sfields[" + i + "]: " + staticFields.get(i));
}
sz = instanceFields.size();
for (int i = 0; i < sz; i++) {
pw.println(" ifields[" + i + "]: " + instanceFields.get(i));
}
sz = directMethods.size();
for (int i = 0; i < sz; i++) {
pw.println(" dmeths[" + i + "]:");
directMethods.get(i).debugPrint(pw, verbose);
}
sz = virtualMethods.size();
for (int i = 0; i < sz; i++) {
pw.println(" vmeths[" + i + "]:");
virtualMethods.get(i).debugPrint(pw, verbose);
}
}
/** {@inheritDoc} */
@Override
public void addContents(DexFile file) {
if (!staticFields.isEmpty()) {
getStaticValuesConstant(); // Force the fields to be sorted.
for (EncodedField field : staticFields) {
field.addContents(file);
}
}
if (!instanceFields.isEmpty()) {
Collections.sort(instanceFields);
for (EncodedField field : instanceFields) {
field.addContents(file);
}
}
if (!directMethods.isEmpty()) {
Collections.sort(directMethods);
for (EncodedMethod method : directMethods) {
method.addContents(file);
}
}
if (!virtualMethods.isEmpty()) {
Collections.sort(virtualMethods);
for (EncodedMethod method : virtualMethods) {
method.addContents(file);
}
}
}
/**
* Gets a {@link CstArray} corresponding to {@link #staticValues} if
* it contains any non-zero non-{@code null} values.
*
* @return {@code null-ok;} the corresponding constant or {@code null} if
* there are no values to encode
*/
public CstArray getStaticValuesConstant() {
if ((staticValuesConstant == null) && (staticFields.size() != 0)) {
staticValuesConstant = makeStaticValuesConstant();
}
return staticValuesConstant;
}
/**
* Gets a {@link CstArray} corresponding to {@link #staticValues} if
* it contains any non-zero non-{@code null} values.
*
* @return {@code null-ok;} the corresponding constant or {@code null} if
* there are no values to encode
*/
private CstArray makeStaticValuesConstant() {
// First sort the statics into their final order.
Collections.sort(staticFields);
/*
* Get the size of staticValues minus any trailing zeros/nulls (both
* nulls per se as well as instances of CstKnownNull).
*/
int size = staticFields.size();
while (size > 0) {
EncodedField field = staticFields.get(size - 1);
Constant cst = staticValues.get(field);
if (cst instanceof CstLiteralBits) {
// Note: CstKnownNull extends CstLiteralBits.
if (((CstLiteralBits) cst).getLongBits() != 0) {
break;
}
} else if (cst != null) {
break;
}
size--;
}
if (size == 0) {
return null;
}
// There is something worth encoding, so build up a result.
CstArray.List list = new CstArray.List(size);
for (int i = 0; i < size; i++) {
EncodedField field = staticFields.get(i);
Constant cst = staticValues.get(field);
if (cst == null) {
cst = Zeroes.zeroFor(field.getRef().getType());
}
list.set(i, cst);
}
list.setImmutable();
return new CstArray(list);
}
/** {@inheritDoc} */
@Override
protected void place0(Section addedTo, int offset) {
// Encode the data and note the size.
ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
encodeOutput(addedTo.getFile(), out);
encodedForm = out.toByteArray();
setWriteSize(encodedForm.length);
}
/**
* Writes out the encoded form of this instance.
*
* @param file {@code non-null;} file this instance is part of
* @param out {@code non-null;} where to write to
*/
private void encodeOutput(DexFile file, AnnotatedOutput out) {
boolean annotates = out.annotates();
if (annotates) {
out.annotate(0, offsetString() + " class data for " +
thisClass.toHuman());
}
encodeSize(file, out, "static_fields", staticFields.size());
encodeSize(file, out, "instance_fields", instanceFields.size());
encodeSize(file, out, "direct_methods", directMethods.size());
encodeSize(file, out, "virtual_methods", virtualMethods.size());
encodeList(file, out, "static_fields", staticFields);
encodeList(file, out, "instance_fields", instanceFields);
encodeList(file, out, "direct_methods", directMethods);
encodeList(file, out, "virtual_methods", virtualMethods);
if (annotates) {
out.endAnnotation();
}
}
/**
* Helper for {@link #encodeOutput}, which writes out the given
* size value, annotating it as well (if annotations are enabled).
*
* @param file {@code non-null;} file this instance is part of
* @param out {@code non-null;} where to write to
* @param label {@code non-null;} the label for the purposes of annotation
* @param size {@code >= 0;} the size to write
*/
private static void encodeSize(DexFile file, AnnotatedOutput out,
String label, int size) {
if (out.annotates()) {
out.annotate(String.format(" %-21s %08x", label + "_size:",
size));
}
out.writeUnsignedLeb128(size);
}
/**
* Helper for {@link #encodeOutput}, which writes out the given
* list. It also annotates the items (if any and if annotations
* are enabled).
*
* @param file {@code non-null;} file this instance is part of
* @param out {@code non-null;} where to write to
* @param label {@code non-null;} the label for the purposes of annotation
* @param list {@code non-null;} the list in question
*/
private static void encodeList(DexFile file, AnnotatedOutput out,
String label, ArrayList<? extends EncodedMember> list) {
int size = list.size();
int lastIndex = 0;
if (size == 0) {
return;
}
if (out.annotates()) {
out.annotate(0, " " + label + ":");
}
for (int i = 0; i < size; i++) {
lastIndex = list.get(i).encode(file, out, lastIndex, i);
}
}
/** {@inheritDoc} */
@Override
public void writeTo0(DexFile file, AnnotatedOutput out) {
boolean annotates = out.annotates();
if (annotates) {
/*
* The output is to be annotated, so redo the work previously
* done by place0(), except this time annotations will actually
* get emitted.
*/
encodeOutput(file, out);
} else {
out.write(encodedForm);
}
}
}