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