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