1 7 8 package org.gjt.jclasslib.browser.detail.attributes.code; 9 10 import org.gjt.jclasslib.browser.*; 11 import org.gjt.jclasslib.bytecode.*; 12 import org.gjt.jclasslib.io.ByteCodeReader; 13 import org.gjt.jclasslib.structures.ClassFile; 14 import org.gjt.jclasslib.structures.InvalidByteCodeException; 15 import org.gjt.jclasslib.structures.attributes.CodeAttribute; 16 17 import javax.swing.*; 18 import javax.swing.border.Border ; 19 import javax.swing.border.EmptyBorder ; 20 import javax.swing.tree.TreePath ; 21 import java.awt.*; 22 import java.awt.datatransfer.StringSelection ; 23 import java.awt.font.*; 24 import java.io.IOException ; 25 import java.text.AttributedString ; 26 import java.util.*; 27 28 34 public class ByteCodeDisplay extends JPanel implements Scrollable { 35 36 37 public static final int MARGIN_X = 3; 38 39 public static final int MARGIN_Y = 3; 40 41 42 public static final Border BORDER = new EmptyBorder (MARGIN_Y, MARGIN_X, MARGIN_Y, MARGIN_X); 43 44 private static Map STYLE_BASE; 45 private static Map STYLE_NORMAL; 46 private static Map STYLE_SMALL; 47 private static Map STYLE_LINK; 48 private static Map STYLE_OFFSET; 49 private static Map STYLE_INSTRUCTION; 50 private static Map STYLE_IMMEDIATE_VALUE; 51 52 private static final String TAB_STRING = " "; 53 54 static { 55 initStyles(null); 56 } 57 58 public static void initStyles(Font baseFont) { 59 60 STYLE_BASE = new HashMap(2); 61 if (baseFont != null) { 62 STYLE_BASE.put(TextAttribute.FAMILY, baseFont.getFamily()); 63 } else { 64 baseFont = UIManager.getFont("TextArea.font"); 65 STYLE_BASE.put(TextAttribute.FAMILY, "MonoSpaced"); 66 } 67 68 STYLE_BASE.put(TextAttribute.SIZE, new Float (baseFont.getSize())); 69 70 STYLE_NORMAL = new HashMap(0); 71 72 STYLE_SMALL = new HashMap(1); 73 STYLE_SMALL.put(TextAttribute.SIZE, new Float (baseFont.getSize() - 1)); 74 75 STYLE_LINK = new HashMap(3); 76 STYLE_LINK.put(TextAttribute.FOREGROUND, new Color(0, 128, 0)); 77 STYLE_LINK.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); 78 STYLE_LINK.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); 79 80 STYLE_OFFSET = new HashMap(1); 81 STYLE_OFFSET.put(TextAttribute.FOREGROUND, new Color(128, 0, 0)); 82 83 STYLE_INSTRUCTION = new HashMap(1); 84 STYLE_INSTRUCTION.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); 85 86 STYLE_IMMEDIATE_VALUE = new HashMap(2); 87 STYLE_IMMEDIATE_VALUE.put(TextAttribute.FOREGROUND, Color.magenta); 88 STYLE_IMMEDIATE_VALUE.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); 89 } 90 91 private ByteCodeDetailPane detailPane; 92 93 private CodeAttribute codeAttribute; 94 private ClassFile classFile; 95 96 private int offsetWidth; 97 private String offsetBlank; 98 private HashMap offsetToLine = new HashMap(); 99 private ArrayList lines = new ArrayList(); 100 private ArrayList textLines = new ArrayList(); 101 private TextLayout[] textLayouts; 102 private Map lineToLink = new HashMap(); 103 104 private LinkedList currentLineCache = new LinkedList(); 105 private FontRenderContext frc; 106 private float currentHeight; 107 private float currentWidth; 108 private int lineHeight; 109 private int ascent; 110 private int characterWidth; 111 112 118 public static String getPaddedValue(int number, int width) { 119 120 StringBuffer buffer = new StringBuffer (); 121 String value = String.valueOf(number); 122 int valueLength = value.length(); 123 for (int i = valueLength; i < width; i++) { 124 buffer.append(' '); 125 } 126 buffer.append(value); 127 return buffer.toString(); 128 } 129 130 134 public ByteCodeDisplay(ByteCodeDetailPane detailPane) { 135 this.detailPane = detailPane; 136 137 setupComponent(); 138 setupEventHandlers(); 139 } 140 141 143 public Dimension getPreferredScrollableViewportSize() { 144 return null; 145 } 146 147 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { 148 149 if (orientation == SwingConstants.HORIZONTAL) { 150 return 10; 151 } else { 152 if (lineHeight == 0) { 153 return 1; 154 } 155 int currentY = ((JViewport)getParent()).getViewPosition().y; 156 float line = 1f * (currentY - MARGIN_Y) / lineHeight; 157 int targetLine = (int)(direction < 0 ? Math.floor(line) - 1: Math.ceil(line) + 1); 158 int targetY = MARGIN_Y + targetLine * lineHeight + 1; 159 return Math.abs(currentY - targetY); 160 } 161 } 162 163 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { 164 165 JViewport viewport = (JViewport)getParent(); 166 if (orientation == SwingConstants.HORIZONTAL) { 167 return viewport.getWidth(); 168 } else { 169 if (lineHeight == 0) { 170 return 1; 171 } 172 int currentY = viewport.getViewPosition().y; 173 int rawTargetY = currentY + (direction < 0 ? -1 : 1) * viewport.getHeight(); 174 float line = 1f * (rawTargetY - MARGIN_Y) / lineHeight; 175 int targetLine = (int)(direction < 0 ? Math.ceil(line): Math.floor(line)); 176 int targetY = MARGIN_Y + targetLine * lineHeight + 1; 177 178 return Math.abs(currentY - targetY); 179 } 180 } 181 182 public boolean getScrollableTracksViewportWidth() { 183 return false; 184 } 185 186 public boolean getScrollableTracksViewportHeight() { 187 return false; 188 } 189 190 192 193 197 public CodeAttribute getCodeAttribute() { 198 return codeAttribute; 199 } 200 201 205 public int getLineCount() { 206 return lines.size(); 207 } 208 209 213 public int getLineHeight() { 214 return lineHeight; 215 } 216 217 221 public int getAscent() { 222 return ascent; 223 } 224 225 230 public void setCodeAttribute(CodeAttribute codeAttribute, ClassFile classFile) { 231 this.codeAttribute = codeAttribute; 232 this.classFile = classFile; 233 frc = ((Graphics2D)getGraphics()).getFontRenderContext(); 234 setupTextLayouts(); 235 invalidate(); 236 } 237 238 243 public void link(Point point) { 244 245 BytecodeLink link = getLink(point); 246 if (link == null) { 247 return; 248 } 249 updateHistory(link.sourceOffset); 250 251 if (link instanceof ConstantPoolLink) { 252 ConstantPoolHyperlinkListener.link(detailPane.getBrowserServices(), ((ConstantPoolLink)link).cpIndex); 253 } else if (link instanceof OffsetLink) { 254 int targetOffset = ((OffsetLink)link).targetOffset; 255 scrollToOffset(targetOffset); 256 updateHistory(targetOffset); 257 } 258 } 259 260 265 public boolean isLink(Point point) { 266 return getLink(point) != null; 267 } 268 269 273 public void scrollToOffset(int offset) { 274 275 Integer line = (Integer )offsetToLine.get(new Integer (offset)); 276 if (line == null) { 277 return; 278 } 279 Rectangle target = new Rectangle(0, line.intValue() * lineHeight + MARGIN_Y + 1, 10, getParent().getHeight()); 280 scrollRectToVisible(target); 281 } 282 283 286 public void copyToClipboard() { 287 288 StringBuffer buffer = new StringBuffer (); 289 Iterator it = textLines.iterator(); 290 while (it.hasNext()) { 291 String line = (String )it.next(); 292 buffer.append(line); 293 buffer.append('\n'); 294 } 295 StringSelection stringSelection = new StringSelection (buffer.toString()); 296 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, stringSelection); 297 } 298 299 protected void paintComponent(Graphics graphics) { 300 301 if (lineHeight == 0) { 302 return; 303 } 304 305 Graphics2D g = (Graphics2D)graphics; 306 g.translate(MARGIN_X, MARGIN_Y); 307 Rectangle clipBounds = graphics.getClipBounds(); 308 Paint oldPaint = g.getPaint(); 309 g.setPaint(Color.WHITE); 310 g.fill(clipBounds); 311 g.setPaint(oldPaint); 312 int startLine = Math.max(0, clipBounds.y / lineHeight - 1); 313 int endLine = Math.min(lines.size(), (clipBounds.y + clipBounds.height) / lineHeight + 1); 314 for (int i = startLine; i < endLine; i++) { 315 TextLayout textLayout = getOrCreateTextLayout(i); 316 textLayout.draw(g, 0, i * lineHeight + textLayout.getAscent()); 317 } 318 319 g.translate(-MARGIN_X, -MARGIN_Y); 320 } 321 322 private TextLayout getOrCreateTextLayout(int i) { 323 324 TextLayout textLayout = textLayouts[i]; 325 if (textLayout == null) { 326 textLayout = textLayouts[i] = new TextLayout(((AttributedString )lines.get(i)).getIterator(), frc); 327 } 328 return textLayout; 329 } 330 331 private void setupComponent() { 332 333 setBorder(BORDER); 334 setDoubleBuffered(false); 335 setOpaque(false); 336 } 337 338 private void setupEventHandlers() { 339 } 340 341 private BytecodeLink getLink(Point point) { 342 343 if (lineHeight == 0) { 344 return null; 345 } 346 int x = point.x - MARGIN_X; 347 int y = point.y - MARGIN_Y; 348 int line = y / lineHeight; 349 BytecodeLink link = (BytecodeLink)lineToLink.get(new Integer (line)); 350 if (link == null) { 351 return null; 352 } 353 354 TextLayout textLayout = getOrCreateTextLayout(line); 355 TextHitInfo textHitInfo = textLayout.hitTestChar(x, y - line * lineHeight); 356 int charIndex = textHitInfo.getCharIndex(); 357 if (charIndex >= link.startCharIndex && charIndex < link.endCharIndex) { 358 return link; 359 } else { 360 return null; 361 } 362 } 363 364 private void updateHistory(int offset) { 365 366 BrowserServices services = detailPane.getBrowserServices(); 367 TreePath treePath = services.getBrowserComponent().getTreePane().getTree().getSelectionPath(); 368 369 BrowserHistory history = services.getBrowserComponent().getHistory(); 370 history.updateHistory(treePath, new Integer (offset)); 371 } 372 373 private void setupTextLayouts() { 374 375 lineHeight = 0; 376 currentHeight = 0f; 377 currentWidth = 0f; 378 textLines.clear(); 379 lines.clear(); 380 textLayouts = null; 381 offsetToLine.clear(); 382 lineToLink.clear(); 383 384 385 byte[] code = codeAttribute.getCode(); 386 387 try { 388 java.util.List instructions = ByteCodeReader.readByteCode(code); 389 390 calculateOffsetWidth(instructions); 391 392 Iterator it = instructions.iterator(); 393 AbstractInstruction currentInstruction; 394 while (it.hasNext()) { 395 currentInstruction = (AbstractInstruction)it.next(); 396 addInstructionToDocument(currentInstruction); 397 } 398 textLayouts = new TextLayout[lines.size()]; 399 } catch (IOException ex) { 400 ex.printStackTrace(); 401 } 402 setPreferredSize(new Dimension((int)currentWidth + 2 * MARGIN_X, (int)currentHeight + 2 * MARGIN_Y)); 403 } 404 405 private void calculateOffsetWidth(java.util.List instructions) { 406 407 int numberOfInstructions = instructions.size(); 408 409 if (numberOfInstructions > 0) { 410 AbstractInstruction lastInstruction = (AbstractInstruction)instructions.get(numberOfInstructions - 1); 411 offsetWidth = String.valueOf(lastInstruction.getOffset()).length(); 412 } else { 413 offsetWidth = 1; 414 } 415 StringBuffer buffer = new StringBuffer (offsetWidth); 416 for (int i = 0; i < offsetWidth; i++) { 417 buffer.append(' '); 418 } 419 offsetBlank = buffer.toString(); 420 } 421 422 423 private void addInstructionToDocument(AbstractInstruction instruction) { 424 425 int offset = instruction.getOffset(); 426 427 addOffsetReference(offset); 428 429 appendString(getPaddedValue(offset, offsetWidth), 430 STYLE_OFFSET); 431 432 appendString(" " + instruction.getOpcodeVerbose(), 433 STYLE_INSTRUCTION); 434 435 addOpcodeSpecificInfo(instruction); 436 437 newLine(); 438 439 } 440 441 private void addOffsetReference(int offset) { 442 443 offsetToLine.put(new Integer (offset), 444 new Integer (getCurrentLine())); 445 } 446 447 private void addOpcodeSpecificInfo(AbstractInstruction instruction) { 448 449 if (instruction instanceof ImmediateByteInstruction) { 450 addImmediateByteSpecificInfo((ImmediateByteInstruction)instruction); 451 } else if (instruction instanceof ImmediateShortInstruction) { 452 addImmediateShortSpecificInfo((ImmediateShortInstruction)instruction); 453 } else if (instruction instanceof ImmediateIntInstruction) { 454 addImmediateIntSpecificInfo((ImmediateIntInstruction)instruction); 455 } else if (instruction instanceof BranchInstruction) { 456 addBranchSpecificInfo((BranchInstruction)instruction); 457 } else if (instruction instanceof TableSwitchInstruction) { 458 addTableSwitchSpecificInfo((TableSwitchInstruction)instruction); 459 } else if (instruction instanceof LookupSwitchInstruction) { 460 addLookupSwitchSpecificInfo((LookupSwitchInstruction)instruction); 461 } 462 } 463 464 private void addImmediateByteSpecificInfo(ImmediateByteInstruction instruction) { 465 466 int opcode = instruction.getOpcode(); 467 int sourceOffset = instruction.getOffset(); 468 int immediateByte = instruction.getImmediateByte(); 469 470 if (opcode == Opcodes.OPCODE_LDC) { 471 addConstantPoolLink(immediateByte, sourceOffset); 472 } else if (opcode == Opcodes.OPCODE_NEWARRAY) { 473 String verbose = OpcodesUtil.getArrayTypeVerbose(immediateByte); 474 appendString(" " + immediateByte + " (" + verbose + ")", 475 STYLE_IMMEDIATE_VALUE); 476 477 } else { 478 appendString(" " + immediateByte, 479 STYLE_IMMEDIATE_VALUE); 480 481 if (instruction instanceof IncrementInstruction) { 482 appendString(" by", STYLE_NORMAL); 483 appendString(" " + ((IncrementInstruction)instruction).getIncrementConst(), 484 STYLE_IMMEDIATE_VALUE); 485 } 486 } 487 } 488 489 private void addImmediateShortSpecificInfo(ImmediateShortInstruction instruction) { 490 491 int opcode = instruction.getOpcode(); 492 int sourceOffset = instruction.getOffset(); 493 int immediateShort = instruction.getImmediateShort(); 494 495 if (opcode == Opcodes.OPCODE_SIPUSH) { 496 appendString(" " + immediateShort, 497 STYLE_IMMEDIATE_VALUE); 498 } else { 499 addConstantPoolLink(immediateShort, sourceOffset); 500 501 if (instruction instanceof InvokeInterfaceInstruction) { 502 appendString(" count " + ((InvokeInterfaceInstruction)instruction).getCount(), 503 STYLE_IMMEDIATE_VALUE); 504 505 } else if (instruction instanceof MultianewarrayInstruction) { 506 appendString(" dim " + ((MultianewarrayInstruction)instruction).getDimensions(), 507 STYLE_IMMEDIATE_VALUE); 508 509 } 510 } 511 512 } 513 514 private void addImmediateIntSpecificInfo(ImmediateIntInstruction instruction) { 515 516 int immediateInt = instruction.getImmediateInt(); 517 int sourceOffset = instruction.getOffset(); 518 519 addConstantPoolLink(immediateInt, sourceOffset); 520 521 } 522 523 private void addBranchSpecificInfo(BranchInstruction instruction) { 524 525 int branchOffset = instruction.getBranchOffset(); 526 int instructionOffset = instruction.getOffset(); 527 528 addOffsetLink(branchOffset, instructionOffset); 529 530 } 531 532 private void addTableSwitchSpecificInfo(TableSwitchInstruction instruction) { 533 534 int instructionOffset = instruction.getOffset(); 535 int lowByte = instruction.getLowByte(); 536 int highByte = instruction.getHighByte(); 537 int[] jumpOffsets = instruction.getJumpOffsets(); 538 539 appendString(" " + lowByte + " to " + highByte, STYLE_IMMEDIATE_VALUE); 540 newLine(); 541 542 for (int i = 0; i <= highByte - lowByte; i++) { 543 appendString(offsetBlank + TAB_STRING + (i + lowByte) + ": ", STYLE_IMMEDIATE_VALUE); 544 addOffsetLink(jumpOffsets[i], instructionOffset); 545 newLine(); 546 547 } 548 appendString(offsetBlank + TAB_STRING + "default: ", STYLE_IMMEDIATE_VALUE); 549 addOffsetLink(instruction.getDefaultOffset(), instructionOffset); 550 551 } 552 553 private void addLookupSwitchSpecificInfo(LookupSwitchInstruction instruction) { 554 555 int instructionOffset = instruction.getOffset(); 556 java.util.List matchOffsetPairs = instruction.getMatchOffsetPairs(); 557 int matchOffsetPairsCount = matchOffsetPairs.size(); 558 559 appendString(" " + matchOffsetPairsCount, STYLE_IMMEDIATE_VALUE); 560 newLine(); 561 562 MatchOffsetPair matchOffsetPairEntry; 563 for (int i = 0; i < matchOffsetPairsCount; i++) { 564 matchOffsetPairEntry = (MatchOffsetPair)matchOffsetPairs.get(i); 565 appendString(offsetBlank + TAB_STRING + matchOffsetPairEntry.getMatch() + ": ", 566 STYLE_IMMEDIATE_VALUE); 567 addOffsetLink(matchOffsetPairEntry.getOffset(), instructionOffset); 568 newLine(); 569 570 } 571 appendString(offsetBlank + TAB_STRING + "default: ", STYLE_IMMEDIATE_VALUE); 572 addOffsetLink(instruction.getDefaultOffset(), instructionOffset); 573 574 } 575 576 private void addConstantPoolLink(int constantPoolIndex, int sourceOffset) { 577 578 appendString(" ", STYLE_NORMAL); 579 int startCharIndex = getCurrentCharIndex(); 580 appendString("#" + constantPoolIndex, STYLE_LINK); 581 int endCharIndex = getCurrentCharIndex(); 582 lineToLink.put(new Integer (getCurrentLine()), new ConstantPoolLink(startCharIndex, endCharIndex, sourceOffset, constantPoolIndex)); 583 584 try { 585 String name = classFile.getConstantPoolEntryName(constantPoolIndex); 586 if (name.length() > 0) { 587 appendString(" <" + name + ">", STYLE_SMALL); 588 } 589 } catch (InvalidByteCodeException ex) { 590 } 591 } 592 593 private void addOffsetLink(int branchOffset, int sourceOffset) { 594 595 int targetOffset = branchOffset + sourceOffset; 596 597 appendString(" ", STYLE_NORMAL); 598 int startCharIndex = getCurrentCharIndex(); 599 appendString(String.valueOf(targetOffset), STYLE_LINK); 600 int endCharIndex = getCurrentCharIndex(); 601 lineToLink.put(new Integer (getCurrentLine()), new OffsetLink(startCharIndex, endCharIndex, sourceOffset, targetOffset)); 602 603 appendString(" (" + (branchOffset > 0 ? "+" : "") + String.valueOf(branchOffset) + ")", 604 STYLE_IMMEDIATE_VALUE); 605 } 606 607 private int getCurrentCharIndex() { 608 609 Iterator it = currentLineCache.iterator(); 610 int offset = 0; 611 while (it.hasNext()) { 612 LineCacheEntry entry = (LineCacheEntry)it.next(); 613 offset += entry.text.length(); 614 } 615 return offset; 616 } 617 618 private int getCurrentLine() { 619 return lines.size(); 620 } 621 622 private void appendString(String text, Map attributes) { 623 currentLineCache.add(new LineCacheEntry(text, attributes)); 624 } 625 626 private void newLine() { 627 628 String text = getCurrentLineText(); 629 AttributedString attrString = new AttributedString (text, STYLE_BASE); 630 Iterator it = currentLineCache.iterator(); 631 int startCharIndex = 0; 632 while (it.hasNext()) { 633 LineCacheEntry entry = (LineCacheEntry)it.next(); 634 int endCharIndex = startCharIndex + entry.text.length(); 635 attrString.addAttributes(entry.attributes, startCharIndex, endCharIndex); 636 startCharIndex = endCharIndex; 637 } 638 lines.add(attrString); 639 textLines.add(text); 640 641 if (lineHeight == 0) { 642 TextLayout textLayout = new TextLayout(attrString.getIterator(), frc); 643 lineHeight = (int)(textLayout.getAscent() + textLayout.getDescent() + textLayout.getLeading()); 644 ascent = (int)textLayout.getAscent(); 645 textLayout = new TextLayout("0", STYLE_BASE, frc); 646 characterWidth = (int)textLayout.getAdvance(); 647 } 648 currentHeight += lineHeight; 649 currentWidth = Math.max(currentWidth, characterWidth * text.length()); 650 651 currentLineCache.clear(); 652 } 653 654 private String getCurrentLineText() { 655 656 StringBuffer buffer = new StringBuffer (getCurrentLineLength()); 657 Iterator it = currentLineCache.iterator(); 658 while (it.hasNext()) { 659 LineCacheEntry entry = (LineCacheEntry)it.next(); 660 buffer.append(entry.text); 661 } 662 return buffer.toString(); 663 } 664 665 private int getCurrentLineLength() { 666 667 int length = 0; 668 Iterator it = currentLineCache.iterator(); 669 while (it.hasNext()) { 670 LineCacheEntry entry = (LineCacheEntry)it.next(); 671 length += entry.text.length(); 672 } 673 return length; 674 } 675 676 private static class LineCacheEntry { 677 678 private String text; 679 private Map attributes; 680 681 private LineCacheEntry(String text, Map attributes) { 682 this.text = text; 683 this.attributes = attributes; 684 } 685 } 686 687 private static class BytecodeLink { 688 689 private int startCharIndex; 690 private int endCharIndex; 691 protected int sourceOffset; 692 693 private BytecodeLink(int startCharIndex, int endCharIndex, int sourceOffset) { 694 this.startCharIndex = startCharIndex; 695 this.endCharIndex = endCharIndex; 696 this.sourceOffset = sourceOffset; 697 } 698 } 699 700 private static class ConstantPoolLink extends BytecodeLink { 701 702 private int cpIndex; 703 704 private ConstantPoolLink(int startCharIndex, int endCharIndex, int sourceOffset, int cpIndex) { 705 super(startCharIndex, endCharIndex, sourceOffset); 706 this.cpIndex = cpIndex; 707 } 708 709 } 710 711 private static class OffsetLink extends BytecodeLink { 712 713 private int targetOffset; 714 715 private OffsetLink(int startCharIndex, int endCharIndex, int sourceOffset, int targetOffset) { 716 super(startCharIndex, endCharIndex, sourceOffset); 717 this.targetOffset = targetOffset; 718 } 719 720 } 721 722 } 723 | Popular Tags |