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 29 package org.jf.baksmali.Adaptors; 30 31 import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; 32 import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver; 33 import org.jf.dexlib.Code.InstructionWithReference; 34 import org.jf.util.IndentingWriter; 35 import org.jf.baksmali.baksmali; 36 import org.jf.dexlib.*; 37 import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; 38 import org.jf.dexlib.Code.Analysis.MethodAnalyzer; 39 import org.jf.dexlib.Code.Analysis.ValidationException; 40 import org.jf.dexlib.Code.Format.Format; 41 import org.jf.dexlib.Code.Instruction; 42 import org.jf.dexlib.Code.OffsetInstruction; 43 import org.jf.dexlib.Code.Opcode; 44 import org.jf.dexlib.Debug.DebugInstructionIterator; 45 import org.jf.dexlib.Util.AccessFlags; 46 import org.jf.dexlib.Util.ExceptionWithContext; 47 import org.jf.dexlib.Util.SparseIntArray; 48 49 import java.io.IOException; 50 import java.util.*; 51 52 public 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 if (annotationSet != null) { 139 AnnotationFormatter.writeTo(writer, annotationSet); 140 } 141 } 142 writer.deindent(4); 143 writer.write(".end method\n"); 144 } 145 146 private static int getRegisterCount(ClassDataItem.EncodedMethod encodedMethod) 147 { 148 int totalRegisters = encodedMethod.codeItem.getRegisterCount(); 149 if (baksmali.useLocalsDirective) { 150 int parameterRegisters = encodedMethod.method.getPrototype().getParameterRegisterCount(); 151 if ((encodedMethod.accessFlags & AccessFlags.STATIC.getValue()) == 0) { 152 parameterRegisters++; 153 } 154 return totalRegisters - parameterRegisters; 155 } 156 return totalRegisters; 157 } 158 159 private static void writeAccessFlags(IndentingWriter writer, ClassDataItem.EncodedMethod encodedMethod) 160 throws IOException { 161 for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(encodedMethod.accessFlags)) { 162 writer.write(accessFlag.toString()); 163 writer.write(' '); 164 } 165 } 166 167 private static void writeParameters(IndentingWriter writer, CodeItem codeItem, 168 AnnotationSetRefList parameterAnnotations) throws IOException { 169 DebugInfoItem debugInfoItem = null; 170 if (baksmali.outputDebugInfo && codeItem != null) { 171 debugInfoItem = codeItem.getDebugInfo(); 172 } 173 174 int parameterCount = 0; 175 AnnotationSetItem[] annotations; 176 StringIdItem[] parameterNames = null; 177 178 if (parameterAnnotations != null) { 179 annotations = parameterAnnotations.getAnnotationSets(); 180 parameterCount = annotations.length; 181 } else { 182 annotations = new AnnotationSetItem[0]; 183 } 184 185 if (debugInfoItem != null) { 186 parameterNames = debugInfoItem.getParameterNames(); 187 } 188 if (parameterNames == null) { 189 parameterNames = new StringIdItem[0]; 190 } 191 192 if (parameterCount < parameterNames.length) { 193 parameterCount = parameterNames.length; 194 } 195 196 for (int i=0; i<parameterCount; i++) { 197 AnnotationSetItem annotationSet = null; 198 if (i < annotations.length) { 199 annotationSet = annotations[i]; 200 } 201 202 StringIdItem parameterName = null; 203 if (i < parameterNames.length) { 204 parameterName = parameterNames[i]; 205 } 206 207 writer.write(".parameter"); 208 209 if (parameterName != null) { 210 writer.write(" \""); 211 writer.write(parameterName.getStringValue()); 212 writer.write('"'); 213 } 214 215 writer.write('\n'); 216 if (annotationSet != null) { 217 writer.indent(4); 218 AnnotationFormatter.writeTo(writer, annotationSet); 219 writer.deindent(4); 220 221 writer.write(".end parameter\n"); 222 } 223 } 224 } 225 226 public LabelCache getLabelCache() { 227 return labelCache; 228 } 229 230 public ValidationException getValidationException() { 231 if (methodAnalyzer == null) { 232 return null; 233 } 234 235 return methodAnalyzer.getValidationException(); 236 } 237 238 public int getPackedSwitchBaseAddress(int packedSwitchDataAddress) { 239 int packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress, -1); 240 241 if (packedSwitchBaseAddress == -1) { 242 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 243 int index = instructionMap.get(packedSwitchDataAddress); 244 245 if (instructions[index].opcode == Opcode.NOP) { 246 packedSwitchBaseAddress = this.packedSwitchMap.get(packedSwitchDataAddress+2, -1); 247 } 248 } 249 250 return packedSwitchBaseAddress; 251 } 252 253 public int getSparseSwitchBaseAddress(int sparseSwitchDataAddress) { 254 int sparseSwitchBaseAddress = this.sparseSwitchMap.get(sparseSwitchDataAddress, -1); 255 256 if (sparseSwitchBaseAddress == -1) { 257 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 258 int index = instructionMap.get(sparseSwitchDataAddress); 259 260 if (instructions[index].opcode == Opcode.NOP) { 261 sparseSwitchBaseAddress = this.packedSwitchMap.get(sparseSwitchDataAddress+2, -1); 262 } 263 } 264 265 return sparseSwitchBaseAddress; 266 } 267 268 /** 269 * @param instructions The instructions array for this method 270 * @param instruction The instruction 271 * @return true if the specified instruction is a NOP, and the next instruction is one of the variable sized 272 * switch/array data structures 273 */ 274 private boolean isInstructionPaddingNop(List<AnalyzedInstruction> instructions, AnalyzedInstruction instruction) { 275 if (instruction.getInstruction().opcode != Opcode.NOP || 276 instruction.getInstruction().getFormat().variableSizeFormat) { 277 278 return false; 279 } 280 281 if (instruction.getInstructionIndex() == instructions.size()-1) { 282 return false; 283 } 284 285 AnalyzedInstruction nextInstruction = instructions.get(instruction.getInstructionIndex()+1); 286 if (nextInstruction.getInstruction().getFormat().variableSizeFormat) { 287 return true; 288 } 289 return false; 290 } 291 292 private List<MethodItem> getMethodItems() { 293 ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>(); 294 295 if (encodedMethod.codeItem == null) { 296 return methodItems; 297 } 298 299 if (baksmali.registerInfo != 0 || baksmali.deodex || baksmali.verify) { 300 addAnalyzedInstructionMethodItems(methodItems); 301 } else { 302 addInstructionMethodItems(methodItems); 303 } 304 305 addTries(methodItems); 306 if (baksmali.outputDebugInfo) { 307 addDebugInfo(methodItems); 308 } 309 310 if (baksmali.useSequentialLabels) { 311 setLabelSequentialNumbers(); 312 } 313 314 for (LabelMethodItem labelMethodItem: labelCache.getLabels()) { 315 methodItems.add(labelMethodItem); 316 } 317 318 Collections.sort(methodItems); 319 320 return methodItems; 321 } 322 323 private void addInstructionMethodItems(List<MethodItem> methodItems) { 324 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 325 326 int currentCodeAddress = 0; 327 for (int i=0; i<instructions.length; i++) { 328 Instruction instruction = instructions[i]; 329 330 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 331 encodedMethod.codeItem, currentCodeAddress, instruction); 332 333 methodItems.add(methodItem); 334 335 if (i != instructions.length - 1) { 336 methodItems.add(new BlankMethodItem(currentCodeAddress)); 337 } 338 339 if (baksmali.addCodeOffsets) { 340 methodItems.add(new MethodItem(currentCodeAddress) { 341 342 @Override 343 public double getSortOrder() { 344 return -1000; 345 } 346 347 @Override 348 public boolean writeTo(IndentingWriter writer) throws IOException { 349 writer.write("#@"); 350 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF); 351 return true; 352 } 353 }); 354 } 355 356 if (!baksmali.noAccessorComments && (instruction instanceof InstructionWithReference)) { 357 if (instruction.opcode == Opcode.INVOKE_STATIC || instruction.opcode == Opcode.INVOKE_STATIC_RANGE) { 358 MethodIdItem methodIdItem = 359 (MethodIdItem)((InstructionWithReference) instruction).getReferencedItem(); 360 361 if (SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodIdItem)) { 362 SyntheticAccessorResolver.AccessedMember accessedMember = 363 baksmali.syntheticAccessorResolver.getAccessedMember(methodIdItem); 364 if (accessedMember != null) { 365 methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress)); 366 } 367 } 368 } 369 } 370 371 currentCodeAddress += instruction.getSize(currentCodeAddress); 372 } 373 } 374 375 private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) { 376 methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex); 377 378 methodAnalyzer.analyze(); 379 380 ValidationException validationException = methodAnalyzer.getValidationException(); 381 if (validationException != null) { 382 methodItems.add(new CommentMethodItem( 383 String.format("ValidationException: %s" ,validationException.getMessage()), 384 validationException.getCodeAddress(), Integer.MIN_VALUE)); 385 } else if (baksmali.verify) { 386 methodAnalyzer.verify(); 387 388 validationException = methodAnalyzer.getValidationException(); 389 if (validationException != null) { 390 methodItems.add(new CommentMethodItem( 391 String.format("ValidationException: %s" ,validationException.getMessage()), 392 validationException.getCodeAddress(), Integer.MIN_VALUE)); 393 } 394 } 395 396 List<AnalyzedInstruction> instructions = methodAnalyzer.getInstructions(); 397 398 int currentCodeAddress = 0; 399 for (int i=0; i<instructions.size(); i++) { 400 AnalyzedInstruction instruction = instructions.get(i); 401 402 MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 403 encodedMethod.codeItem, currentCodeAddress, instruction.getInstruction()); 404 405 methodItems.add(methodItem); 406 407 if (instruction.getInstruction().getFormat() == Format.UnresolvedOdexInstruction) { 408 methodItems.add(new CommentedOutMethodItem( 409 InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, 410 encodedMethod.codeItem, currentCodeAddress, instruction.getOriginalInstruction()))); 411 } 412 413 if (i != instructions.size() - 1) { 414 methodItems.add(new BlankMethodItem(currentCodeAddress)); 415 } 416 417 if (baksmali.addCodeOffsets) { 418 methodItems.add(new MethodItem(currentCodeAddress) { 419 420 @Override 421 public double getSortOrder() { 422 return -1000; 423 } 424 425 @Override 426 public boolean writeTo(IndentingWriter writer) throws IOException { 427 writer.write("#@"); 428 writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFF); 429 return true; 430 } 431 }); 432 } 433 434 if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) { 435 methodItems.add( 436 new PreInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 437 438 methodItems.add( 439 new PostInstructionRegisterInfoMethodItem(instruction, methodAnalyzer, currentCodeAddress)); 440 } 441 442 currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress); 443 } 444 } 445 446 private void addTries(List<MethodItem> methodItems) { 447 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getTries() == null) { 448 return; 449 } 450 451 Instruction[] instructions = encodedMethod.codeItem.getInstructions(); 452 453 for (CodeItem.TryItem tryItem: encodedMethod.codeItem.getTries()) { 454 int startAddress = tryItem.getStartCodeAddress(); 455 int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength(); 456 457 /** 458 * The end address points to the address immediately after the end of the last 459 * instruction that the try block covers. We want the .catch directive and end_try 460 * label to be associated with the last covered instruction, so we need to get 461 * the address for that instruction 462 */ 463 464 int index = instructionMap.get(endAddress, -1); 465 int lastInstructionAddress; 466 467 /** 468 * If we couldn't find the index, then the try block probably extends to the last instruction in the 469 * method, and so endAddress would be the address immediately after the end of the last instruction. 470 * Check to make sure this is the case, if not, throw an exception. 471 */ 472 if (index == -1) { 473 Instruction lastInstruction = instructions[instructions.length - 1]; 474 lastInstructionAddress = instructionMap.keyAt(instructionMap.size() - 1); 475 476 if (endAddress != lastInstructionAddress + lastInstruction.getSize(lastInstructionAddress)) { 477 throw new RuntimeException("Invalid code offset " + endAddress + " for the try block end address"); 478 } 479 } else { 480 if (index == 0) { 481 throw new RuntimeException("Unexpected instruction index"); 482 } 483 Instruction lastInstruction = instructions[index - 1]; 484 485 if (lastInstruction.getFormat().variableSizeFormat) { 486 throw new RuntimeException("This try block unexpectedly ends on a switch/array data block."); 487 } 488 489 //getSize for non-variable size formats should return the same size regardless of code address, so just 490 //use a dummy address of "0" 491 lastInstructionAddress = endAddress - lastInstruction.getSize(0); 492 } 493 494 //add the catch all handler if it exists 495 int catchAllAddress = tryItem.encodedCatchHandler.getCatchAllHandlerAddress(); 496 if (catchAllAddress != -1) { 497 CatchMethodItem catchAllMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, null, 498 startAddress, endAddress, catchAllAddress); 499 methodItems.add(catchAllMethodItem); 500 } 501 502 //add the rest of the handlers 503 for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { 504 //use the address from the last covered instruction 505 CatchMethodItem catchMethodItem = new CatchMethodItem(labelCache, lastInstructionAddress, 506 handler.exceptionType, startAddress, endAddress, handler.getHandlerAddress()); 507 methodItems.add(catchMethodItem); 508 } 509 } 510 } 511 512 private void addDebugInfo(final List<MethodItem> methodItems) { 513 if (encodedMethod.codeItem == null || encodedMethod.codeItem.getDebugInfo() == null) { 514 return; 515 } 516 517 final CodeItem codeItem = encodedMethod.codeItem; 518 DebugInfoItem debugInfoItem = codeItem.getDebugInfo(); 519 520 DebugInstructionIterator.DecodeInstructions(debugInfoItem, codeItem.getRegisterCount(), 521 new DebugInstructionIterator.ProcessDecodedDebugInstructionDelegate() { 522 @Override 523 public void ProcessStartLocal(final int codeAddress, final int length, final int registerNum, 524 final StringIdItem name, final TypeIdItem type) { 525 methodItems.add(new DebugMethodItem(codeAddress, -1) { 526 @Override 527 public boolean writeTo(IndentingWriter writer) throws IOException { 528 writeStartLocal(writer, codeItem, registerNum, name, type, null); 529 return true; 530 } 531 }); 532 } 533 534 @Override 535 public void ProcessStartLocalExtended(final int codeAddress, final int length, 536 final int registerNum, final StringIdItem name, 537 final TypeIdItem type, final StringIdItem signature) { 538 methodItems.add(new DebugMethodItem(codeAddress, -1) { 539 @Override 540 public boolean writeTo(IndentingWriter writer) throws IOException { 541 writeStartLocal(writer, codeItem, registerNum, name, type, signature); 542 return true; 543 } 544 }); 545 } 546 547 @Override 548 public void ProcessEndLocal(final int codeAddress, final int length, final int registerNum, 549 final StringIdItem name, final TypeIdItem type, 550 final StringIdItem signature) { 551 methodItems.add(new DebugMethodItem(codeAddress, -1) { 552 @Override 553 public boolean writeTo(IndentingWriter writer) throws IOException { 554 writeEndLocal(writer, codeItem, registerNum, name, type, signature); 555 return true; 556 } 557 }); 558 } 559 560 @Override 561 public void ProcessRestartLocal(final int codeAddress, final int length, final int registerNum, 562 final StringIdItem name, final TypeIdItem type, 563 final StringIdItem signature) { 564 methodItems.add(new DebugMethodItem(codeAddress, -1) { 565 @Override 566 public boolean writeTo(IndentingWriter writer) throws IOException { 567 writeRestartLocal(writer, codeItem, registerNum, name, type, signature); 568 return true; 569 } 570 }); 571 } 572 573 @Override 574 public void ProcessSetPrologueEnd(int codeAddress) { 575 methodItems.add(new DebugMethodItem(codeAddress, -4) { 576 @Override 577 public boolean writeTo(IndentingWriter writer) throws IOException { 578 writeEndPrologue(writer); 579 return true; 580 } 581 }); 582 } 583 584 @Override 585 public void ProcessSetEpilogueBegin(int codeAddress) { 586 methodItems.add(new DebugMethodItem(codeAddress, -4) { 587 @Override 588 public boolean writeTo(IndentingWriter writer) throws IOException { 589 writeBeginEpilogue(writer); 590 return true; 591 } 592 }); 593 } 594 595 @Override 596 public void ProcessSetFile(int codeAddress, int length, final StringIdItem name) { 597 methodItems.add(new DebugMethodItem(codeAddress, -3) { 598 @Override 599 public boolean writeTo(IndentingWriter writer) throws IOException { 600 writeSetFile(writer, name.getStringValue()); 601 return true; 602 } 603 }); 604 } 605 606 @Override 607 public void ProcessLineEmit(int codeAddress, final int line) { 608 methodItems.add(new DebugMethodItem(codeAddress, -2) { 609 @Override 610 public boolean writeTo(IndentingWriter writer) throws IOException { 611 writeLine(writer, line); 612 return true; 613 } 614 }); 615 } 616 }); 617 } 618 619 private void setLabelSequentialNumbers() { 620 HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>(); 621 ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels()); 622 623 //sort the labels by their location in the method 624 Collections.sort(sortedLabels); 625 626 for (LabelMethodItem labelMethodItem: sortedLabels) { 627 Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix()); 628 if (labelSequence == null) { 629 labelSequence = 0; 630 } 631 labelMethodItem.setLabelSequence(labelSequence); 632 nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1); 633 } 634 } 635 636 public static class LabelCache { 637 protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>(); 638 639 public LabelCache() { 640 } 641 642 public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) { 643 LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem); 644 if (internedLabelMethodItem != null) { 645 return internedLabelMethodItem; 646 } 647 labels.put(labelMethodItem, labelMethodItem); 648 return labelMethodItem; 649 } 650 651 652 public Collection<LabelMethodItem> getLabels() { 653 return labels.values(); 654 } 655 } 656 } 657