summaryrefslogtreecommitdiffstats
path: root/dx/src/com/android/dx/dex/file/Section.java
blob: bde714c46d1e5257d432cda4a6cfc45c61050e43 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.file;

import com.android.dx.util.AnnotatedOutput;
import java.util.Collection;

/**
 * A section of a {@code .dex} file. Each section consists of a list
 * of items of some sort or other.
 */
public abstract class Section {
    /** {@code null-ok;} name of this part, for annotation purposes */
    private final String name;

    /** {@code non-null;} file that this instance is part of */
    private final DexFile file;

    /** {@code > 0;} alignment requirement for the final output;
     * must be a power of 2 */
    private final int alignment;

    /** {@code >= -1;} offset from the start of the file to this part, or
     * {@code -1} if not yet known */
    private int fileOffset;

    /** whether {@link #prepare} has been called successfully on this
     * instance */
    private boolean prepared;

    /**
     * Validates an alignment.
     *
     * @param alignment the alignment
     * @throws IllegalArgumentException thrown if {@code alignment}
     * isn't a positive power of 2
     */
    public static void validateAlignment(int alignment) {
        if ((alignment <= 0) ||
            (alignment & (alignment - 1)) != 0) {
            throw new IllegalArgumentException("invalid alignment");
        }
    }

    /**
     * Constructs an instance. The file offset is initially unknown.
     *
     * @param name {@code null-ok;} the name of this instance, for annotation
     * purposes
     * @param file {@code non-null;} file that this instance is part of
     * @param alignment {@code > 0;} alignment requirement for the final output;
     * must be a power of 2
     */
    public Section(String name, DexFile file, int alignment) {
        if (file == null) {
            throw new NullPointerException("file == null");
        }

        validateAlignment(alignment);

        this.name = name;
        this.file = file;
        this.alignment = alignment;
        this.fileOffset = -1;
        this.prepared = false;
    }

    /**
     * Gets the file that this instance is part of.
     *
     * @return {@code non-null;} the file
     */
    public final DexFile getFile() {
        return file;
    }

    /**
     * Gets the alignment for this instance's final output.
     *
     * @return {@code > 0;} the alignment
     */
    public final int getAlignment() {
        return alignment;
    }

    /**
     * Gets the offset from the start of the file to this part. This
     * throws an exception if the offset has not yet been set.
     *
     * @return {@code >= 0;} the file offset
     */
    public final int getFileOffset() {
        if (fileOffset < 0) {
            throw new RuntimeException("fileOffset not set");
        }

        return fileOffset;
    }

    /**
     * Sets the file offset. It is only valid to call this method once
     * once per instance.
     *
     * @param fileOffset {@code >= 0;} the desired offset from the start of the
     * file where this for this instance
     * @return {@code >= 0;} the offset that this instance should be placed at
     * in order to meet its alignment constraint
     */
    public final int setFileOffset(int fileOffset) {
        if (fileOffset < 0) {
            throw new IllegalArgumentException("fileOffset < 0");
        }

        if (this.fileOffset >= 0) {
            throw new RuntimeException("fileOffset already set");
        }

        int mask = alignment - 1;
        fileOffset = (fileOffset + mask) & ~mask;

        this.fileOffset = fileOffset;

        return fileOffset;
    }

    /**
     * Writes this instance to the given raw data object.
     *
     * @param out {@code non-null;} where to write to
     */
    public final void writeTo(AnnotatedOutput out) {
        throwIfNotPrepared();
        align(out);

        int cursor = out.getCursor();

        if (fileOffset < 0) {
            fileOffset = cursor;
        } else if (fileOffset != cursor) {
            throw new RuntimeException("alignment mismatch: for " + this +
                                       ", at " + cursor +
                                       ", but expected " + fileOffset);
        }

        if (out.annotates()) {
            if (name != null) {
                out.annotate(0, "\n" + name + ":");
            } else if (cursor != 0) {
                out.annotate(0, "\n");
            }
        }

        writeTo0(out);
    }

    /**
     * Returns the absolute file offset, given an offset from the
     * start of this instance's output. This is only valid to call
     * once this instance has been assigned a file offset (via {@link
     * #setFileOffset}).
     *
     * @param relative {@code >= 0;} the relative offset
     * @return {@code >= 0;} the corresponding absolute file offset
     */
    public final int getAbsoluteOffset(int relative) {
        if (relative < 0) {
            throw new IllegalArgumentException("relative < 0");
        }

        if (fileOffset < 0) {
            throw new RuntimeException("fileOffset not yet set");
        }

        return fileOffset + relative;
    }

    /**
     * Returns the absolute file offset of the given item which must
     * be contained in this section. This is only valid to call
     * once this instance has been assigned a file offset (via {@link
     * #setFileOffset}).
     *
     * <p><b>Note:</b> Subclasses must implement this as appropriate for
     * their contents.</p>
     *
     * @param item {@code non-null;} the item in question
     * @return {@code >= 0;} the item's absolute file offset
     */
    public abstract int getAbsoluteItemOffset(Item item);

    /**
     * Prepares this instance for writing. This performs any necessary
     * prerequisites, including particularly adding stuff to other
     * sections. This method may only be called once per instance;
     * subsequent calls will throw an exception.
     */
    public final void prepare() {
        throwIfPrepared();
        prepare0();
        prepared = true;
    }

    /**
     * Gets the collection of all the items in this section.
     * It is not valid to attempt to change the returned list.
     *
     * @return {@code non-null;} the items
     */
    public abstract Collection<? extends Item> items();

    /**
     * Does the main work of {@link #prepare}.
     */
    protected abstract void prepare0();

    /**
     * Gets the size of this instance when output, in bytes.
     *
     * @return {@code >= 0;} the size of this instance, in bytes
     */
    public abstract int writeSize();

    /**
     * Throws an exception if {@link #prepare} has not been
     * called on this instance.
     */
    protected final void throwIfNotPrepared() {
        if (!prepared) {
            throw new RuntimeException("not prepared");
        }
    }

    /**
     * Throws an exception if {@link #prepare} has already been called
     * on this instance.
     */
    protected final void throwIfPrepared() {
        if (prepared) {
            throw new RuntimeException("already prepared");
        }
    }

    /**
     * Aligns the output of the given data to the alignment of this instance.
     *
     * @param out {@code non-null;} the output to align
     */
    protected final void align(AnnotatedOutput out) {
        out.alignTo(alignment);
    }

    /**
     * Writes this instance to the given raw data object. This gets
     * called by {@link #writeTo} after aligning the cursor of
     * {@code out} and verifying that either the assigned file
     * offset matches the actual cursor {@code out} or that the
     * file offset was not previously assigned, in which case it gets
     * assigned to {@code out}'s cursor.
     *
     * @param out {@code non-null;} where to write to
     */
    protected abstract void writeTo0(AnnotatedOutput out);

    /**
     * Returns the name of this section, for annotation purposes.
     *
     * @return {@code null-ok;} name of this part, for annotation purposes
     */
    protected final String getName() {
        return name;
    }
}