Home | History | Annotate | Download | only in file
      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.dexgen.dex.file;
     18 
     19 import com.android.dexgen.util.AnnotatedOutput;
     20 import com.android.dexgen.util.ExceptionWithContext;
     21 import com.android.dexgen.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      */
     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