summaryrefslogtreecommitdiff
path: root/dx/src/com/android/dx/dex/file/OffsettedItem.java
blob: 772147066a7f97d6015f1a8aea6d9e82285f5187 (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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/*
 * 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 com.android.dx.util.ExceptionWithContext;

/**
 * An item in a Dalvik file which is referenced by absolute offset.
 */
public abstract class OffsettedItem extends Item
        implements Comparable<OffsettedItem> {
    /** {@code > 0;} alignment requirement */
    private final int alignment;

    /** {@code >= -1;} the size of this instance when written, in bytes, or
     * {@code -1} if not yet known */
    private int writeSize;

    /**
     * {@code null-ok;} section the item was added to, or {@code null} if
     * not yet added
     */
    private Section addedTo;

    /**
     * {@code >= -1;} assigned offset of the item from the start of its section,
     * or {@code -1} if not yet assigned
     */
    private int offset;

    /**
     * Gets the absolute offset of the given item, returning {@code 0}
     * if handed {@code null}.
     *
     * @param item {@code null-ok;} the item in question
     * @return {@code >= 0;} the item's absolute offset, or {@code 0}
     * if {@code item == null}
     */
    public static int getAbsoluteOffsetOr0(OffsettedItem item) {
        if (item == null) {
            return 0;
        }

        return item.getAbsoluteOffset();
    }

    /**
     * Constructs an instance. The offset is initially unassigned.
     *
     * @param alignment {@code > 0;} output alignment requirement; must be a
     * power of 2
     * @param writeSize {@code >= -1;} the size of this instance when written,
     * in bytes, or {@code -1} if not immediately known
     */
    public OffsettedItem(int alignment, int writeSize) {
        Section.validateAlignment(alignment);

        if (writeSize < -1) {
            throw new IllegalArgumentException("writeSize < -1");
        }

        this.alignment = alignment;
        this.writeSize = writeSize;
        this.addedTo = null;
        this.offset = -1;
    }

    /**
     * {@inheritDoc}
     *
     * Comparisons for this class are defined to be type-major (if the
     * types don't match then the objects are not equal), with
     * {@link #compareTo0} deciding same-type comparisons.
     */
    @Override
    public final boolean equals(Object other) {
        if (this == other) {
            return true;
        }

        OffsettedItem otherItem = (OffsettedItem) other;
        ItemType thisType = itemType();
        ItemType otherType = otherItem.itemType();

        if (thisType != otherType) {
            return false;
        }

        return (compareTo0(otherItem) == 0);
    }

    /**
     * {@inheritDoc}
     *
     * Comparisons for this class are defined to be class-major (if the
     * classes don't match then the objects are not equal), with
     * {@link #compareTo0} deciding same-class comparisons.
     */
    public final int compareTo(OffsettedItem other) {
        if (this == other) {
            return 0;
        }

        ItemType thisType = itemType();
        ItemType otherType = other.itemType();

        if (thisType != otherType) {
            return thisType.compareTo(otherType);
        }

        return compareTo0(other);
    }

    /**
     * Sets the write size of this item. This may only be called once
     * per instance, and only if the size was unknown upon instance
     * creation.
     *
     * @param writeSize {@code > 0;} the write size, in bytes
     */
    public final void setWriteSize(int writeSize) {
        if (writeSize < 0) {
            throw new IllegalArgumentException("writeSize < 0");
        }

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

        this.writeSize = writeSize;
    }

    /** {@inheritDoc}
     *
     * @throws UnsupportedOperationException thrown if the write size
     * is not yet known
     */
    @Override
    public final int writeSize() {
        if (writeSize < 0) {
            throw new UnsupportedOperationException("writeSize is unknown");
        }

        return writeSize;
    }

    /** {@inheritDoc} */
    @Override
    public final void writeTo(DexFile file, AnnotatedOutput out) {
        out.alignTo(alignment);

        try {
            if (writeSize < 0) {
                throw new UnsupportedOperationException(
                        "writeSize is unknown");
            }
            out.assertCursor(getAbsoluteOffset());
        } catch (RuntimeException ex) {
            throw ExceptionWithContext.withContext(ex,
                    "...while writing " + this);
        }

        writeTo0(file, out);
    }

    /**
     * Gets the relative item offset. The offset is from the start of
     * the section which the instance was written to.
     *
     * @return {@code >= 0;} the offset
     * @throws RuntimeException thrown if the offset is not yet known
     */
    public final int getRelativeOffset() {
        if (offset < 0) {
            throw new RuntimeException("offset not yet known");
        }

        return offset;
    }

    /**
     * Gets the absolute item offset. The offset is from the start of
     * the file which the instance was written to.
     *
     * @return {@code >= 0;} the offset
     * @throws RuntimeException thrown if the offset is not yet known
     */
    public final int getAbsoluteOffset() {
        if (offset < 0) {
            throw new RuntimeException("offset not yet known");
        }

        return addedTo.getAbsoluteOffset(offset);
    }

    /**
     * Indicates that this item has been added to the given section at
     * the given offset. It is only valid to call this method once per
     * instance.
     *
     * @param addedTo {@code non-null;} the section this instance has
     * been added to
     * @param offset {@code >= 0;} the desired offset from the start of the
     * section where this instance was placed
     * @return {@code >= 0;} the offset that this instance should be placed at
     * in order to meet its alignment constraint
     */
    public final int place(Section addedTo, int offset) {
        if (addedTo == null) {
            throw new NullPointerException("addedTo == null");
        }

        if (offset < 0) {
            throw new IllegalArgumentException("offset < 0");
        }

        if (this.addedTo != null) {
            throw new RuntimeException("already written");
        }

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

        this.addedTo = addedTo;
        this.offset = offset;

        place0(addedTo, offset);

        return offset;
    }

    /**
     * Gets the alignment requirement of this instance. An instance should
     * only be written when so aligned.
     *
     * @return {@code > 0;} the alignment requirement; must be a power of 2
     */
    public final int getAlignment() {
        return alignment;
    }

    /**
     * Gets the absolute offset of this item as a string, suitable for
     * including in annotations.
     *
     * @return {@code non-null;} the offset string
     */
    public final String offsetString() {
        return '[' + Integer.toHexString(getAbsoluteOffset()) + ']';
    }

    /**
     * Gets a short human-readable string representing this instance.
     *
     * @return {@code non-null;} the human form
     */
    public abstract String toHuman();

    /**
     * Compares this instance to another which is guaranteed to be of
     * the same class. The default implementation of this method is to
     * throw an exception (unsupported operation). If a particular
     * class needs to actually sort, then it should override this
     * method.
     *
     * @param other {@code non-null;} instance to compare to
     * @return {@code -1}, {@code 0}, or {@code 1}, depending
     * on the sort order of this instance and the other
     */
    protected int compareTo0(OffsettedItem other) {
        throw new UnsupportedOperationException("unsupported");
    }

    /**
     * Does additional work required when placing an instance. The
     * default implementation of this method is a no-op. If a
     * particular class needs to do something special, then it should
     * override this method. In particular, if this instance did not
     * know its write size up-front, then this method is responsible
     * for setting it.
     *
     * @param addedTo {@code non-null;} the section this instance has been added to
     * @param offset {@code >= 0;} the offset from the start of the
     * section where this instance was placed
     */
    protected void place0(Section addedTo, int offset) {
        // This space intentionally left blank.
    }

    /**
     * Performs the actual write of the contents of this instance to
     * the given data section. This is called by {@link #writeTo},
     * which will have taken care of ensuring alignment.
     *
     * @param file {@code non-null;} the file to use for reference
     * @param out {@code non-null;} where to write to
     */
    protected abstract void writeTo0(DexFile file, AnnotatedOutput out);
}