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