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