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 com.android.dx.util.ExceptionWithContext;
21 
22 /**
23  * An item in a Dalvik file which is referenced by absolute offset.
24  */
25 public abstract class OffsettedItem extends Item
26         implements Comparable<OffsettedItem> {
27     /** {@code > 0;} alignment requirement */
28     private final int alignment;
29 
30     /** {@code >= -1;} the size of this instance when written, in bytes, or
31      * {@code -1} if not yet known */
32     private int writeSize;
33 
34     /**
35      * {@code null-ok;} section the item was added to, or {@code null} if
36      * not yet added
37      */
38     private Section addedTo;
39 
40     /**
41      * {@code >= -1;} assigned offset of the item from the start of its section,
42      * or {@code -1} if not yet assigned
43      */
44     private int offset;
45 
46     /**
47      * Gets the absolute offset of the given item, returning {@code 0}
48      * if handed {@code null}.
49      *
50      * @param item {@code null-ok;} the item in question
51      * @return {@code >= 0;} the item's absolute offset, or {@code 0}
52      * if {@code item == null}
53      */
getAbsoluteOffsetOr0(OffsettedItem item)54     public static int getAbsoluteOffsetOr0(OffsettedItem item) {
55         if (item == null) {
56             return 0;
57         }
58 
59         return item.getAbsoluteOffset();
60     }
61 
62     /**
63      * Constructs an instance. The offset is initially unassigned.
64      *
65      * @param alignment {@code > 0;} output alignment requirement; must be a
66      * power of 2
67      * @param writeSize {@code >= -1;} the size of this instance when written,
68      * in bytes, or {@code -1} if not immediately known
69      */
OffsettedItem(int alignment, int writeSize)70     public OffsettedItem(int alignment, int writeSize) {
71         Section.validateAlignment(alignment);
72 
73         if (writeSize < -1) {
74             throw new IllegalArgumentException("writeSize < -1");
75         }
76 
77         this.alignment = alignment;
78         this.writeSize = writeSize;
79         this.addedTo = null;
80         this.offset = -1;
81     }
82 
83     /**
84      * {@inheritDoc}
85      *
86      * Comparisons for this class are defined to be type-major (if the
87      * types don't match then the objects are not equal), with
88      * {@link #compareTo0} deciding same-type comparisons.
89      */
90     @Override
equals(Object other)91     public final boolean equals(Object other) {
92         if (this == other) {
93             return true;
94         }
95 
96         OffsettedItem otherItem = (OffsettedItem) other;
97         ItemType thisType = itemType();
98         ItemType otherType = otherItem.itemType();
99 
100         if (thisType != otherType) {
101             return false;
102         }
103 
104         return (compareTo0(otherItem) == 0);
105     }
106 
107     /**
108      * {@inheritDoc}
109      *
110      * Comparisons for this class are defined to be class-major (if the
111      * classes don't match then the objects are not equal), with
112      * {@link #compareTo0} deciding same-class comparisons.
113      */
compareTo(OffsettedItem other)114     public final int compareTo(OffsettedItem other) {
115         if (this == other) {
116             return 0;
117         }
118 
119         ItemType thisType = itemType();
120         ItemType otherType = other.itemType();
121 
122         if (thisType != otherType) {
123             return thisType.compareTo(otherType);
124         }
125 
126         return compareTo0(other);
127     }
128 
129     /**
130      * Sets the write size of this item. This may only be called once
131      * per instance, and only if the size was unknown upon instance
132      * creation.
133      *
134      * @param writeSize {@code > 0;} the write size, in bytes
135      */
setWriteSize(int writeSize)136     public final void setWriteSize(int writeSize) {
137         if (writeSize < 0) {
138             throw new IllegalArgumentException("writeSize < 0");
139         }
140 
141         if (this.writeSize >= 0) {
142             throw new UnsupportedOperationException("writeSize already set");
143         }
144 
145         this.writeSize = writeSize;
146     }
147 
148     /** {@inheritDoc}
149      *
150      * @throws UnsupportedOperationException thrown if the write size
151      * is not yet known
152      */
153     @Override
writeSize()154     public final int writeSize() {
155         if (writeSize < 0) {
156             throw new UnsupportedOperationException("writeSize is unknown");
157         }
158 
159         return writeSize;
160     }
161 
162     /** {@inheritDoc} */
163     @Override
writeTo(DexFile file, AnnotatedOutput out)164     public final void writeTo(DexFile file, AnnotatedOutput out) {
165         out.alignTo(alignment);
166 
167         try {
168             if (writeSize < 0) {
169                 throw new UnsupportedOperationException(
170                         "writeSize is unknown");
171             }
172             out.assertCursor(getAbsoluteOffset());
173         } catch (RuntimeException ex) {
174             throw ExceptionWithContext.withContext(ex,
175                     "...while writing " + this);
176         }
177 
178         writeTo0(file, out);
179     }
180 
181     /**
182      * Gets the relative item offset. The offset is from the start of
183      * the section which the instance was written to.
184      *
185      * @return {@code >= 0;} the offset
186      * @throws RuntimeException thrown if the offset is not yet known
187      */
getRelativeOffset()188     public final int getRelativeOffset() {
189         if (offset < 0) {
190             throw new RuntimeException("offset not yet known");
191         }
192 
193         return offset;
194     }
195 
196     /**
197      * Gets the absolute item offset. The offset is from the start of
198      * the file which the instance was written to.
199      *
200      * @return {@code >= 0;} the offset
201      * @throws RuntimeException thrown if the offset is not yet known
202      */
getAbsoluteOffset()203     public final int getAbsoluteOffset() {
204         if (offset < 0) {
205             throw new RuntimeException("offset not yet known");
206         }
207 
208         return addedTo.getAbsoluteOffset(offset);
209     }
210 
211     /**
212      * Indicates that this item has been added to the given section at
213      * the given offset. It is only valid to call this method once per
214      * instance.
215      *
216      * @param addedTo {@code non-null;} the section this instance has
217      * been added to
218      * @param offset {@code >= 0;} the desired offset from the start of the
219      * section where this instance was placed
220      * @return {@code >= 0;} the offset that this instance should be placed at
221      * in order to meet its alignment constraint
222      */
place(Section addedTo, int offset)223     public final int place(Section addedTo, int offset) {
224         if (addedTo == null) {
225             throw new NullPointerException("addedTo == null");
226         }
227 
228         if (offset < 0) {
229             throw new IllegalArgumentException("offset < 0");
230         }
231 
232         if (this.addedTo != null) {
233             throw new RuntimeException("already written");
234         }
235 
236         int mask = alignment - 1;
237         offset = (offset + mask) & ~mask;
238 
239         this.addedTo = addedTo;
240         this.offset = offset;
241 
242         place0(addedTo, offset);
243 
244         return offset;
245     }
246 
247     /**
248      * Gets the alignment requirement of this instance. An instance should
249      * only be written when so aligned.
250      *
251      * @return {@code > 0;} the alignment requirement; must be a power of 2
252      */
getAlignment()253     public final int getAlignment() {
254         return alignment;
255     }
256 
257     /**
258      * Gets the absolute offset of this item as a string, suitable for
259      * including in annotations.
260      *
261      * @return {@code non-null;} the offset string
262      */
offsetString()263     public final String offsetString() {
264         return '[' + Integer.toHexString(getAbsoluteOffset()) + ']';
265     }
266 
267     /**
268      * Gets a short human-readable string representing this instance.
269      *
270      * @return {@code non-null;} the human form
271      */
toHuman()272     public abstract String toHuman();
273 
274     /**
275      * Compares this instance to another which is guaranteed to be of
276      * the same class. The default implementation of this method is to
277      * throw an exception (unsupported operation). If a particular
278      * class needs to actually sort, then it should override this
279      * method.
280      *
281      * @param other {@code non-null;} instance to compare to
282      * @return {@code -1}, {@code 0}, or {@code 1}, depending
283      * on the sort order of this instance and the other
284      */
compareTo0(OffsettedItem other)285     protected int compareTo0(OffsettedItem other) {
286         throw new UnsupportedOperationException("unsupported");
287     }
288 
289     /**
290      * Does additional work required when placing an instance. The
291      * default implementation of this method is a no-op. If a
292      * particular class needs to do something special, then it should
293      * override this method. In particular, if this instance did not
294      * know its write size up-front, then this method is responsible
295      * for setting it.
296      *
297      * @param addedTo {@code non-null;} the section this instance has been added to
298      * @param offset {@code >= 0;} the offset from the start of the
299      * section where this instance was placed
300      */
place0(Section addedTo, int offset)301     protected void place0(Section addedTo, int offset) {
302         // This space intentionally left blank.
303     }
304 
305     /**
306      * Performs the actual write of the contents of this instance to
307      * the given data section. This is called by {@link #writeTo},
308      * which will have taken care of ensuring alignment.
309      *
310      * @param file {@code non-null;} the file to use for reference
311      * @param out {@code non-null;} where to write to
312      */
writeTo0(DexFile file, AnnotatedOutput out)313     protected abstract void writeTo0(DexFile file, AnnotatedOutput out);
314 }
315