• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.dx.dex.file;
18 
19 import com.android.dx.util.AnnotatedOutput;
20 import com.android.dx.util.ExceptionWithContext;
21 import com.android.dx.util.Hex;
22 
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.NoSuchElementException;
31 import 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  */
41 public 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      */
MixedItemSection(String name, DexFile file, int alignment, SortType sort)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
items()100     public Collection<? extends Item> items() {
101         return items;
102     }
103 
104     /** {@inheritDoc} */
105     @Override
writeSize()106     public int writeSize() {
107         throwIfNotPrepared();
108         return writeSize;
109     }
110 
111     /** {@inheritDoc} */
112     @Override
getAbsoluteItemOffset(Item item)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      */
size()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      */
writeHeaderPart(AnnotatedOutput out)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      */
add(OffsettedItem item)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      */
intern(T item)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      */
get(T item)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      */
writeIndexAnnotation(AnnotatedOutput out, ItemType itemType, String intro)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
prepare0()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      */
placeItems()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
writeTo0(AnnotatedOutput out)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