MethodDefinition.java revision 343df2f456f38c305ee7d6742f6601d9bde09715
1/* 2 * [The "BSD licence"] 3 * Copyright (c) 2010 Ben Gruver (JesusFreke) 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29package org.jf.baksmali.Adaptors; 30 31import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; 32import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver; 33import org.jf.dexlib.Code.InstructionWithReference; 34import org.jf.util.IndentingWriter; 35import org.jf.baksmali.baksmali; 36import org.jf.dexlib.*; 37import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; 38import org.jf.dexlib.Code.Analysis.MethodAnalyzer; 39import org.jf.dexlib.Code.Analysis.ValidationException; 40import org.jf.dexlib.Code.Format.Format; 41import org.jf.dexlib.Code.Instruction; 42import org.jf.dexlib.Code.OffsetInstruction; 43import org.jf.dexlib.Code.Opcode; 44import org.jf.dexlib.Debug.DebugInstructionIterator; 45import org.jf.dexlib.Util.AccessFlags; 46import org.jf.dexlib.Util.ExceptionWithContext; 47import org.jf.dexlib.Util.SparseIntArray; 48 49import java.io.IOException; 50import java.util.*; 51 52public class MethodDefinition { 53 private final ClassDataItem.EncodedMethod encodedMethod; 54 private MethodAnalyzer methodAnalyzer; 55 56 private final LabelCache labelCache = new LabelCache(); 57 58 private final SparseIntArray packedSwitchMap; 59 private final SparseIntArray sparseSwitchMap; 60 private final SparseIntArray instructionMap; 61 62 public MethodDefinition(ClassDataItem.EncodedMethod encodedMethod) { 63 64 65 try { 66 this.encodedMethod = encodedMethod; 67 68 //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. 69 70 if (encodedMethod.codeItem != null) { 71 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 72 73 packedSwitchMap = new SparseIntArray(1); 74 sparseSwitchMap = new SparseIntArray(1); 75 instructionMap = new SparseIntArray(instructions.length); 76 77 int currentCodeAddress = 0; 78 for (int i=0; i<instructions.length; i++) { 79 Instruction instruction = instructions[i]; 80 if (instruction.opcode == Opcode.PACKED_SWITCH) { 81 packedSwitchMap.append( 82 currentCodeAddress + 83 ((OffsetInstruction)instruction).getTargetAddressOffset(), 84 currentCodeAddress); 85 } else if (instruction.opcode == Opcode.SPARSE_SWITCH) { 86 sparseSwitchMap.append( 87 currentCodeAddress + 88 ((OffsetInstruction)instruction).getTargetAddressOffset(), 89 currentCodeAddress); 90 } 91 instructionMap.append(currentCodeAddress, i); 92 currentCodeAddress += instruction.getSize(currentCodeAddress); 93 } 94 } else { 95 packedSwitchMap = null; 96 sparseSwitchMap = null; 97 instructionMap = null; 98 methodAnalyzer = null; 99 } 100 }catch (Exception ex) { 101 throw ExceptionWithContext.withContext(ex, String.format("Error while processing method %s", 102 encodedMethod.method.getMethodString())); 103 } 104 } 105 106 public void writeTo(IndentingWriter writer, AnnotationSetItem annotationSet, 107 AnnotationSetRefList parameterAnnotations) throws IOException { 108 final CodeItem codeItem = encodedMethod.codeItem; 109 110 writer.write(".method "); 111 writeAccessFlags(writer, encodedMethod); 112 writer.write(encodedMethod.method.getMethodName().getStringValue()); 113 writer.write(encodedMethod.method.getPrototype().getPrototypeString()); 114 writer.write('\n'); 115 116 writer.indent(4); 117 if (codeItem != null) { 118 if (baksmali.useLocalsDirective) { 119 writer.write(".locals "); 120 } else { 121 writer.write(".registers "); 122 } 123 writer.printSignedIntAsDec(getRegisterCount(encodedMethod)); 124 writer.write('\n'); 125 writeParameters(writer, codeItem, parameterAnnotations); 126 if (annotationSet != null) { 127 AnnotationFormatter.writeTo(writer, annotationSet); 128 } 129 130 writer.write('\n'); 131 132 for (MethodItem methodItem: getMethodItems()) { 133 if (methodItem.writeTo(writer)) { 134 writer.write('\n'); 135 } 136 } 137 } else { 138 writeParameters(writer, codeItem, parameterAnnotations); 139 if (annotationSet != null) { 140 AnnotationFormatter.writeTo(writer, annotationSet); 141 } 142 } 143 writer.deindent(4); 144 writer.write(".end method\n"); 145 } 146 147 private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod) 148 { 149 int totalRegisters = encodedMethod.codeItem.getRegisterCount(); 150 if (baksmali.useLocalsDirective) { 151 int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount(); 152 if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) { 153 parameterRegisters++; 154 } 155 return totalRegisters - parameterRegisters; 156 } 157 return totalRegisters; 158 } 159 160 private static void writeAccessFlags(IndentingWriter writer, ClassDataItem.EncodedMethod encodedMethod) 161 throws IOException { 162 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) { 163 writer.write(accessFlag.toString()); 164 writer.write(' '); 165 } 166 } 167 168 private static void writeParameters(IndentingWriter writer, CodeItem codeItem, 169 AnnotationSetRefList parameterAnnotations) throws IOException { 170 DebugInfoItem debugInfoItem = null; 171 if (baksmali.outputDebugInfo && codeItem != null) { 172 debugInfoItem = codeItem.getDebugInfo(); 173 } 174 175 int parameterCount = 0; 176 AnnotationSetItem[] annotations; 177 StringIdItem[] parameterNames = null; 178 179 if (parameterAnnotations != null) { 180 annotations = parameterAnnotations.getAnnotationSets(); 181 parameterCount = annotations.length; 182 } else { 183 annotations = new AnnotationSetItem[0]; 184 } 185 186 if (debugInfoItem != null) { 187 parameterNames = debugInfoItem.getParameterNames(); 188 } 189 if (parameterNames == null) { 190 parameterNames = new StringIdItem[0]; 191 } 192 193 if (parameterCount < parameterNames.length) { 194 parameterCount = parameterNames.length; 195 } 196 197 for (int i=0; i<parameterCount; i++) { 198 AnnotationSetItem annotationSet = null; 199 if (i < annotations.length) { 200 annotationSet = annotations[i]; 201 } 202 203 StringIdItem parameterName = null; 204 if (i < parameterNames.length) { 205 parameterName = parameterNames[i]; 206 } 207 208 writer.write(".parameter"); 209 210 if (parameterName != null) { 211 writer.write(" \""); 212 writer.write(parameterName.getStringValue()); 213 writer.write('"'); 214 } 215 216 writer.write('\n'); 217 if (annotationSet != null) { 218 writer.indent(4); 219 AnnotationFormatter.writeTo(writer, annotationSet); 220 writer.deindent(4); 221 222 writer.write(".end parameter\n"); 223 } 224 } 225 } 226 227 public LabelCache getLabelCache() { 228 return labelCache; 229 } 230 231 public ValidationException getValidationException() { 232 if (methodAnalyzer == null) { 233 return null; 234 } 235 236 return methodAnalyzer.getValidationException(); 237 } 238 239 public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) { 240 int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1); 241 242 if (packedSwitchBaseAddress == -1) { 243 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 244 int index = instructionMap.get(packedSwitchDataAddress); 245 246 if (instructions[index].opcode == Opcode.NOP) { 247 packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress+2, -1); 248 } 249 } 250 251 return packedSwitchBaseAddress; 252 } 253 254 public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) { 255 int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1); 256 257 if (sparseSwitchBaseAddress == -1) { 258 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 259 int index = instructionMap.get(sparseSwitchDataAddress); 260 261 if (instructions[index].opcode == Opcode.NOP) { 262 sparseSwitchBaseAddress = this.packedSwitchMap.get(sparseSwitchDataAddress+2, -1); 263 } 264 } 265 266 return sparseSwitchBaseAddress; 267 } 268 269 /** 270 * @param instructions The instructions array for this method 271 * @param instruction The instruction 272 * @return true if the specified instruction is a NOP, and the next instruction is one of the variable sized 273 * switch/array data structures 274 */ 275 private boolean isInstructionPaddingNop(List<AnalyzedInstruction> instructions, AnalyzedInstruction instruction) { 276 if (instruction.getInstruction().opcode != Opcode.NOP || 277 instruction.getInstruction().getFormat().variableSizeFormat) { 278 279 return false; 280 } 281 282 if (instruction.getInstructionIndex() == instructions.size()-1) { 283 return false; 284 } 285 286 AnalyzedInstruction nextInstruction = instructions.get(instruction.getInstructionIndex()+1); 287 if (nextInstruction.getInstruction().getFormat().variableSizeFormat) { 288 return true; 289 } 290 return false; 291 } 292 293 private boolean needsAnalyzed() { 294 for (Instruction instruction: encodedMethod.codeItem.getInstructions()) { 295 if (instruction.opcode.odexOnly()) { 296 return true; 297 } 298 } 299 return false; 300 } 301 302 private List<MethodItem> getMethodItems() { 303 ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>(); 304 305 if (encodedMethod.codeItem == null) { 306 return methodItems; 307 } 308 309 if ((baksmali.registerInfo != 0) || baksmali.verify || 310 (baksmali.deodex && needsAnalyzed())) { 311 addAnalyzedInstructionMethodItems(methodItems); 312 } else { 313 addInstructionMethodItems(methodItems); 314 } 315 316 addTries(methodItems); 317 if (baksmali.outputDebugInfo) { 318 addDebugInfo(methodItems); 319 } 320 321 if (baksmali.useSequentialLabels) { 322 setLabelSequentialNumbers(); 323 } 324 325 for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { 326 methodItems.add(labelMethodItem); 327 } 328 329 Collections.sort(methodItems); 330 331 return methodItems; 332 } 333 334 private void addInstructionMethodItems(List<MethodItem> methodItems) { 335 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 336 337 int currentCodeAddress = 0; 338 for (int i=0; i<instructions.length; i++) { 339 Instruction instruction = instructions[i]; 340 341 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 342 encodedMethod.codeItem, currentCodeAddress, instruction); 343 344 methodItems.add(methodItem); 345 346 if (i != instructions.length - 1) { 347 methodItems.add(new BlankMethodItem(currentCodeAddress)); 348 } 349 350 if (baksmali.addCodeOffsets) { 351 methodItems.add(new MethodItem(currentCodeAddress) { 352 353 @Override 354 public double getSortOrder() { 355 return -1000; 356 } 357 358 @Override 359 public boolean writeTo(IndentingWriter writer) throws IOException { 360 writer.write("#@"); 361 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF); 362 return true; 363 } 364 }); 365 } 366 367 if (!baksmali.noAccessorComments && (instruction instanceof InstructionWithReference)) { 368 if (instruction.opcode == Opcode.INVOKE_STATIC || instruction.opcode == Opcode.INVOKE_STATIC_RANGE) { 369 MethodIdItem methodIdItem = 370 (MethodIdItem)((InstructionWithReference) instruction).getReferencedItem(); 371 372 if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodIdItem)) { 373 SyntheticAccessorResolver.AccessedMember accessedMember = 374 baksmali.syntheticAccessorResolver.getAccessedMember(methodIdItem); 375 if (accessedMember != null) { 376 methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress)); 377 } 378 } 379 } 380 } 381 382 currentCodeAddress += instruction.getSize(currentCodeAddress); 383 } 384 } 385 386 private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) { 387 methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex, baksmali.inlineResolver); 388 389 methodAnalyzer.analyze(); 390 391 ValidationException validationException = methodAnalyzer.getValidationException(); 392 if (validationException != null) { 393 methodItems.add(new CommentMethodItem( 394 String.format("ValidationException: %s" ,validationException.getMessage()), 395 validationException.getCodeAddress(), Integer.MIN_VALUE)); 396 } else if (baksmali.verify) { 397 methodAnalyzer.verify(); 398 399 validationException = methodAnalyzer.getValidationException(); 400 if (validationException != null) { 401 methodItems.add(new CommentMethodItem( 402 String.format("ValidationException: %s" ,validationException.getMessage()), 403 validationException.getCodeAddress(), Integer.MIN_VALUE)); 404 } 405 } 406 407 List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions(); 408 409 int currentCodeAddress = 0; 410 for (int i=0; i<instructions.size(); i++) { 411 AnalyzedInstruction instruction = instructions.get(i); 412 413 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 414 encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction()); 415 416 methodItems.add(methodItem); 417 418 if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) { 419 methodItems.add(new CommentedOutMethodItem( 420 InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 421 encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction()))); 422 } 423 424 if (i != instructions.size() - 1) { 425 methodItems.add(new BlankMethodItem(currentCodeAddress)); 426 } 427 428 if (baksmali.addCodeOffsets) { 429 methodItems.add(new MethodItem(currentCodeAddress) { 430 431 @Override 432 public double getSortOrder() { 433 return -1000; 434 } 435 436 @Override 437 public boolean writeTo(IndentingWriter writer) throws IOException { 438 writer.write("#@"); 439 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF); 440 return true; 441 } 442 }); 443 } 444 445 if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) { 446 methodItems.add( 447 new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 448 449 methodItems.add( 450 new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 451 } 452 453 currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress); 454 } 455 } 456 457 private void addTries(List<MethodItem> methodItems) { 458 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) { 459 return; 460 } 461 462 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 463 464 for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { 465 int startAddress = tryItem.getStartCodeAddress(); 466 int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength(); 467 468 /** 469 * The end address points to the address immediately after the end of the last 470 * instruction that the try block covers. We want the .catch directive and end_try 471 * label to be associated with the last covered instruction, so we need to get 472 * the address for that instruction 473 */ 474 475 int index = instructionMap.get(endAddress, -1); 476 int lastInstructionAddress; 477 478 /** 479 * If we couldn't find the index, then the try block probably extends to the last instruction in the 480 * method, and so endAddress would be the address immediately after the end of the last instruction. 481 * Check to make sure this is the case, if not, throw an exception. 482 */ 483 if (index == -1) { 484 Instruction lastInstruction = instructions[instructions.length - 1]; 485 lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1); 486 487 if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) { 488 throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address"); 489 } 490 } else { 491 if (index == 0) { 492 throw new RuntimeException("Unexpected instruction index"); 493 } 494 Instruction lastInstruction = instructions[index - 1]; 495 496 if (lastInstruction.getFormat().variableSizeFormat) { 497 throw new RuntimeException("This try block unexpectedly ends on a switch/array data block."); 498 } 499 500 //getSize for non-variable size formats should return the same size regardless of code address, so just 501 //use a dummy address of "0" 502 lastInstructionAddress = endAddress - lastInstruction.getSize(0); 503 } 504 505 //add the catch all handler if it exists 506 int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress(); 507 if (catchAllAddress != -1) { 508 CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, null, 509 startAddress, endAddress, catchAllAddress); 510 methodItems.add(catchAllMethodItem); 511 } 512 513 //add the rest of the handlers 514 for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { 515 //use the address from the last covered instruction 516 CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, 517 handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress()); 518 methodItems.add(catchMethodItem); 519 } 520 } 521 } 522 523 private void addDebugInfo(final List<MethodItem> methodItems) { 524 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) { 525 return; 526 } 527 528 final CodeItem codeItem = encodedMethod.codeItem; 529 DebugInfoItem debugInfoItem = codeItem.getDebugInfo(); 530 531 DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(), 532 new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() { 533 @Override 534 public void ProcessStartLocal(final int codeAddress, final int length, final int registerNum, 535 final StringIdItem name, final TypeIdItem type) { 536 methodItems.add(new DebugMethodItem(codeAddress, -1) { 537 @Override 538 public boolean writeTo(IndentingWriter writer) throws IOException { 539 writeStartLocal(writer, codeItem, registerNum, name, type, null); 540 return true; 541 } 542 }); 543 } 544 545 @Override 546 public void ProcessStartLocalExtended(final int codeAddress, final int length, 547 final int registerNum, final StringIdItem name, 548 final TypeIdItem type, final StringIdItem signature) { 549 methodItems.add(new DebugMethodItem(codeAddress, -1) { 550 @Override 551 public boolean writeTo(IndentingWriter writer) throws IOException { 552 writeStartLocal(writer, codeItem, registerNum, name, type, signature); 553 return true; 554 } 555 }); 556 } 557 558 @Override 559 public void ProcessEndLocal(final int codeAddress, final int length, final int registerNum, 560 final StringIdItem name, final TypeIdItem type, 561 final StringIdItem signature) { 562 methodItems.add(new DebugMethodItem(codeAddress, -1) { 563 @Override 564 public boolean writeTo(IndentingWriter writer) throws IOException { 565 writeEndLocal(writer, codeItem, registerNum, name, type, signature); 566 return true; 567 } 568 }); 569 } 570 571 @Override 572 public void ProcessRestartLocal(final int codeAddress, final int length, final int registerNum, 573 final StringIdItem name, final TypeIdItem type, 574 final StringIdItem signature) { 575 methodItems.add(new DebugMethodItem(codeAddress, -1) { 576 @Override 577 public boolean writeTo(IndentingWriter writer) throws IOException { 578 writeRestartLocal(writer, codeItem, registerNum, name, type, signature); 579 return true; 580 } 581 }); 582 } 583 584 @Override 585 public void ProcessSetPrologueEnd(int codeAddress) { 586 methodItems.add(new DebugMethodItem(codeAddress, -4) { 587 @Override 588 public boolean writeTo(IndentingWriter writer) throws IOException { 589 writeEndPrologue(writer); 590 return true; 591 } 592 }); 593 } 594 595 @Override 596 public void ProcessSetEpilogueBegin(int codeAddress) { 597 methodItems.add(new DebugMethodItem(codeAddress, -4) { 598 @Override 599 public boolean writeTo(IndentingWriter writer) throws IOException { 600 writeBeginEpilogue(writer); 601 return true; 602 } 603 }); 604 } 605 606 @Override 607 public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) { 608 methodItems.add(new DebugMethodItem(codeAddress, -3) { 609 @Override 610 public boolean writeTo(IndentingWriter writer) throws IOException { 611 writeSetFile(writer, name.getStringValue()); 612 return true; 613 } 614 }); 615 } 616 617 @Override 618 public void ProcessLineEmit(int codeAddress, final int line) { 619 methodItems.add(new DebugMethodItem(codeAddress, -2) { 620 @Override 621 public boolean writeTo(IndentingWriter writer) throws IOException { 622 writeLine(writer, line); 623 return true; 624 } 625 }); 626 } 627 }); 628 } 629 630 private void setLabelSequentialNumbers() { 631 HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); 632 ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); 633 634 //sort the labels by their location in the method 635 Collections.sort(sortedLabels); 636 637 for (LabelMethodItem labelMethodItem: sortedLabels) { 638 Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); 639 if (labelSequence == null) { 640 labelSequence = 0; 641 } 642 labelMethodItem.setLabelSequence(labelSequence); 643 nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); 644 } 645 } 646 647 public static class LabelCache { 648 protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); 649 650 public LabelCache() { 651 } 652 653 public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { 654 LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); 655 if (internedLabelMethodItem != null) { 656 return internedLabelMethodItem; 657 } 658 labels.put(labelMethodItem, labelMethodItem); 659 return labelMethodItem; 660 } 661 662 663 public Collection<LabelMethodItem> getLabels() { 664 return labels.values(); 665 } 666 } 667} 668