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