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