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 java.util.Collection; 21 22 /** 23 * A section of a {@code .dex} file. Each section consists of a list 24 * of items of some sort or other. 25 */ 26 public abstract class Section { 27 /** {@code null-ok;} name of this part, for annotation purposes */ 28 private final String name; 29 30 /** {@code non-null;} file that this instance is part of */ 31 private final DexFile file; 32 33 /** {@code > 0;} alignment requirement for the final output; 34 * must be a power of 2 */ 35 private final int alignment; 36 37 /** {@code >= -1;} offset from the start of the file to this part, or 38 * {@code -1} if not yet known */ 39 private int fileOffset; 40 41 /** whether {@link #prepare} has been called successfully on this 42 * instance */ 43 private boolean prepared; 44 45 /** 46 * Validates an alignment. 47 * 48 * @param alignment the alignment 49 * @throws IllegalArgumentException thrown if {@code alignment} 50 * isn't a positive power of 2 51 */ 52 public static void validateAlignment(int alignment) { 53 if ((alignment <= 0) || 54 (alignment & (alignment - 1)) != 0) { 55 throw new IllegalArgumentException("invalid alignment"); 56 } 57 } 58 59 /** 60 * Constructs an instance. The file offset is initially unknown. 61 * 62 * @param name {@code null-ok;} the name of this instance, for annotation 63 * purposes 64 * @param file {@code non-null;} file that this instance is part of 65 * @param alignment {@code > 0;} alignment requirement for the final output; 66 * must be a power of 2 67 */ 68 public Section(String name, DexFile file, int alignment) { 69 if (file == null) { 70 throw new NullPointerException("file == null"); 71 } 72 73 validateAlignment(alignment); 74 75 this.name = name; 76 this.file = file; 77 this.alignment = alignment; 78 this.fileOffset = -1; 79 this.prepared = false; 80 } 81 82 /** 83 * Gets the file that this instance is part of. 84 * 85 * @return {@code non-null;} the file 86 */ 87 public final DexFile getFile() { 88 return file; 89 } 90 91 /** 92 * Gets the alignment for this instance's final output. 93 * 94 * @return {@code > 0;} the alignment 95 */ 96 public final int getAlignment() { 97 return alignment; 98 } 99 100 /** 101 * Gets the offset from the start of the file to this part. This 102 * throws an exception if the offset has not yet been set. 103 * 104 * @return {@code >= 0;} the file offset 105 */ 106 public final int getFileOffset() { 107 if (fileOffset < 0) { 108 throw new RuntimeException("fileOffset not set"); 109 } 110 111 return fileOffset; 112 } 113 114 /** 115 * Sets the file offset. It is only valid to call this method once 116 * once per instance. 117 * 118 * @param fileOffset {@code >= 0;} the desired offset from the start of the 119 * file where this for this instance 120 * @return {@code >= 0;} the offset that this instance should be placed at 121 * in order to meet its alignment constraint 122 */ 123 public final int setFileOffset(int fileOffset) { 124 if (fileOffset < 0) { 125 throw new IllegalArgumentException("fileOffset < 0"); 126 } 127 128 if (this.fileOffset >= 0) { 129 throw new RuntimeException("fileOffset already set"); 130 } 131 132 int mask = alignment - 1; 133 fileOffset = (fileOffset + mask) & ~mask; 134 135 this.fileOffset = fileOffset; 136 137 return fileOffset; 138 } 139 140 /** 141 * Writes this instance to the given raw data object. 142 * 143 * @param out {@code non-null;} where to write to 144 */ 145 public final void writeTo(AnnotatedOutput out) { 146 throwIfNotPrepared(); 147 align(out); 148 149 int cursor = out.getCursor(); 150 151 if (fileOffset < 0) { 152 fileOffset = cursor; 153 } else if (fileOffset != cursor) { 154 throw new RuntimeException("alignment mismatch: for " + this + 155 ", at " + cursor + 156 ", but expected " + fileOffset); 157 } 158 159 if (out.annotates()) { 160 if (name != null) { 161 out.annotate(0, "\n" + name + ":"); 162 } else if (cursor != 0) { 163 out.annotate(0, "\n"); 164 } 165 } 166 167 writeTo0(out); 168 } 169 170 /** 171 * Returns the absolute file offset, given an offset from the 172 * start of this instance's output. This is only valid to call 173 * once this instance has been assigned a file offset (via {@link 174 * #setFileOffset}). 175 * 176 * @param relative {@code >= 0;} the relative offset 177 * @return {@code >= 0;} the corresponding absolute file offset 178 */ 179 public final int getAbsoluteOffset(int relative) { 180 if (relative < 0) { 181 throw new IllegalArgumentException("relative < 0"); 182 } 183 184 if (fileOffset < 0) { 185 throw new RuntimeException("fileOffset not yet set"); 186 } 187 188 return fileOffset + relative; 189 } 190 191 /** 192 * Returns the absolute file offset of the given item which must 193 * be contained in this section. This is only valid to call 194 * once this instance has been assigned a file offset (via {@link 195 * #setFileOffset}). 196 * 197 * <p><b>Note:</b> Subclasses must implement this as appropriate for 198 * their contents.</p> 199 * 200 * @param item {@code non-null;} the item in question 201 * @return {@code >= 0;} the item's absolute file offset 202 */ 203 public abstract int getAbsoluteItemOffset(Item item); 204 205 /** 206 * Prepares this instance for writing. This performs any necessary 207 * prerequisites, including particularly adding stuff to other 208 * sections. This method may only be called once per instance; 209 * subsequent calls will throw an exception. 210 */ 211 public final void prepare() { 212 throwIfPrepared(); 213 prepare0(); 214 prepared = true; 215 } 216 217 /** 218 * Gets the collection of all the items in this section. 219 * It is not valid to attempt to change the returned list. 220 * 221 * @return {@code non-null;} the items 222 */ 223 public abstract Collection<? extends Item> items(); 224 225 /** 226 * Does the main work of {@link #prepare}. 227 */ 228 protected abstract void prepare0(); 229 230 /** 231 * Gets the size of this instance when output, in bytes. 232 * 233 * @return {@code >= 0;} the size of this instance, in bytes 234 */ 235 public abstract int writeSize(); 236 237 /** 238 * Throws an exception if {@link #prepare} has not been 239 * called on this instance. 240 */ 241 protected final void throwIfNotPrepared() { 242 if (!prepared) { 243 throw new RuntimeException("not prepared"); 244 } 245 } 246 247 /** 248 * Throws an exception if {@link #prepare} has already been called 249 * on this instance. 250 */ 251 protected final void throwIfPrepared() { 252 if (prepared) { 253 throw new RuntimeException("already prepared"); 254 } 255 } 256 257 /** 258 * Aligns the output of the given data to the alignment of this instance. 259 * 260 * @param out {@code non-null;} the output to align 261 */ 262 protected final void align(AnnotatedOutput out) { 263 out.alignTo(alignment); 264 } 265 266 /** 267 * Writes this instance to the given raw data object. This gets 268 * called by {@link #writeTo} after aligning the cursor of 269 * {@code out} and verifying that either the assigned file 270 * offset matches the actual cursor {@code out} or that the 271 * file offset was not previously assigned, in which case it gets 272 * assigned to {@code out}'s cursor. 273 * 274 * @param out {@code non-null;} where to write to 275 */ 276 protected abstract void writeTo0(AnnotatedOutput out); 277 278 /** 279 * Returns the name of this section, for annotation purposes. 280 * 281 * @return {@code null-ok;} name of this part, for annotation purposes 282 */ 283 protected final String getName() { 284 return name; 285 } 286 } 287