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