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