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.dexgen.dex.file; 18 19import com.android.dexgen.util.AnnotatedOutput; 20import com.android.dexgen.util.ExceptionWithContext; 21 22/** 23 * An item in a Dalvik file which is referenced by absolute offset. 24 */ 25public 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 */ 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 */ 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 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 313 protected abstract void writeTo0(DexFile file, AnnotatedOutput out); 314} 315