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
17package com.android.dx.dex.file;
18
19import com.android.dx.util.AnnotatedOutput;
20
21import 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 */
27public 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