1/*
2 * Copyright (C) 2007 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.util.AnnotatedOutput;
20import com.android.dx.util.ExceptionWithContext;
21import com.android.dx.util.Hex;
22
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Comparator;
28import java.util.HashMap;
29import java.util.Map;
30import java.util.NoSuchElementException;
31import java.util.TreeMap;
32
33/**
34 * A section of a {@code .dex} file which consists of a sequence of
35 * {@link OffsettedItem} objects, which may each be of a different concrete
36 * class and/or size.
37 *
38 * <b>Note:</b> It is invalid for an item in an instance of this class to
39 * have a larger alignment requirement than the alignment of this instance.
40 */
41public final class MixedItemSection extends Section {
42    static enum SortType {
43        /** no sorting */
44        NONE,
45
46        /** sort by type only */
47        TYPE,
48
49        /** sort in class-major order, with instances sorted per-class */
50        INSTANCE;
51    };
52
53    /** {@code non-null;} sorter which sorts instances by type */
54    private static final Comparator<OffsettedItem> TYPE_SORTER =
55        new Comparator<OffsettedItem>() {
56        public int compare(OffsettedItem item1, OffsettedItem item2) {
57            ItemType type1 = item1.itemType();
58            ItemType type2 = item2.itemType();
59            return type1.compareTo(type2);
60        }
61    };
62
63    /** {@code non-null;} the items in this part */
64    private final ArrayList<OffsettedItem> items;
65
66    /** {@code non-null;} items that have been explicitly interned */
67    private final HashMap<OffsettedItem, OffsettedItem> interns;
68
69    /** {@code non-null;} how to sort the items */
70    private final SortType sort;
71
72    /**
73     * {@code >= -1;} the current size of this part, in bytes, or {@code -1}
74     * if not yet calculated
75     */
76    private int writeSize;
77
78    /**
79     * Constructs an instance. The file offset is initially unknown.
80     *
81     * @param name {@code null-ok;} the name of this instance, for annotation
82     * purposes
83     * @param file {@code non-null;} file that this instance is part of
84     * @param alignment {@code > 0;} alignment requirement for the final output;
85     * must be a power of 2
86     * @param sort how the items should be sorted in the final output
87     */
88    public MixedItemSection(String name, DexFile file, int alignment,
89            SortType sort) {
90        super(name, file, alignment);
91
92        this.items = new ArrayList<OffsettedItem>(100);
93        this.interns = new HashMap<OffsettedItem, OffsettedItem>(100);
94        this.sort = sort;
95        this.writeSize = -1;
96    }
97
98    /** {@inheritDoc} */
99    @Override
100    public Collection<? extends Item> items() {
101        return items;
102    }
103
104    /** {@inheritDoc} */
105    @Override
106    public int writeSize() {
107        throwIfNotPrepared();
108        return writeSize;
109    }
110
111    /** {@inheritDoc} */
112    @Override
113    public int getAbsoluteItemOffset(Item item) {
114        OffsettedItem oi = (OffsettedItem) item;
115        return oi.getAbsoluteOffset();
116    }
117
118    /**
119     * Gets the size of this instance, in items.
120     *
121     * @return {@code >= 0;} the size
122     */
123    public int size() {
124        return items.size();
125    }
126
127    /**
128     * Writes the portion of the file header that refers to this instance.
129     *
130     * @param out {@code non-null;} where to write
131     */
132    public void writeHeaderPart(AnnotatedOutput out) {
133        throwIfNotPrepared();
134
135        if (writeSize == -1) {
136            throw new RuntimeException("write size not yet set");
137        }
138
139        int sz = writeSize;
140        int offset = (sz == 0) ? 0 : getFileOffset();
141        String name = getName();
142
143        if (name == null) {
144            name = "<unnamed>";
145        }
146
147        int spaceCount = 15 - name.length();
148        char[] spaceArr = new char[spaceCount];
149        Arrays.fill(spaceArr, ' ');
150        String spaces = new String(spaceArr);
151
152        if (out.annotates()) {
153            out.annotate(4, name + "_size:" + spaces + Hex.u4(sz));
154            out.annotate(4, name + "_off: " + spaces + Hex.u4(offset));
155        }
156
157        out.writeInt(sz);
158        out.writeInt(offset);
159    }
160
161    /**
162     * Adds an item to this instance. This will in turn tell the given item
163     * that it has been added to this instance. It is invalid to add the
164     * same item to more than one instance, nor to add the same items
165     * multiple times to a single instance.
166     *
167     * @param item {@code non-null;} the item to add
168     */
169    public void add(OffsettedItem item) {
170        throwIfPrepared();
171
172        try {
173            if (item.getAlignment() > getAlignment()) {
174                throw new IllegalArgumentException(
175                        "incompatible item alignment");
176            }
177        } catch (NullPointerException ex) {
178            // Elucidate the exception.
179            throw new NullPointerException("item == null");
180        }
181
182        items.add(item);
183    }
184
185    /**
186     * Interns an item in this instance, returning the interned instance
187     * (which may not be the one passed in). This will add the item if no
188     * equal item has been added.
189     *
190     * @param item {@code non-null;} the item to intern
191     * @return {@code non-null;} the equivalent interned instance
192     */
193    public <T extends OffsettedItem> T intern(T item) {
194        throwIfPrepared();
195
196        OffsettedItem result = interns.get(item);
197
198        if (result != null) {
199            return (T) result;
200        }
201
202        add(item);
203        interns.put(item, item);
204        return item;
205    }
206
207    /**
208     * Gets an item which was previously interned.
209     *
210     * @param item {@code non-null;} the item to look for
211     * @return {@code non-null;} the equivalent already-interned instance
212     */
213    public <T extends OffsettedItem> T get(T item) {
214        throwIfNotPrepared();
215
216        OffsettedItem result = interns.get(item);
217
218        if (result != null) {
219            return (T) result;
220        }
221
222        throw new NoSuchElementException(item.toString());
223    }
224
225    /**
226     * Writes an index of contents of the items in this instance of the
227     * given type. If there are none, this writes nothing. If there are any,
228     * then the index is preceded by the given intro string.
229     *
230     * @param out {@code non-null;} where to write to
231     * @param itemType {@code non-null;} the item type of interest
232     * @param intro {@code non-null;} the introductory string for non-empty indices
233     */
234    public void writeIndexAnnotation(AnnotatedOutput out, ItemType itemType,
235            String intro) {
236        throwIfNotPrepared();
237
238        TreeMap<String, OffsettedItem> index =
239            new TreeMap<String, OffsettedItem>();
240
241        for (OffsettedItem item : items) {
242            if (item.itemType() == itemType) {
243                String label = item.toHuman();
244                index.put(label, item);
245            }
246        }
247
248        if (index.size() == 0) {
249            return;
250        }
251
252        out.annotate(0, intro);
253
254        for (Map.Entry<String, OffsettedItem> entry : index.entrySet()) {
255            String label = entry.getKey();
256            OffsettedItem item = entry.getValue();
257            out.annotate(0, item.offsetString() + ' ' + label + '\n');
258        }
259    }
260
261    /** {@inheritDoc} */
262    @Override
263    protected void prepare0() {
264        DexFile file = getFile();
265
266        /*
267         * It's okay for new items to be added as a result of an
268         * addContents() call; we just have to deal with the possibility.
269         */
270
271        int i = 0;
272        for (;;) {
273            int sz = items.size();
274            if (i >= sz) {
275                break;
276            }
277
278            for (/*i*/; i < sz; i++) {
279                OffsettedItem one = items.get(i);
280                one.addContents(file);
281            }
282        }
283    }
284
285    /**
286     * Places all the items in this instance at particular offsets. This
287     * will call {@link OffsettedItem#place} on each item. If an item
288     * does not know its write size before the call to {@code place},
289     * it is that call which is responsible for setting the write size.
290     * This method may only be called once per instance; subsequent calls
291     * will throw an exception.
292     */
293    public void placeItems() {
294        throwIfNotPrepared();
295
296        switch (sort) {
297            case INSTANCE: {
298                Collections.sort(items);
299                break;
300            }
301            case TYPE: {
302                Collections.sort(items, TYPE_SORTER);
303                break;
304            }
305        }
306
307        int sz = items.size();
308        int outAt = 0;
309        for (int i = 0; i < sz; i++) {
310            OffsettedItem one = items.get(i);
311            try {
312                int placedAt = one.place(this, outAt);
313
314                if (placedAt < outAt) {
315                    throw new RuntimeException("bogus place() result for " +
316                            one);
317                }
318
319                outAt = placedAt + one.writeSize();
320            } catch (RuntimeException ex) {
321                throw ExceptionWithContext.withContext(ex,
322                        "...while placing " + one);
323            }
324        }
325
326        writeSize = outAt;
327    }
328
329    /** {@inheritDoc} */
330    @Override
331    protected void writeTo0(AnnotatedOutput out) {
332        boolean annotates = out.annotates();
333        boolean first = true;
334        DexFile file = getFile();
335        int at = 0;
336
337        for (OffsettedItem one : items) {
338            if (annotates) {
339                if (first) {
340                    first = false;
341                } else {
342                    out.annotate(0, "\n");
343                }
344            }
345
346            int alignMask = one.getAlignment() - 1;
347            int writeAt = (at + alignMask) & ~alignMask;
348
349            if (at != writeAt) {
350                out.writeZeroes(writeAt - at);
351                at = writeAt;
352            }
353
354            one.writeTo(file, out);
355            at += one.writeSize();
356        }
357
358        if (at != writeSize) {
359            throw new RuntimeException("output size mismatch");
360        }
361    }
362}
363