gt:                 if (branchOffset != 0) {
context: /*
      This library is free software; you can redistribute it and/or
      modify it under the terms of the GNU General Public
      License as published by the Free Software Foundation; either
      version 2 of the license, or (at your option) any later version.
  */
  
  package org.gjt.jclasslib.browser.detail.attributes.code;
  
  import org.gjt.jclasslib.browser.BrowserHistory;
  import org.gjt.jclasslib.browser.BrowserServices;
  import org.gjt.jclasslib.browser.ConstantPoolHyperlinkListener;
  import org.gjt.jclasslib.bytecode.*;
  import org.gjt.jclasslib.io.ByteCodeReader;
  import org.gjt.jclasslib.structures.ClassFile;
  import org.gjt.jclasslib.structures.InvalidByteCodeException;
  import org.gjt.jclasslib.structures.attributes.CodeAttribute;
  
  import javax.swing.*;
  import javax.swing.border.Border;
  import javax.swing.border.EmptyBorder;
  import javax.swing.tree.TreePath;
  import java.awt.*;
  import java.awt.datatransfer.StringSelection;
  import java.awt.font.FontRenderContext;
  import java.awt.font.TextAttribute;
  import java.awt.font.TextHitInfo;
  import java.awt.font.TextLayout;
  import java.io.IOException;
  import java.text.AttributedString;
  import java.util.*;
  import java.util.List;
  
  /**
      Bytecode renderer.
  
      @author <a href="mailto:jclasslib@ej-technologies.com">Ingo Kegel</a>
  */
  public class ByteCodeDisplay extends JPanel implements Scrollable {
  
      /** Horizontal margin. */
      public static final int MARGIN_X = 3;
      /** Vertical margin. */
      public static final int MARGIN_Y = 3;
  
      /** Border for the renderer. */
      public static final Border BORDER = new EmptyBorder(MARGIN_Y, MARGIN_X, MARGIN_Y, MARGIN_X);
  
      private static Map<TextAttribute, Object> STYLE_BASE;
      private static Map<TextAttribute, Object> STYLE_NORMAL;
      private static Map<TextAttribute, Object> STYLE_SMALL;
      private static Map<TextAttribute, Object> STYLE_LINK;
      private static Map<TextAttribute, Object> STYLE_OFFSET;
      private static Map<TextAttribute, Object> STYLE_INSTRUCTION;
      private static Map<TextAttribute, Object> STYLE_IMMEDIATE_VALUE;
  
      private static final String TAB_STRING = "        ";
  
      static {
          initStyles(null);
      }
  
      public static void initStyles(Font baseFont) {
  
          STYLE_BASE = new HashMap<TextAttribute, Object>(2);
          if (baseFont != null) {
              STYLE_BASE.put(TextAttribute.FAMILY, baseFont.getFamily());
          } else {
              baseFont = UIManager.getFont("TextArea.font");
              STYLE_BASE.put(TextAttribute.FAMILY, "MonoSpaced");
          }
  
          STYLE_BASE.put(TextAttribute.SIZE, (float)baseFont.getSize());
  
          STYLE_NORMAL = new HashMap<TextAttribute, Object>(0);
  
          STYLE_SMALL = new HashMap<TextAttribute, Object>(1);
          STYLE_SMALL.put(TextAttribute.SIZE, (float)(baseFont.getSize() - 1));
  
          STYLE_LINK = new HashMap<TextAttribute, Object>(3);
          STYLE_LINK.put(TextAttribute.FOREGROUND, new Color(0, 128, 0));
          STYLE_LINK.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
          STYLE_LINK.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
  
          STYLE_OFFSET = new HashMap<TextAttribute, Object>(1);
          STYLE_OFFSET.put(TextAttribute.FOREGROUND, new Color(128, 0, 0));
  
          STYLE_INSTRUCTION = new HashMap<TextAttribute, Object>(1);
          STYLE_INSTRUCTION.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
  
          STYLE_IMMEDIATE_VALUE = new HashMap<TextAttribute, Object>(2);
          STYLE_IMMEDIATE_VALUE.put(TextAttribute.FOREGROUND, Color.magenta);
          STYLE_IMMEDIATE_VALUE.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
      }
  
      private ByteCodeDetailPane detailPane;
  
      private CodeAttribute codeAttribute;
      private ClassFile classFile;
  
      private int offsetWidth;
      private String offsetBlank;
      private HashMap<Integer, Integer> offsetToLine = new HashMap<Integer, Integer>();
      private ArrayList<AttributedString> lines = new ArrayList<AttributedString>();
      private ArrayList<String> textLines = new ArrayList<String>();
      private TextLayout[] textLayouts;
      private Map<Integer, BytecodeLink> lineToLink = new HashMap<Integer, BytecodeLink>();
      private Set<AbstractInstruction> invalidBranches = new HashSet<AbstractInstruction>();
  
      private LinkedList<LineCacheEntry> currentLineCache = new LinkedList<LineCacheEntry>();
      private FontRenderContext frc;
      private float currentHeight;
      private float currentWidth;
      private int lineHeight;
      private int ascent;
      private int characterWidth;
  
      /**
       * Get the left-padded value for a number.
       * @param number the number
       * @param width the total width.
       * @return the padded string.
       */
      public static String getPaddedValue(int number, int width) {
  
          StringBuilder buffer = new StringBuilder();
          String value = String.valueOf(number);
          int valueLength = value.length();
          for (int i = valueLength; i < width; i++) {
              buffer.append(' ');
          }
          buffer.append(value);
          return buffer.toString();
      }
  
      /**
       * Constructor.
       * @param detailPane the parent detail pane.
       */
      public ByteCodeDisplay(ByteCodeDetailPane detailPane) {
          this.detailPane = detailPane;
  
          setupComponent();
          setupEventHandlers();
      }
  
      // Scrollable
  
      public Dimension getPreferredScrollableViewportSize() {
          return null;
      }
  
      public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
  
          if (orientation == SwingConstants.HORIZONTAL) {
              return 10;
          } else {
              if (lineHeight == 0) {
                  return 1;
              }
              int currentY = ((JViewport)getParent()).getViewPosition().y;
              float line = 1f * (currentY - MARGIN_Y) / lineHeight;
              int targetLine = (int)(direction < 0 ? Math.floor(line) - 1: Math.ceil(line) + 1);
              int targetY = MARGIN_Y + targetLine * lineHeight + 1;
              return Math.abs(currentY - targetY);
          }
      }
  
      public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
  
          JViewport viewport = (JViewport)getParent();
          if (orientation == SwingConstants.HORIZONTAL) {
              return viewport.getWidth();
          } else {
              if (lineHeight == 0) {
                  return 1;
              }
              int currentY = viewport.getViewPosition().y;
              int rawTargetY = currentY + (direction < 0 ? -1 : 1) * viewport.getHeight();
              float line = 1f * (rawTargetY - MARGIN_Y) / lineHeight;
              int targetLine = (int)(direction < 0 ? Math.ceil(line): Math.floor(line));
              int targetY = MARGIN_Y + targetLine * lineHeight + 1;
  
              return Math.abs(currentY - targetY);
          }
      }
  
      public boolean getScrollableTracksViewportWidth() {
          return false;
      }
  
      public boolean getScrollableTracksViewportHeight() {
          return false;
      }
  
      // end Scrollable
  
  
      /**
       * Get the currently displayed code attribute.
       * @return the code attribute.
       */
      public CodeAttribute getCodeAttribute() {
          return codeAttribute;
      }
  
      /**
       * Get the current line count.
       * @return the line count.
       */
      public int getLineCount() {
          return lines.size();
      }
  
      /**
       * Get the current line height.
       * @return the line height.
       */
      public int getLineHeight() {
          return lineHeight;
      }
  
      /**
       * Get the current line ascent.
       * @return the line ascent.
       */
      public int getAscent() {
          return ascent;
      }
  
      /**
       * Set the code attribute that is to be displayed.
       * @param codeAttribute the code attribute.
       * @param classFile the class file of the code attribute.
       */
      public void setCodeAttribute(CodeAttribute codeAttribute, ClassFile classFile) {
          this.codeAttribute = codeAttribute;
          this.classFile = classFile;
          frc = ((Graphics2D)getGraphics()).getFontRenderContext();
          setupTextLayouts();
          invalidate();
      }
  
      /**
       * Perform a link operation at a given point. Does nothing if there is no
       * link at this point.
       * @param point the point.
       */
      public void link(Point point) {
  
          BytecodeLink link = getLink(point);
          if (link == null) {
              return;
          }
          updateHistory(link.sourceOffset);
  
          if (link instanceof ConstantPoolLink) {
              ConstantPoolHyperlinkListener.link(detailPane.getBrowserServices(), ((ConstantPoolLink)link).cpIndex);
          } else if (link instanceof OffsetLink) {
              int targetOffset = ((OffsetLink)link).targetOffset;
              scrollToOffset(targetOffset);
              updateHistory(targetOffset);
          }
      }
  
      /**
       * Returns whether there is a link below the given point.
       * @param point the point.
       * @return the value.
       */
      public boolean isLink(Point point) {
          return getLink(point) != null;
      }
  
      /**
       * Scroll the view to a given bytecode offset.
       * @param offset the bytecode offset.
       */
      public void scrollToOffset(int offset) {
  
          Integer line = offsetToLine.get(new Integer(offset));
          if (line == null) {
              return;
          }
          Rectangle target = new Rectangle(0, line * lineHeight + MARGIN_Y + 1, 10, getParent().getHeight());
          scrollRectToVisible(target);
      }
  
      /**
       * Copy the view text to the clipboard.
       */
      public void copyToClipboard() {
          StringSelection stringSelection = new StringSelection(getClipboardText());
          Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, stringSelection);
      }
  
      public String getClipboardText() {
          StringBuilder buffer = new StringBuilder();
          for (String line : textLines) {
              buffer.append(line);
              buffer.append('\n');
          }
          return buffer.toString();
      }
  
      protected void paintComponent(Graphics graphics) {
  
          if (lineHeight == 0) {
              return;
          }
  
          Graphics2D g = (Graphics2D)graphics;
          g.translate(MARGIN_X, MARGIN_Y);
          Rectangle clipBounds = graphics.getClipBounds();
          Paint oldPaint = g.getPaint();
          g.setPaint(Color.WHITE);
          g.fill(clipBounds);
          g.setPaint(oldPaint);
          int startLine = Math.max(0, clipBounds.y / lineHeight - 1);
          int endLine = Math.min(lines.size(), (clipBounds.y + clipBounds.height) / lineHeight + 1);
          for (int i = startLine; i < endLine; i++) {
              TextLayout textLayout = getOrCreateTextLayout(i);
              textLayout.draw(g, 0, i * lineHeight + textLayout.getAscent());
          }
  
          g.translate(-MARGIN_X, -MARGIN_Y);
      }
  
      private TextLayout getOrCreateTextLayout(int i) {
  
          TextLayout textLayout = textLayouts[i];
          if (textLayout == null) {
              textLayout = textLayouts[i] = new TextLayout((lines.get(i)).getIterator(), frc);
          }
          return textLayout;
      }
  
      private void setupComponent() {
  
          setBorder(BORDER);
          setDoubleBuffered(false);
          setOpaque(false);
      }
  
      private void setupEventHandlers() {
      }
  
      private BytecodeLink getLink(Point point) {
  
          if (lineHeight == 0) {
              return null;
          }
          int x = point.x - MARGIN_X;
          int y = point.y - MARGIN_Y;
          int line = y / lineHeight;
          BytecodeLink link = lineToLink.get(line);
          if (link == null) {
              return null;
          }
  
          TextLayout textLayout = getOrCreateTextLayout(line);
          TextHitInfo textHitInfo = textLayout.hitTestChar(x, y - line * lineHeight);
          int charIndex = textHitInfo.getCharIndex();
          if (charIndex >= link.startCharIndex && charIndex < link.endCharIndex) {
              return link;
          } else {
              return null;
          }
      }
  
      private void updateHistory(int offset) {
  
          BrowserServices services = detailPane.getBrowserServices();
          TreePath treePath = services.getBrowserComponent().getTreePane().getTree().getSelectionPath();
  
          BrowserHistory history = services.getBrowserComponent().getHistory();
          history.updateHistory(treePath, offset);
      }
  
      private void setupTextLayouts() {
  
          lineHeight = 0;
          currentHeight = 0f;
          currentWidth = 0f;
          textLines.clear();
          lines.clear();
          textLayouts = null;
          offsetToLine.clear();
          lineToLink.clear();
          invalidBranches.clear();
  
  
          byte[] code = codeAttribute.getCode();
  
          try {
              ArrayList<AbstractInstruction> instructions = ByteCodeReader.readByteCode(code);
              verifyOffsets(instructions);
  
              calculateOffsetWidth(instructions);
              detailPane.setCurrentInstructions(instructions);
  
              for (AbstractInstruction instruction : instructions) {
                  addInstructionToDocument(instruction);
              }
              textLayouts = new TextLayout[lines.size()];
          } catch (IOException ex) {
              ex.printStackTrace();
          }
          setPreferredSize(new Dimension((int)currentWidth + 2 * MARGIN_X, (int)currentHeight + 2 * MARGIN_Y));
      }
  
      private void verifyOffsets(ArrayList<AbstractInstruction> instructions) {
          int instructionsLength = instructions.size();
          for (int i = 0; i < instructionsLength; i++) {
              AbstractInstruction instruction = instructions.get(i);
              if (instruction instanceof AbstractBranchInstruction) {
                  int branchOffset = ((AbstractBranchInstruction)instruction).getBranchOffset();
                  int targetDistance = 0;
                  if (branchOffset > 0) {
                      branchOffset -= instruction.getSize();
                      while (branchOffset > 0 && i + targetDistance + 1 < instructionsLength) {
                          ++targetDistance;
                          branchOffset -= (instructions.get(i + targetDistance)).getSize();
                      }
                  } else {
                      while (branchOffset < 0 && i + targetDistance > 0) {
                          --targetDistance;
                          branchOffset += (instructions.get(i + targetDistance)).getSize();
                      }
                  }
  