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.dx.dex.file;
     18 
     19 import com.android.dx.util.AnnotatedOutput;
     20 
     21 import java.util.Collection;
     22 
     23 /**
     24  * A section of a {@code .dex} file. Each section consists of a list
     25  * of items of some sort or other.
     26  */
     27 public abstract class Section {
     28     /** {@code null-ok;} name of this part, for annotation purposes */
     29     private final String name;
     30 
     31     /** {@code non-null;} file that this instance is part of */
     32     private final DexFile file;
     33 
     34     /** {@code > 0;} alignment requirement for the final output;
     35      * must be a power of 2 */
     36     private final int alignment;
     37 
     38     /** {@code >= -1;} offset from the start of the file to this part, or
     39      * {@code -1} if not yet known */
     40     private int fileOffset;
     41 
     42     /** whether {@link #prepare} has been called successfully on this
     43      * instance */
     44     private boolean prepared;
     45 
     46     /**
     47      * Validates an alignment.
     48      *
     49      * @param alignment the alignment
     50      * @throws IllegalArgumentException thrown if {@code alignment}
     51      * isn't a positive power of 2
     52      */
     53     public static void validateAlignment(int alignment) {
     54         if ((alignment <= 0) ||
     55             (alignment & (alignment - 1)) != 0) {
     56             throw new IllegalArgumentException("invalid alignment");
     57         }
     58     }
     59 
     60     /**
     61      * Constructs an instance. The file offset is initially unknown.
     62      *
     63      * @param name {@code null-ok;} the name of this instance, for annotation
     64      * purposes
     65      * @param file {@code non-null;} file that this instance is part of
     66      * @param alignment {@code > 0;} alignment requirement for the final output;
     67      * must be a power of 2
     68      */
     69     public Section(String name, DexFile file, int alignment) {
     70         if (file == null) {
     71             throw new NullPointerException("file == null");
     72         }
     73 
     74         validateAlignment(alignment);
     75 
     76         this.name = name;
     77         this.file = file;
     78         this.alignment = alignment;
     79         this.fileOffset = -1;
     80         this.prepared = false;
     81     }
     82 
     83     /**
     84      * Gets the file that this instance is part of.
     85      *
     86      * @return {@code non-null;} the file
     87      */
     88     public final DexFile getFile() {
     89         return file;
     90     }
     91 
     92     /**
     93      * Gets the alignment for this instance's final output.
     94      *
     95      * @return {@code > 0;} the alignment
     96      */
     97     public final int getAlignment() {
     98         return alignment;
     99     }
    100 
    101     /**
    102      * Gets the offset from the start of the file to this part. This
    103      * throws an exception if the offset has not yet been set.
    104      *
    105      * @return {@code >= 0;} the file offset
    106      */
    107     public final int getFileOffset() {
    108         if (fileOffset < 0) {
    109             throw new RuntimeException("fileOffset not set");
    110         }
    111 
    112         return fileOffset;
    113     }
    114 
    115     /**
    116      * Sets the file offset. It is only valid to call this method once
    117      * once per instance.
    118      *
    119      * @param fileOffset {@code >= 0;} the desired offset from the start of the
    120      * file where this for this instance
    121      * @return {@code >= 0;} the offset that this instance should be placed at
    122      * in order to meet its alignment constraint
    123      */
    124     public final int setFileOffset(int fileOffset) {
    125         if (fileOffset < 0) {
    126             throw new IllegalArgumentException("fileOffset < 0");
    127         }
    128 
    129         if (this.fileOffset >= 0) {
    130             throw new RuntimeException("fileOffset already set");
    131         }
    132 
    133         int mask = alignment - 1;
    134         fileOffset = (fileOffset + mask) & ~mask;
    135 
    136         this.fileOffset = fileOffset;
    137 
    138         return fileOffset;
    139     }
    140 
    141     /**
    142      * Writes this instance to the given raw data object.
    143      *
    144      * @param out {@code non-null;} where to write to
    145      */
    146     public final void writeTo(AnnotatedOutput out) {
    147         throwIfNotPrepared();
    148         align(out);
    149 
    150         int cursor = out.getCursor();
    151 
    152         if (fileOffset < 0) {
    153             fileOffset = cursor;
    154         } else if (fileOffset != cursor) {
    155             throw new RuntimeException("alignment mismatch: for " + this +
    156                                        ", at " + cursor +
    157                                        ", but expected " + fileOffset);
    158         }
    159 
    160         if (out.annotates()) {
    161             if (name != null) {
    162                 out.annotate(0, "\n" + name + ":");
    163             } else if (cursor != 0) {
    164                 out.annotate(0, "\n");
    165             }
    166         }
    167 
    168         writeTo0(out);
    169     }
    170 
    171     /**
    172      * Returns the absolute file offset, given an offset from the
    173      * start of this instance's output. This is only valid to call
    174      * once this instance has been assigned a file offset (via {@link
    175      * #setFileOffset}).
    176      *
    177      * @param relative {@code >= 0;} the relative offset
    178      * @return {@code >= 0;} the corresponding absolute file offset
    179      */
    180     public final int getAbsoluteOffset(int relative) {
    181         if (relative < 0) {
    182             throw new IllegalArgumentException("relative < 0");
    183         }
    184 
    185         if (fileOffset < 0) {
    186             throw new RuntimeException("fileOffset not yet set");
    187         }
    188 
    189         return fileOffset + relative;
    190     }
    191 
    192     /**
    193      * Returns the absolute file offset of the given item which must
    194      * be contained in this section. This is only valid to call
    195      * once this instance has been assigned a file offset (via {@link
    196      * #setFileOffset}).
    197      *
    198      * <p><b>Note:</b> Subclasses must implement this as appropriate for
    199      * their contents.</p>
    200      *
    201      * @param item {@code non-null;} the item in question
    202      * @return {@code >= 0;} the item's absolute file offset
    203      */
    204     public abstract int getAbsoluteItemOffset(Item item);
    205 
    206     /**
    207      * Prepares this instance for writing. This performs any necessary
    208      * prerequisites, including particularly adding stuff to other
    209      * sections. This method may only be called once per instance;
    210      * subsequent calls will throw an exception.
    211      */
    212     public final void prepare() {
    213         throwIfPrepared();
    214         prepare0();
    215         prepared = true;
    216     }
    217 
    218     /**
    219      * Gets the collection of all the items in this section.
    220      * It is not valid to attempt to change the returned list.
    221      *
    222      * @return {@code non-null;} the items
    223      */
    224     public abstract Collection<? extends Item> items();
    225 
    226     /**
    227      * Does the main work of {@link #prepare}.
    228      */
    229     protected abstract void prepare0();
    230 
    231     /**
    232      * Gets the size of this instance when output, in bytes.
    233      *
    234      * @return {@code >= 0;} the size of this instance, in bytes
    235      */
    236     public abstract int writeSize();
    237 
    238     /**
    239      * Throws an exception if {@link #prepare} has not been
    240      * called on this instance.
    241      */
    242     protected final void throwIfNotPrepared() {
    243         if (!prepared) {
    244             throw new RuntimeException("not prepared");
    245         }
    246     }
    247 
    248     /**
    249      * Throws an exception if {@link #prepare} has already been called
    250      * on this instance.
    251      */
    252     protected final void throwIfPrepared() {
    253         if (prepared) {
    254             throw new RuntimeException("already prepared");
    255         }
    256     }
    257 
    258     /**
    259      * Aligns the output of the given data to the alignment of this instance.
    260      *
    261      * @param out {@code non-null;} the output to align
    262      */
    263     protected final void align(AnnotatedOutput out) {
    264         out.alignTo(alignment);
    265     }
    266 
    267     /**
    268      * Writes this instance to the given raw data object. This gets
    269      * called by {@link #writeTo} after aligning the cursor of
    270      * {@code out} and verifying that either the assigned file
    271      * offset matches the actual cursor {@code out} or that the
    272      * file offset was not previously assigned, in which case it gets
    273      * assigned to {@code out}'s cursor.
    274      *
    275      * @param out {@code non-null;} where to write to
    276      */
    277     protected abstract void writeTo0(AnnotatedOutput out);
    278 
    279     /**
    280      * Returns the name of this section, for annotation purposes.
    281      *
    282      * @return {@code null-ok;} name of this part, for annotation purposes
    283      */
    284     protected final String getName() {
    285         return name;
    286     }
    287 }
    288