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.dex.util.ExceptionWithContext; 20import com.android.dx.util.AnnotatedOutput; 21import com.android.dx.util.Hex; 22import java.util.ArrayList; 23import java.util.Arrays; 24import java.util.Collection; 25import java.util.Collections; 26import java.util.Comparator; 27import java.util.HashMap; 28import java.util.Map; 29import java.util.NoSuchElementException; 30import java.util.TreeMap; 31 32/** 33 * A section of a {@code .dex} file which consists of a sequence of 34 * {@link OffsettedItem} objects, which may each be of a different concrete 35 * class and/or size. 36 * 37 * <b>Note:</b> It is invalid for an item in an instance of this class to 38 * have a larger alignment requirement than the alignment of this instance. 39 */ 40public final class MixedItemSection extends Section { 41 static enum SortType { 42 /** no sorting */ 43 NONE, 44 45 /** sort by type only */ 46 TYPE, 47 48 /** sort in class-major order, with instances sorted per-class */ 49 INSTANCE; 50 }; 51 52 /** {@code non-null;} sorter which sorts instances by type */ 53 private static final Comparator<OffsettedItem> TYPE_SORTER = 54 new Comparator<OffsettedItem>() { 55 public int compare(OffsettedItem item1, OffsettedItem item2) { 56 ItemType type1 = item1.itemType(); 57 ItemType type2 = item2.itemType(); 58 return type1.compareTo(type2); 59 } 60 }; 61 62 /** {@code non-null;} the items in this part */ 63 private final ArrayList<OffsettedItem> items; 64 65 /** {@code non-null;} items that have been explicitly interned */ 66 private final HashMap<OffsettedItem, OffsettedItem> interns; 67 68 /** {@code non-null;} how to sort the items */ 69 private final SortType sort; 70 71 /** 72 * {@code >= -1;} the current size of this part, in bytes, or {@code -1} 73 * if not yet calculated 74 */ 75 private int writeSize; 76 77 /** 78 * Constructs an instance. The file offset is initially unknown. 79 * 80 * @param name {@code null-ok;} the name of this instance, for annotation 81 * purposes 82 * @param file {@code non-null;} file that this instance is part of 83 * @param alignment {@code > 0;} alignment requirement for the final output; 84 * must be a power of 2 85 * @param sort how the items should be sorted in the final output 86 */ 87 public MixedItemSection(String name, DexFile file, int alignment, 88 SortType sort) { 89 super(name, file, alignment); 90 91 this.items = new ArrayList<OffsettedItem>(100); 92 this.interns = new HashMap<OffsettedItem, OffsettedItem>(100); 93 this.sort = sort; 94 this.writeSize = -1; 95 } 96 97 /** {@inheritDoc} */ 98 @Override 99 public Collection<? extends Item> items() { 100 return items; 101 } 102 103 /** {@inheritDoc} */ 104 @Override 105 public int writeSize() { 106 throwIfNotPrepared(); 107 return writeSize; 108 } 109 110 /** {@inheritDoc} */ 111 @Override 112 public int getAbsoluteItemOffset(Item item) { 113 OffsettedItem oi = (OffsettedItem) item; 114 return oi.getAbsoluteOffset(); 115 } 116 117 /** 118 * Gets the size of this instance, in items. 119 * 120 * @return {@code >= 0;} the size 121 */ 122 public int size() { 123 return items.size(); 124 } 125 126 /** 127 * Writes the portion of the file header that refers to this instance. 128 * 129 * @param out {@code non-null;} where to write 130 */ 131 public void writeHeaderPart(AnnotatedOutput out) { 132 throwIfNotPrepared(); 133 134 if (writeSize == -1) { 135 throw new RuntimeException("write size not yet set"); 136 } 137 138 int sz = writeSize; 139 int offset = (sz == 0) ? 0 : getFileOffset(); 140 String name = getName(); 141 142 if (name == null) { 143 name = "<unnamed>"; 144 } 145 146 int spaceCount = 15 - name.length(); 147 char[] spaceArr = new char[spaceCount]; 148 Arrays.fill(spaceArr, ' '); 149 String spaces = new String(spaceArr); 150 151 if (out.annotates()) { 152 out.annotate(4, name + "_size:" + spaces + Hex.u4(sz)); 153 out.annotate(4, name + "_off: " + spaces + Hex.u4(offset)); 154 } 155 156 out.writeInt(sz); 157 out.writeInt(offset); 158 } 159 160 /** 161 * Adds an item to this instance. This will in turn tell the given item 162 * that it has been added to this instance. It is invalid to add the 163 * same item to more than one instance, nor to add the same items 164 * multiple times to a single instance. 165 * 166 * @param item {@code non-null;} the item to add 167 */ 168 public void add(OffsettedItem item) { 169 throwIfPrepared(); 170 171 try { 172 if (item.getAlignment() > getAlignment()) { 173 throw new IllegalArgumentException( 174 "incompatible item alignment"); 175 } 176 } catch (NullPointerException ex) { 177 // Elucidate the exception. 178 throw new NullPointerException("item == null"); 179 } 180 181 items.add(item); 182 } 183 184 /** 185 * Interns an item in this instance, returning the interned instance 186 * (which may not be the one passed in). This will add the item if no 187 * equal item has been added. 188 * 189 * @param item {@code non-null;} the item to intern 190 * @return {@code non-null;} the equivalent interned instance 191 */ 192 public <T extends OffsettedItem> T intern(T item) { 193 throwIfPrepared(); 194 195 OffsettedItem result = interns.get(item); 196 197 if (result != null) { 198 return (T) result; 199 } 200 201 add(item); 202 interns.put(item, item); 203 return item; 204 } 205 206 /** 207 * Gets an item which was previously interned. 208 * 209 * @param item {@code non-null;} the item to look for 210 * @return {@code non-null;} the equivalent already-interned instance 211 */ 212 public <T extends OffsettedItem> T get(T item) { 213 throwIfNotPrepared(); 214 215 OffsettedItem result = interns.get(item); 216 217 if (result != null) { 218 return (T) result; 219 } 220 221 throw new NoSuchElementException(item.toString()); 222 } 223 224 /** 225 * Writes an index of contents of the items in this instance of the 226 * given type. If there are none, this writes nothing. If there are any, 227 * then the index is preceded by the given intro string. 228 * 229 * @param out {@code non-null;} where to write to 230 * @param itemType {@code non-null;} the item type of interest 231 * @param intro {@code non-null;} the introductory string for non-empty indices 232 */ 233 public void writeIndexAnnotation(AnnotatedOutput out, ItemType itemType, 234 String intro) { 235 throwIfNotPrepared(); 236 237 TreeMap<String, OffsettedItem> index = 238 new TreeMap<String, OffsettedItem>(); 239 240 for (OffsettedItem item : items) { 241 if (item.itemType() == itemType) { 242 String label = item.toHuman(); 243 index.put(label, item); 244 } 245 } 246 247 if (index.size() == 0) { 248 return; 249 } 250 251 out.annotate(0, intro); 252 253 for (Map.Entry<String, OffsettedItem> entry : index.entrySet()) { 254 String label = entry.getKey(); 255 OffsettedItem item = entry.getValue(); 256 out.annotate(0, item.offsetString() + ' ' + label + '\n'); 257 } 258 } 259 260 /** {@inheritDoc} */ 261 @Override 262 protected void prepare0() { 263 DexFile file = getFile(); 264 265 /* 266 * It's okay for new items to be added as a result of an 267 * addContents() call; we just have to deal with the possibility. 268 */ 269 270 int i = 0; 271 for (;;) { 272 int sz = items.size(); 273 if (i >= sz) { 274 break; 275 } 276 277 for (/*i*/; i < sz; i++) { 278 OffsettedItem one = items.get(i); 279 one.addContents(file); 280 } 281 } 282 } 283 284 /** 285 * Places all the items in this instance at particular offsets. This 286 * will call {@link OffsettedItem#place} on each item. If an item 287 * does not know its write size before the call to {@code place}, 288 * it is that call which is responsible for setting the write size. 289 * This method may only be called once per instance; subsequent calls 290 * will throw an exception. 291 */ 292 public void placeItems() { 293 throwIfNotPrepared(); 294 295 switch (sort) { 296 case INSTANCE: { 297 Collections.sort(items); 298 break; 299 } 300 case TYPE: { 301 Collections.sort(items, TYPE_SORTER); 302 break; 303 } 304 } 305 306 int sz = items.size(); 307 int outAt = 0; 308 for (int i = 0; i < sz; i++) { 309 OffsettedItem one = items.get(i); 310 try { 311 int placedAt = one.place(this, outAt); 312 313 if (placedAt < outAt) { 314 throw new RuntimeException("bogus place() result for " + 315 one); 316 } 317 318 outAt = placedAt + one.writeSize(); 319 } catch (RuntimeException ex) { 320 throw ExceptionWithContext.withContext(ex, 321 "...while placing " + one); 322 } 323 } 324 325 writeSize = outAt; 326 } 327 328 /** {@inheritDoc} */ 329 @Override 330 protected void writeTo0(AnnotatedOutput out) { 331 boolean annotates = out.annotates(); 332 boolean first = true; 333 DexFile file = getFile(); 334 int at = 0; 335 336 for (OffsettedItem one : items) { 337 if (annotates) { 338 if (first) { 339 first = false; 340 } else { 341 out.annotate(0, "\n"); 342 } 343 } 344 345 int alignMask = one.getAlignment() - 1; 346 int writeAt = (at + alignMask) & ~alignMask; 347 348 if (at != writeAt) { 349 out.writeZeroes(writeAt - at); 350 at = writeAt; 351 } 352 353 one.writeTo(file, out); 354 at += one.writeSize(); 355 } 356 357 if (at != writeSize) { 358 throw new RuntimeException("output size mismatch"); 359 } 360 } 361} 362