1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dexgen.dex.file;
18
19import com.android.dexgen.rop.cst.Constant;
20import com.android.dexgen.rop.cst.CstArray;
21import com.android.dexgen.rop.cst.CstLiteralBits;
22import com.android.dexgen.rop.cst.CstType;
23import com.android.dexgen.rop.cst.Zeroes;
24import com.android.dexgen.util.AnnotatedOutput;
25import com.android.dexgen.util.ByteArrayAnnotatedOutput;
26import com.android.dexgen.util.Hex;
27import com.android.dexgen.util.Writers;
28
29import java.io.PrintWriter;
30import java.io.Writer;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collections;
34import java.util.List;
35import java.util.HashMap;
36
37/**
38 * Representation of all the parts of a Dalvik class that are generally
39 * "inflated" into an in-memory representation at runtime. Instances of
40 * this class are represented in a compact streamable form in a
41 * {@code dex} file, as opposed to a random-access form.
42 */
43public final class ClassDataItem extends OffsettedItem {
44    /** {@code non-null;} what class this data is for, just for listing generation */
45    private final CstType thisClass;
46
47    /** {@code non-null;} list of static fields */
48    private final ArrayList<EncodedField> staticFields;
49
50    /** {@code non-null;} list of initial values for static fields */
51    private final HashMap<EncodedField, Constant> staticValues;
52
53    /** {@code non-null;} list of instance fields */
54    private final ArrayList<EncodedField> instanceFields;
55
56    /** {@code non-null;} list of direct methods */
57    private final ArrayList<EncodedMethod> directMethods;
58
59    /** {@code non-null;} list of virtual methods */
60    private final ArrayList<EncodedMethod> virtualMethods;
61
62    /** {@code null-ok;} static initializer list; set in {@link #addContents} */
63    private CstArray staticValuesConstant;
64
65    /**
66     * {@code null-ok;} encoded form, ready for writing to a file; set during
67     * {@link #place0}
68     */
69    private byte[] encodedForm;
70
71    /**
72     * Constructs an instance. Its sets of members are initially
73     * empty.
74     *
75     * @param thisClass {@code non-null;} what class this data is for, just
76     * for listing generation
77     */
78    public ClassDataItem(CstType thisClass) {
79        super(1, -1);
80
81        if (thisClass == null) {
82            throw new NullPointerException("thisClass == null");
83        }
84
85        this.thisClass = thisClass;
86        this.staticFields = new ArrayList<EncodedField>(20);
87        this.staticValues = new HashMap<EncodedField, Constant>(40);
88        this.instanceFields = new ArrayList<EncodedField>(20);
89        this.directMethods = new ArrayList<EncodedMethod>(20);
90        this.virtualMethods = new ArrayList<EncodedMethod>(20);
91        this.staticValuesConstant = null;
92    }
93
94    /** {@inheritDoc} */
95    @Override
96    public ItemType itemType() {
97        return ItemType.TYPE_CLASS_DATA_ITEM;
98    }
99
100    /** {@inheritDoc} */
101    @Override
102    public String toHuman() {
103        return toString();
104    }
105
106    /**
107     * Returns whether this instance is empty.
108     *
109     * @return {@code true} if this instance is empty or
110     * {@code false} if at least one element has been added to it
111     */
112    public boolean isEmpty() {
113        return staticFields.isEmpty() && instanceFields.isEmpty()
114            && directMethods.isEmpty() && virtualMethods.isEmpty();
115    }
116
117    /**
118     * Adds a static field.
119     *
120     * @param field {@code non-null;} the field to add
121     * @param value {@code null-ok;} initial value for the field, if any
122     */
123    public void addStaticField(EncodedField field, Constant value) {
124        if (field == null) {
125            throw new NullPointerException("field == null");
126        }
127
128        if (staticValuesConstant != null) {
129            throw new UnsupportedOperationException(
130                    "static fields already sorted");
131        }
132
133        staticFields.add(field);
134        staticValues.put(field, value);
135    }
136
137    /**
138     * Adds an instance field.
139     *
140     * @param field {@code non-null;} the field to add
141     */
142    public void addInstanceField(EncodedField field) {
143        if (field == null) {
144            throw new NullPointerException("field == null");
145        }
146
147        instanceFields.add(field);
148    }
149
150    /**
151     * Adds a direct ({@code static} and/or {@code private}) method.
152     *
153     * @param method {@code non-null;} the method to add
154     */
155    public void addDirectMethod(EncodedMethod method) {
156        if (method == null) {
157            throw new NullPointerException("method == null");
158        }
159
160        directMethods.add(method);
161    }
162
163    /**
164     * Adds a virtual method.
165     *
166     * @param method {@code non-null;} the method to add
167     */
168    public void addVirtualMethod(EncodedMethod method) {
169        if (method == null) {
170            throw new NullPointerException("method == null");
171        }
172
173        virtualMethods.add(method);
174    }
175
176    /**
177     * Gets all the methods in this class. The returned list is not linked
178     * in any way to the underlying lists contained in this instance, but
179     * the objects contained in the list are shared.
180     *
181     * @return {@code non-null;} list of all methods
182     */
183    public ArrayList<EncodedMethod> getMethods() {
184        int sz = directMethods.size() + virtualMethods.size();
185        ArrayList<EncodedMethod> result = new ArrayList<EncodedMethod>(sz);
186
187        result.addAll(directMethods);
188        result.addAll(virtualMethods);
189
190        return result;
191    }
192
193
194    /**
195     * Prints out the contents of this instance, in a debugging-friendly
196     * way.
197     *
198     * @param out {@code non-null;} where to output to
199     * @param verbose whether to be verbose with the output
200     */
201    public void debugPrint(Writer out, boolean verbose) {
202        PrintWriter pw = Writers.printWriterFor(out);
203
204        int sz = staticFields.size();
205        for (int i = 0; i < sz; i++) {
206            pw.println("  sfields[" + i + "]: " + staticFields.get(i));
207        }
208
209        sz = instanceFields.size();
210        for (int i = 0; i < sz; i++) {
211            pw.println("  ifields[" + i + "]: " + instanceFields.get(i));
212        }
213
214        sz = directMethods.size();
215        for (int i = 0; i < sz; i++) {
216            pw.println("  dmeths[" + i + "]:");
217            directMethods.get(i).debugPrint(pw, verbose);
218        }
219
220        sz = virtualMethods.size();
221        for (int i = 0; i < sz; i++) {
222            pw.println("  vmeths[" + i + "]:");
223            virtualMethods.get(i).debugPrint(pw, verbose);
224        }
225    }
226
227    /** {@inheritDoc} */
228    @Override
229    public void addContents(DexFile file) {
230        if (!staticFields.isEmpty()) {
231            getStaticValuesConstant(); // Force the fields to be sorted.
232            for (EncodedField field : staticFields) {
233                field.addContents(file);
234            }
235        }
236
237        if (!instanceFields.isEmpty()) {
238            Collections.sort(instanceFields);
239            for (EncodedField field : instanceFields) {
240                field.addContents(file);
241            }
242        }
243
244        if (!directMethods.isEmpty()) {
245            Collections.sort(directMethods);
246            for (EncodedMethod method : directMethods) {
247                method.addContents(file);
248            }
249        }
250
251        if (!virtualMethods.isEmpty()) {
252            Collections.sort(virtualMethods);
253            for (EncodedMethod method : virtualMethods) {
254                method.addContents(file);
255            }
256        }
257    }
258
259    /**
260     * Gets a {@link CstArray} corresponding to {@link #staticValues} if
261     * it contains any non-zero non-{@code null} values.
262     *
263     * @return {@code null-ok;} the corresponding constant or {@code null} if
264     * there are no values to encode
265     */
266    public CstArray getStaticValuesConstant() {
267        if ((staticValuesConstant == null) && (staticFields.size() != 0)) {
268            staticValuesConstant = makeStaticValuesConstant();
269        }
270
271        return staticValuesConstant;
272    }
273
274    /**
275     * Gets a {@link CstArray} corresponding to {@link #staticValues} if
276     * it contains any non-zero non-{@code null} values.
277     *
278     * @return {@code null-ok;} the corresponding constant or {@code null} if
279     * there are no values to encode
280     */
281    private CstArray makeStaticValuesConstant() {
282        // First sort the statics into their final order.
283        Collections.sort(staticFields);
284
285        /*
286         * Get the size of staticValues minus any trailing zeros/nulls (both
287         * nulls per se as well as instances of CstKnownNull).
288         */
289
290        int size = staticFields.size();
291        while (size > 0) {
292            EncodedField field = staticFields.get(size - 1);
293            Constant cst = staticValues.get(field);
294            if (cst instanceof CstLiteralBits) {
295                // Note: CstKnownNull extends CstLiteralBits.
296                if (((CstLiteralBits) cst).getLongBits() != 0) {
297                    break;
298                }
299            } else if (cst != null) {
300                break;
301            }
302            size--;
303        }
304
305        if (size == 0) {
306            return null;
307        }
308
309        // There is something worth encoding, so build up a result.
310
311        CstArray.List list = new CstArray.List(size);
312        for (int i = 0; i < size; i++) {
313            EncodedField field = staticFields.get(i);
314            Constant cst = staticValues.get(field);
315            if (cst == null) {
316                cst = Zeroes.zeroFor(field.getRef().getType());
317            }
318            list.set(i, cst);
319        }
320        list.setImmutable();
321
322        return new CstArray(list);
323    }
324
325    /** {@inheritDoc} */
326    @Override
327    protected void place0(Section addedTo, int offset) {
328        // Encode the data and note the size.
329
330        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
331
332        encodeOutput(addedTo.getFile(), out);
333        encodedForm = out.toByteArray();
334        setWriteSize(encodedForm.length);
335    }
336
337    /**
338     * Writes out the encoded form of this instance.
339     *
340     * @param file {@code non-null;} file this instance is part of
341     * @param out {@code non-null;} where to write to
342     */
343    private void encodeOutput(DexFile file, AnnotatedOutput out) {
344        boolean annotates = out.annotates();
345
346        if (annotates) {
347            out.annotate(0, offsetString() + " class data for " +
348                    thisClass.toHuman());
349        }
350
351        encodeSize(file, out, "static_fields", staticFields.size());
352        encodeSize(file, out, "instance_fields", instanceFields.size());
353        encodeSize(file, out, "direct_methods", directMethods.size());
354        encodeSize(file, out, "virtual_methods", virtualMethods.size());
355
356        encodeList(file, out, "static_fields", staticFields);
357        encodeList(file, out, "instance_fields", instanceFields);
358        encodeList(file, out, "direct_methods", directMethods);
359        encodeList(file, out, "virtual_methods", virtualMethods);
360
361        if (annotates) {
362            out.endAnnotation();
363        }
364    }
365
366    /**
367     * Helper for {@link #encodeOutput}, which writes out the given
368     * size value, annotating it as well (if annotations are enabled).
369     *
370     * @param file {@code non-null;} file this instance is part of
371     * @param out {@code non-null;} where to write to
372     * @param label {@code non-null;} the label for the purposes of annotation
373     * @param size {@code >= 0;} the size to write
374     */
375    private static void encodeSize(DexFile file, AnnotatedOutput out,
376            String label, int size) {
377        if (out.annotates()) {
378            out.annotate(String.format("  %-21s %08x", label + "_size:",
379                            size));
380        }
381
382        out.writeUnsignedLeb128(size);
383    }
384
385    /**
386     * Helper for {@link #encodeOutput}, which writes out the given
387     * list. It also annotates the items (if any and if annotations
388     * are enabled).
389     *
390     * @param file {@code non-null;} file this instance is part of
391     * @param out {@code non-null;} where to write to
392     * @param label {@code non-null;} the label for the purposes of annotation
393     * @param list {@code non-null;} the list in question
394     */
395    private static void encodeList(DexFile file, AnnotatedOutput out,
396            String label, ArrayList<? extends EncodedMember> list) {
397        int size = list.size();
398        int lastIndex = 0;
399
400        if (size == 0) {
401            return;
402        }
403
404        if (out.annotates()) {
405            out.annotate(0, "  " + label + ":");
406        }
407
408        for (int i = 0; i < size; i++) {
409            lastIndex = list.get(i).encode(file, out, lastIndex, i);
410        }
411    }
412
413    /** {@inheritDoc} */
414    @Override
415    public void writeTo0(DexFile file, AnnotatedOutput out) {
416        boolean annotates = out.annotates();
417
418        if (annotates) {
419            /*
420             * The output is to be annotated, so redo the work previously
421             * done by place0(), except this time annotations will actually
422             * get emitted.
423             */
424            encodeOutput(file, out);
425        } else {
426            out.write(encodedForm);
427        }
428    }
429}
430