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      */
validateAlignment(int alignment)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      */
Section(String name, DexFile file, int alignment)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      */
getFile()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      */
getAlignment()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      */
getFileOffset()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      */
setFileOffset(int fileOffset)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      */
writeTo(AnnotatedOutput out)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      */
getAbsoluteOffset(int relative)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      */
getAbsoluteItemOffset(Item item)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      */
prepare()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      */
items()223     public abstract Collection<? extends Item> items();
224 
225     /**
226      * Does the main work of {@link #prepare}.
227      */
prepare0()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      */
writeSize()235     public abstract int writeSize();
236 
237     /**
238      * Throws an exception if {@link #prepare} has not been
239      * called on this instance.
240      */
throwIfNotPrepared()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      */
throwIfPrepared()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      */
align(AnnotatedOutput out)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      */
writeTo0(AnnotatedOutput out)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      */
getName()283     protected final String getName() {
284         return name;
285     }
286 }
287