Package net.sf.rej.gui.compare

Source Code of net.sf.rej.gui.compare.ComparePanel

/* Copyright (C) 2004-2007 Sami Koivu
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
package net.sf.rej.gui.compare;

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;

import net.sf.rej.Imports;
import net.sf.rej.gui.EditorFacade;
import net.sf.rej.gui.MainWindow;
import net.sf.rej.gui.SystemFacade;
import net.sf.rej.gui.dialog.QuickOutlineDialog;
import net.sf.rej.gui.editor.CaseInsensitiveMatcher;
import net.sf.rej.gui.editor.rendering.BytecodeRenderer;
import net.sf.rej.gui.editor.rendering.HTMLSyntaxDrawer;
import net.sf.rej.gui.editor.rendering.PlaintextSyntaxDrawer;
import net.sf.rej.gui.editor.row.BlankRow;
import net.sf.rej.gui.editor.row.ClassCommentRow;
import net.sf.rej.gui.editor.row.ClassDefRow;
import net.sf.rej.gui.editor.row.CodeRow;
import net.sf.rej.gui.editor.row.DeprecatedAnnotationDefRow;
import net.sf.rej.gui.editor.row.EditorRow;
import net.sf.rej.gui.editor.row.FieldDefRow;
import net.sf.rej.gui.editor.row.ImportDefRow;
import net.sf.rej.gui.editor.row.LabelRow;
import net.sf.rej.gui.editor.row.LocalVariableDefRow;
import net.sf.rej.gui.editor.row.MethodAnnotationDefRow;
import net.sf.rej.gui.editor.row.MethodDefRow;
import net.sf.rej.gui.editor.row.PackageDefRow;
import net.sf.rej.gui.editor.transfer.BytecodeEditorTransferHandler;
import net.sf.rej.gui.editor.transfer.TransferComponent;
import net.sf.rej.gui.editor.transfer.Transferrable;
import net.sf.rej.java.ClassFile;
import net.sf.rej.java.Code;
import net.sf.rej.java.Descriptor;
import net.sf.rej.java.Field;
import net.sf.rej.java.Interface;
import net.sf.rej.java.LocalVariable;
import net.sf.rej.java.Method;
import net.sf.rej.java.attribute.Attributes;
import net.sf.rej.java.attribute.CodeAttribute;
import net.sf.rej.java.attribute.LineNumberTableAttribute;
import net.sf.rej.java.attribute.LocalVariableTableAttribute;
import net.sf.rej.java.attribute.RuntimeInvisibleAnnotationsAttribute;
import net.sf.rej.java.attribute.RuntimeVisibleAnnotationsAttribute;
import net.sf.rej.java.attribute.SourceFileAttribute;
import net.sf.rej.java.attribute.annotations.Annotation;
import net.sf.rej.java.instruction.DecompilationContext;
import net.sf.rej.java.instruction.Instruction;
import net.sf.rej.java.instruction.Label;

/**
* Bytecode compare display. Uses a
* <code>JList</code> with a custom renderer.
*
* @author Sami Koivu
*/

public class ComparePanel extends JPanel implements TransferComponent {

  private static final long serialVersionUID = 1L;

  private CaseInsensitiveMatcher lastSearch = null;
  private String lastQueryString = null;
 
  private List<EditorRow> rowsAll;
  private Collection<EditorRow> rowsA;
  private Collection<EditorRow> rowsB;

  private CodeCompareRenderer renderer = new CodeCompareRenderer();

  DefaultListModel model = new DefaultListModel();

  JList list = new JList(this.model);
  private JScrollPane editorScrollPane = new JScrollPane(this.list);
 
  private ClassFile cfA;
  private ClassFile cfB;

  private JLabel label = new JLabel("Bytecode Compare");

  BufferedImage clueImage = null;

  public ComparePanel() {
    this.setLayout(new BorderLayout());
    this.list.setCellRenderer(this.renderer);
    this.list.setTransferHandler(new BytecodeEditorTransferHandler(this));
    this.add(this.label, BorderLayout.NORTH);
    this.add(this.editorScrollPane, BorderLayout.CENTER);
    this.editorScrollPane.setVerticalScrollBar(new JScrollBar() {
      final AlphaComposite SEMI_OPAQUE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
          @Override
          public void paint(Graphics g) {
            g.setPaintMode();
            super.paint(g);
            if (clueImage != null) {
              Graphics2D g2 = (Graphics2D)g;
              g2.setComposite(SEMI_OPAQUE);
              g2.drawImage(clueImage, 2, 17, getWidth()-3, getHeight()-34, null);
            }
          }
        });
  }
 
  public void setClassFiles(ClassFile cfA, ClassFile cfB) {
    this.cfA = cfA;
    this.cfB = cfB;
   
    if (cfA.getFullClassName().equals(cfB.getFullClassName())) {
      this.label.setText("Bytecode Compare: " + cfA.getFullClassName());
    } else {
      this.label.setText("Bytecode Compare: " + cfA.getFullClassName() + " / " + cfB.getFullClassName());     
    }
   
    load(cfA, cfB);
  }

  private void load(ClassFile cfA, ClassFile cfB) {
    this.rowsAll = new ArrayList<EditorRow>();
    this.rowsA = new HashSet<EditorRow>();
    this.rowsB = new HashSet<EditorRow>();

    // Package
    if (cfA.getPackageName().equals(cfB.getPackageName())) {
      this.rowsAll.add(new PackageDefRow(cfA));     
    } else {
      PackageDefRow pdrA = new PackageDefRow(cfA);
      PackageDefRow pdrB = new PackageDefRow(cfB);
      this.rowsA.add(pdrA);
      this.rowsB.add(pdrB);
      this.rowsAll.add(pdrA);
      this.rowsAll.add(pdrB);
    }
   
    this.rowsAll.add(new BlankRow());

    // Imports
    Imports importsA = EditorFacade.getInstance().getImports(cfA);
    Imports importsB = EditorFacade.getInstance().getImports(cfB);
    this.renderer.setImports(importsA, importsB);
   
    Set<String> tsA = importsA.getImports();
    Set<String> tsB = importsB.getImports();
    Set<String> allOrdered = new TreeSet<String>();
    allOrdered.addAll(tsA);
    allOrdered.addAll(tsB);
    for (String imp : allOrdered) {
      ImportDefRow idr = new ImportDefRow(imp);
      this.rowsAll.add(idr);
      if (!tsA.contains(imp)) {
        this.rowsB.add(idr);
      }
      if (!tsB.contains(imp)) {
        this.rowsA.add(idr);
      }
    }

    if (allOrdered.size() > 0) {
      this.rowsAll.add(new BlankRow());
      /* empty space between imports and class def */
    }
   
    // Add some useful information as comments

    // Source file name
    SourceFileAttribute sfA = cfA.getAttributes().getSourceFileAttribute();
    SourceFileAttribute sfB = cfB.getAttributes().getSourceFileAttribute();
    if (sfA == null && sfB == null) {
      // Add nothing, neither has a source file attribute
    } else if (sfA == null || sfB == null) {
      // Only one has a source file attribute
      if (sfA != null) {
        ClassCommentRow sfComment = new ClassCommentRow("SourceFile = " + sfA.getSourceFile());
        this.rowsA.add(sfComment);
        this.rowsAll.add(sfComment);
      }
     
      if (sfB != null) {
        ClassCommentRow sfComment = new ClassCommentRow("SourceFile = " + sfB.getSourceFile());
        this.rowsB.add(sfComment);
        this.rowsAll.add(sfComment);       
      }
     
    } else {
      // Both have source file attributes
      if (sfA.getSourceFile().equals(sfB.getSourceFile())) {
        ClassCommentRow sfComment = new ClassCommentRow("SourceFile = " + sfA.getSourceFile());
        this.rowsAll.add(sfComment);
      } else {
        ClassCommentRow sfCommentA = new ClassCommentRow("SourceFile = " + sfA.getSourceFile());
        this.rowsA.add(sfCommentA);
        this.rowsAll.add(sfCommentA);

        ClassCommentRow sfCommentB = new ClassCommentRow("SourceFile = " + sfB.getSourceFile());
        this.rowsB.add(sfCommentB);
        this.rowsAll.add(sfCommentB);
      }
    }
   
    // Class version
    if (cfA.getMajorVersion() == cfB.getMajorVersion()
     && cfA.getMinorVersion() == cfB.getMinorVersion()) {
        ClassCommentRow versionComment = new ClassCommentRow("Class Version: " + cfA.getMajorVersion() + "." + cfA.getMinorVersion());
        this.rowsAll.add(versionComment);
    } else {
      ClassCommentRow versionCommentA = new ClassCommentRow("Class Version: " + cfA.getMajorVersion() + "." + cfA.getMinorVersion());
      this.rowsA.add(versionCommentA);
      ClassCommentRow versionCommentB = new ClassCommentRow("Class Version: " + cfB.getMajorVersion() + "." + cfB.getMinorVersion());
      this.rowsB.add(versionCommentB);
      this.rowsAll.add(versionCommentA);
      this.rowsAll.add(versionCommentB);
    }

    // Class annotations
    /* TODO:
    RuntimeInvisibleAnnotationsAttribute annInvisible = cf.getAttributes()
        .getRuntimeInvisibleAnnotationsAttribute();
    RuntimeVisibleAnnotationsAttribute annVisible = cf.getAttributes()
        .getRuntimeVisibleAnnotationsAttribute();
    List<Annotation> classAnnotations = new ArrayList<Annotation>();
    if (annInvisible != null) {
      classAnnotations.addAll(annInvisible.getAnnotations());
    }
    if (annVisible != null) {
      classAnnotations.addAll(annVisible.getAnnotations());
    }
    for (Annotation annotation : classAnnotations) {
      ClassAnnotationDefRow adr = new ClassAnnotationDefRow(annotation);
      this.rows.add(adr);
    }
     */
   
    List<Interface> interfacesA = cfA.getInterfaces();
    List<Interface> interfacesB = cfB.getInterfaces();
    boolean interfacesAreEqual = interfacesA.equals(interfacesB);

    // Class
    if (cfA.getShortClassName().equals(cfB.getShortClassName())
     && cfA.getAccessFlags() == cfB.getAccessFlags()
     && cfA.getSuperClassName().equals(cfB.getSuperClassName())
     && interfacesAreEqual) {
      this.rowsAll.add(new ClassDefRow(cfA, true));
    } else {
      ClassDefRow cdrA = new ClassDefRow(cfA, true);
      ClassDefRow cdrB = new ClassDefRow(cfB, true);
      this.rowsA.add(cdrA);
      this.rowsB.add(cdrB);
      this.rowsAll.add(cdrA);
      this.rowsAll.add(cdrB);
    }
   
    this.rowsAll.add(new BlankRow());

    // Fields
    // TODO: Constant value compare
    Map<String, Field> fieldsA = new HashMap<String, Field>();
    for (Field field : cfA.getFields()) {
      fieldsA.put(field.getSignatureLine(), field);
    }
    Map<String, Field> fieldsB = new HashMap<String, Field>();
    for (Field field : cfB.getFields()) {
      fieldsB.put(field.getSignatureLine(), field);
    }
    Set<String> allFields = new TreeSet<String>();
    allFields.addAll(fieldsA.keySet());
    allFields.addAll(fieldsB.keySet());
    for (String fieldSignature : allFields) {
      // Field annotations
      /* TODO
      RuntimeInvisibleAnnotationsAttribute fieldAnnInvisible = field
          .getAttributes().getRuntimeInvisibleAnnotationsAttribute();
      RuntimeVisibleAnnotationsAttribute fieldAnnVisible = field
          .getAttributes().getRuntimeVisibleAnnotationsAttribute();
      List<Annotation> fieldAnnotations = new ArrayList<Annotation>();
      if (fieldAnnInvisible != null) {
        fieldAnnotations.addAll(fieldAnnInvisible.getAnnotations());
      }
      if (fieldAnnVisible != null) {
        fieldAnnotations.addAll(fieldAnnVisible.getAnnotations());
      }
      for (Annotation annotation : fieldAnnotations) {
        FieldAnnotationDefRow fadr = new FieldAnnotationDefRow(
            annotation);
        this.rows.add(fadr);
      }
       */

      FieldDefRow fdr = null;
      Field field = fieldsA.get(fieldSignature);
      if (field == null) {
        field = fieldsB.get(fieldSignature);
        fdr = new FieldDefRow(cfB, field);
        this.rowsB.add(fdr);
      } else {
        fdr = new FieldDefRow(cfA, field);
        if (!fieldsB.keySet().contains(fieldSignature)) {
          this.rowsA.add(fdr);
        }
      }
     
      this.rowsAll.add(fdr);
    }

    if (allFields.size() > 0) {
      this.rowsAll.add(new BlankRow());
    }

    // Methods
    Map<String, Method> methodsA = new HashMap<String, Method>();
    for (Method method : cfA.getMethods()) {
      Descriptor desc = method.getDescriptor();
      methodsA.put(desc.getReturn() + method.getName() + " " + desc.getParams(), method);
    }
    Map<String, Method> methodsB = new HashMap<String, Method>();
    for (Method method : cfB.getMethods()) {
      Descriptor desc = method.getDescriptor();
      methodsB.put(desc.getReturn() + method.getName() + " " + desc.getParams(), method);
    }

    Set<String> allMethods = new TreeSet<String>();
    allMethods.addAll(methodsA.keySet());
    allMethods.addAll(methodsB.keySet());
    for (String methodTypeNameParams : allMethods) {
      Method methodA = methodsA.get(methodTypeNameParams);
      Method methodB = methodsB.get(methodTypeNameParams);
      if (methodA == null) {
        List<EditorRow> methodRows = getMethodRows(cfB, methodB);
        this.rowsB.addAll(methodRows);
        this.rowsAll.addAll(methodRows);
        // method only exists in B
      } else if (methodB == null) {
        List<EditorRow> methodRows = getMethodRows(cfA, methodA);
        this.rowsA.addAll(methodRows);
        this.rowsAll.addAll(methodRows);
        // method only exists in A
      } else {
        // method exists in both
        addMethodRows(cfA, cfB, methodA, methodB);
      }

      this.rowsAll.add(new BlankRow());
    }

    this.rowsAll.add(new ClassDefRow(cfA, false));
   
    this.renderer.setRedSet(this.rowsA);
    this.renderer.setYellowSet(this.rowsB);

    this.model.clear();
    for (EditorRow er : this.rowsAll) {
      this.model.addElement(er);
    }
   
    // Scrollbar clue image
    int all = rowsAll.size();
    this.clueImage = new BufferedImage(10, all, BufferedImage.TYPE_INT_ARGB);
    Graphics g = this.clueImage.getGraphics();
    g.setColor(Color.RED);
    for (EditorRow er : rowsA) {
      int i = rowsAll.indexOf(er);
        g.fillRect(0, i, 10, 1);               
    }
    g.setColor(Color.YELLOW.darker());
    for (EditorRow er : rowsB) {
      int i = rowsAll.indexOf(er);
        g.fillRect(0, i, 10, 1);
    }
  }

  public void find() {
    if (this.rowsAll == null) return; // early return
   
    String query = (String)JOptionPane.showInputDialog(this, "Search for..", "Search", JOptionPane.QUESTION_MESSAGE, null, null, this.lastQueryString);
    if (query == null)
      return; // early return

    this.lastQueryString = query;
    this.lastSearch = new CaseInsensitiveMatcher(query);
    BytecodeRenderer renderer = new BytecodeRenderer();
    PlaintextSyntaxDrawer sd = new PlaintextSyntaxDrawer();
    Imports imports = EditorFacade.getInstance().getImports(cfA);

    for (int i = 0; i < model.size(); i++) {
      sd.clear();
      renderer.render((EditorRow) model.elementAt(i), sd, imports);
      if (this.lastSearch.matches(sd.getText())) {
        list.setSelectedIndex(i);
        list.ensureIndexIsVisible(i);
        SystemFacade.getInstance().setStatus("Found '" + query + "'.");
        return; // early return
      }
    }
    this.lastSearch = null;
    SystemFacade.getInstance().setStatus("No occurances of '" + query + "' found.");   
  }

  public void findNext() {
    if (this.lastSearch == null) {
      find();
    } else {
      BytecodeRenderer renderer = new BytecodeRenderer();
      PlaintextSyntaxDrawer sd = new PlaintextSyntaxDrawer();
      Imports imports = EditorFacade.getInstance().getImports(cfA);

      for (int i = this.list.getSelectedIndex() + 1; i < model
          .size(); i++) {
        sd.clear();
        renderer.render((EditorRow) model.elementAt(i), sd, imports);
        if (this.lastSearch.matches(sd.getText())) {
          this.list.setSelectedIndex(i);
          this.list.ensureIndexIsVisible(i);
          SystemFacade.getInstance().setStatus("Found '" + this.lastQueryString + "'.");
          return; // early return
        }
      }
      SystemFacade.getInstance().setStatus("No more occurances of '" + this.lastQueryString + "' found.");   
    }
  }

  public String getSelectionPlainText() {
    BytecodeRenderer renderer = new BytecodeRenderer();
    PlaintextSyntaxDrawer sd = new PlaintextSyntaxDrawer();
    Imports imports = EditorFacade.getInstance().getImports(this.cfA);
    for (Object obj : this.list.getSelectedValues()) {
      EditorRow er = (EditorRow) obj;
      renderer.render(er, sd, imports);
      sd.drawLineBreak();
    }

    return sd.getText();
  }

  public String getSelectionHTML() {
    BytecodeRenderer renderer = new BytecodeRenderer();
    HTMLSyntaxDrawer sd = new HTMLSyntaxDrawer();
    Imports imports = EditorFacade.getInstance().getImports(this.cfA);
    for (Object obj : this.list.getSelectedValues()) {
      EditorRow er = (EditorRow) obj;
      renderer.render(er, sd, imports);
      sd.drawLineBreak();
    }

    return "<HTML><FONT FACE=\"Courier New\">" + sd.getHTML() + "</FONT></HTML>";
  }

  public Object getSelectionObject() {
    return new ArrayList<Transferrable>();
  }

  public void pasteRows(Object data) {
  }
 
  public void outline() {
    if (this.rowsAll == null) {
      return; // early return
    }
   
    List<EditorRow> list = new ArrayList<EditorRow>();
    for (EditorRow er : this.rowsAll) {
      if (er instanceof PackageDefRow) {
        list.add(er);
      } else if (er instanceof ClassDefRow) {
        ClassDefRow cdr = (ClassDefRow) er;
        if (!cdr.isClosing()) {
          list.add(er);
        }
      } else if (er instanceof FieldDefRow) {
        list.add(er);
      } else if (er instanceof MethodDefRow) {
        MethodDefRow mdr = (MethodDefRow) er;
        if (!mdr.isClosing()) {
          list.add(er);
        }
      }
    }
   
    QuickOutlineDialog qod = new QuickOutlineDialog(MainWindow.getInstance(), list);
    qod.invoke();
    EditorRow er = qod.getSelected();
    if (er != null) {
      int index = this.rowsAll.indexOf(er);
      this.list.setSelectedIndex(index);
      this.list.ensureIndexIsVisible(index);
    }
  }
 
  /**
   * A straightforward method processing. This method is for obtaining a rows
   * of a method which only exists in one of the classes under comparison.
   * @param cf
   * @param method
   * @return a list of <code>EditorRow</code> objects.
   */
  private List<EditorRow> getMethodRows(ClassFile cf, Method method) {
    List<EditorRow> list = new ArrayList<EditorRow>();
   
    // Method annotations
    boolean deprecatedAnnotationAdded = false;
    RuntimeInvisibleAnnotationsAttribute methodAnnInvisible = method
        .getAttributes().getRuntimeInvisibleAnnotationsAttribute();
    RuntimeVisibleAnnotationsAttribute methodAnnVisible = method
        .getAttributes().getRuntimeVisibleAnnotationsAttribute();
    List<Annotation> methodAnnotations = new ArrayList<Annotation>();
    if (methodAnnInvisible != null) {
      methodAnnotations.addAll(methodAnnInvisible.getAnnotations());
    }
    if (methodAnnVisible != null) {
      methodAnnotations.addAll(methodAnnVisible.getAnnotations());
    }
    for (Annotation annotation : methodAnnotations) {
      MethodAnnotationDefRow madr = new MethodAnnotationDefRow(
          annotation);
      list.add(madr);
      if ("java.lang.Deprecated".equals(annotation.getName())) {
        deprecatedAnnotationAdded = true;
        // store this information so that
        // the Deprecated attribute isn't used to
        // create another deprecation EditorRow
      }
    }

    if (!deprecatedAnnotationAdded && method.isDeprecated()) {
      DeprecatedAnnotationDefRow ddr = new DeprecatedAnnotationDefRow();
      list.add(ddr);
    }

    MethodDefRow mdr = new MethodDefRow(cf, method, true, method.getAttributes().getCode() != null);
    list.add(mdr);

    Attributes attr = method.getAttributes();
    CodeAttribute codeAttr = attr.getCode();

    LineNumberTableAttribute lnAttr = null;
    LocalVariableTableAttribute lvs = null;
    if (codeAttr != null) {
      if (codeAttr.getAttributes() != null) {
        lnAttr = codeAttr.getAttributes().getLineNumberTable();
        lvs = codeAttr.getAttributes().getLocalVariableTable();
      }
      Code code = codeAttr.getCode();
      DecompilationContext dc = code.createDecompilationContext();
      List instructions = code.getInstructions();
      dc.setPosition(0);
      for (int j = 0; j < instructions.size(); j++) {
        Instruction instruction = (Instruction) instructions.get(j);

        if (instruction instanceof Label) {
          LabelRow lr = new LabelRow((Label) instruction, mdr);
          lr.setParentCode(code);
          list.add(lr);
          mdr.addCodeRow(lr);
        } else {
          int lineNumber = -1;

          if (lnAttr != null) {
            lineNumber = lnAttr.getLineNumber(dc.getPosition());
          }
          if (lvs != null) {
            List locals = lvs
                .getLocalVariable(dc.getPosition());
            for (int k = 0; k < locals.size(); k++) {
              LocalVariable lv = (LocalVariable) locals
                  .get(k);
              LocalVariableDefRow lvdr = new LocalVariableDefRow(
                  lv, mdr);
              list.add(lvdr);
              mdr.addLocalVariable(lvdr);
            }
          }

          CodeRow cd = new CodeRow(cf, mdr, instruction);
          cd.setPosition(dc.getPosition());
          cd.setDecompilationContext(dc);
          cd.setParentCode(code);

          if (lineNumber != -1) {
            cd.setLineNumber(lineNumber);
          }

          list.add(cd);
          mdr.addCodeRow(cd);

          dc.incrementPosition(instruction);
        }

      }

      list.add(new MethodDefRow(cf, method, false, true));
    }

    return list;
  }
 
  /**
   * Method contents (bytecode) diff
   * @param cfA
   * @param cfB
   * @param methodA
   * @param methodB
   */
  private void addMethodRows(ClassFile cfA, ClassFile cfB, Method methodA, Method methodB) {
    // TODO: Annotations and non-annotation deprecation
   
    // We matched these methods based on descriptor (=type & params) and name
    // but the thrown exceptions and access type may still be different
    // and if it is, we want that highlighted
    MethodDefRow mdrA = null;
    MethodDefRow mdrB = null;
    if (methodA.getAccessFlags() == methodB.getAccessFlags()
    && methodA.getExceptions().equals(methodB.getExceptions())) {
      // everything is equal, just add one line
      MethodDefRow mdr = new MethodDefRow(cfA, methodA, true, methodA.getAttributes().getCode() != null);
      mdrA = mdr;
      mdrB = mdr;
      this.rowsAll.add(mdr);
    } else {
      mdrA = new MethodDefRow(cfA, methodA, true, methodA.getAttributes().getCode() != null);
      rowsA.add(mdrA);
      mdrB = new MethodDefRow(cfB, methodB, true, methodB.getAttributes().getCode() != null);
      rowsB.add(mdrB);
      rowsAll.add(mdrA);
      rowsAll.add(mdrB);
    }

    Attributes attrA = methodA.getAttributes();
    Attributes attrB = methodB.getAttributes();
    CodeAttribute codeAttrA = attrA.getCode();
    CodeAttribute codeAttrB = attrB.getCode();

    LineNumberTableAttribute lnAttrA = null;
    LineNumberTableAttribute lnAttrB = null;
    LocalVariableTableAttribute lvsA = null;
    LocalVariableTableAttribute lvsB = null;
    if (codeAttrA != null && codeAttrB != null) {
      // TODO: deal with the case where only one is null
      // ie, one method has code, the other doesn't
     
      if (codeAttrA.getAttributes() != null) {
        lnAttrA = codeAttrA.getAttributes().getLineNumberTable();
        lvsA = codeAttrA.getAttributes().getLocalVariableTable();
      }
      if (codeAttrB.getAttributes() != null) {
        lnAttrB = codeAttrB.getAttributes().getLineNumberTable();
        lvsB = codeAttrB.getAttributes().getLocalVariableTable();
      }

      Code codeA = codeAttrA.getCode();
      Code codeB = codeAttrB.getCode();
     
      DecompilationContext dcA = codeA.createDecompilationContext();
      DecompilationContext dcB = codeB.createDecompilationContext();
     
      List<Instruction> instructionsA = codeA.getInstructions();
      List<Instruction> instructionsB = codeB.getInstructions();
      dcA.setPosition(0);
      dcB.setPosition(0);
     
      List<EditorRow> methodRowsA = getMethodRows(instructionsA, codeA, mdrA, lnAttrA, lvsA, dcA, cfA);
      List<EditorRow> methodRowsB = getMethodRows(instructionsB, codeB, mdrB, lnAttrB, lvsB, dcB, cfB);
     
      // find out the equal instructions at the beginning of the block
      int startEqCount = 0;
      while (true) {
        if (startEqCount == methodRowsA.size()) break;
        if (startEqCount == methodRowsB.size()) break;
       
        EditorRow erA = methodRowsA.get(startEqCount);
        EditorRow erB = methodRowsB.get(startEqCount);
        boolean equal = rowsAreEqual(erA, erB);
       
        if (!equal) break;
        startEqCount++;
      }
      for (int i=0; i < startEqCount; i++) {
        this.rowsAll.add(methodRowsA.get(0));
        methodRowsA.remove(0);
        methodRowsB.remove(0);
      }

      // find out the equal instructions at the end of each code block
      int endEqCount = 0;
      while (true) {
        if (endEqCount == methodRowsA.size()) break;
        if (endEqCount == methodRowsB.size()) break;
       
        EditorRow erA = methodRowsA.get((methodRowsA.size()-1)-endEqCount);
        EditorRow erB = methodRowsB.get((methodRowsB.size()-1)-endEqCount);
        boolean equal = rowsAreEqual(erA, erB);
       
        if (!equal) break;
        endEqCount++;
      }
      List<EditorRow> equalRowsAtTheEnd = new ArrayList<EditorRow>();
      for (int i=0; i < endEqCount; i++) {
        equalRowsAtTheEnd.add(methodRowsA.get(methodRowsA.size()-1));
        methodRowsA.remove(methodRowsA.size()-1);
        methodRowsB.remove(methodRowsB.size()-1);
      }
     
        int m = methodRowsA.size();
        int n = methodRowsB.size();
      int[][] C = new int[m+1][n+1];
        lcs(C, methodRowsA, methodRowsB);
       
        List<EditorRow> common = new ArrayList<EditorRow>();
        bt(C, methodRowsA, methodRowsB, m, n, common);

        for (EditorRow commonRow : common) {
          // rows from set A before the next common row
          while (!rowsAreEqual(methodRowsA.get(0), commonRow)) {
            this.rowsAll.add(methodRowsA.get(0));
            this.rowsA.add(methodRowsA.get(0));
            methodRowsA.remove(0);
          }

          // rows from set B before the next common row
          while (!rowsAreEqual(methodRowsB.get(0), commonRow)) {
            this.rowsAll.add(methodRowsB.get(0));
            this.rowsB.add(methodRowsB.get(0));
            methodRowsB.remove(0);
          }
         
          this.rowsAll.add(commonRow);
          methodRowsA.remove(0);
          methodRowsB.remove(0);
        }
       
        this.rowsA.addAll(methodRowsA);
        this.rowsAll.addAll(methodRowsA);
       
        this.rowsB.addAll(methodRowsB);
        this.rowsAll.addAll(methodRowsB);

      this.rowsAll.addAll(equalRowsAtTheEnd);
      this.rowsAll.add(new MethodDefRow(cfA, methodA, false, true));
    }

  }
 
  private List<EditorRow> getMethodRows(List<Instruction> instructions, Code code, MethodDefRow mdr, LineNumberTableAttribute lnAttr, LocalVariableTableAttribute lvs, DecompilationContext dc, ClassFile cf) {
    List<EditorRow> list = new ArrayList<EditorRow>();
   
    for (Instruction instruction : instructions) {

      if (instruction instanceof Label) {
        LabelRow lr = new LabelRow((Label) instruction, mdr);
        lr.setParentCode(code);
        list.add(lr);
        mdr.addCodeRow(lr);
      } else {
        int lineNumber = -1;

        if (lnAttr != null) {
          lineNumber = lnAttr.getLineNumber(dc.getPosition());
        }
        if (lvs != null) {
          List locals = lvs
              .getLocalVariable(dc.getPosition());
          for (int k = 0; k < locals.size(); k++) {
            LocalVariable lv = (LocalVariable) locals
                .get(k);
            LocalVariableDefRow lvdr = new LocalVariableDefRow(
                lv, mdr);
            list.add(lvdr);
            mdr.addLocalVariable(lvdr);
          }
        }

        CodeRow cd = new CodeRow(cf, mdr, instruction);
        cd.setPosition(dc.getPosition());
        cd.setDecompilationContext(dc);
        cd.setParentCode(code);

        if (lineNumber != -1) {
          cd.setLineNumber(lineNumber);
        }

        list.add(cd);
        mdr.addCodeRow(cd);

        dc.incrementPosition(instruction);
      }

    }
   
    return list;
  }
 
  public static boolean rowsAreEqual(EditorRow erA, EditorRow erB) {
    if (!erA.getClass().equals(erB.getClass())) return false;
   
    if (erA instanceof LocalVariableDefRow) {
      LocalVariableDefRow lvdrA = (LocalVariableDefRow) erA;
      LocalVariableDefRow lvdrB = (LocalVariableDefRow) erB;
      return lvdrA.getLocalVariable().getName().equals(lvdrB.getLocalVariable().getName());
    } else if (erA instanceof LabelRow) {
      LabelRow lrA = (LabelRow) erA;
      LabelRow lrB = (LabelRow) erB;
      return lrA.getLabel().getId().equals(lrB.getLabel().getId());
    } else if (erA instanceof CodeRow) {
      CodeRow crA = (CodeRow) erA;
      CodeRow crB = (CodeRow) erB;
      Instruction instA = crA.getInstruction();
      Instruction instB = crB.getInstruction();
      boolean opCodesEqual = (instA.getOpcode() == instB.getOpcode());
      if (!opCodesEqual) return false;
           
      return instA.getParameters().getString(crA.getDecompilationContext()).equals(instB.getParameters().getString(crB.getDecompilationContext()));
    } else {
      throw new AssertionError("Invalid object type: " + erA.getClass());
    }
  }
 
  public static void lcs(int[][] C, List<EditorRow> X, List<EditorRow> Y) {
      for (int i = 1; i < X.size()+1; i++) {
          for(int j = 1; j < Y.size()+1; j++) {
            if (rowsAreEqual(X.get(i-1), Y.get(j-1))) {
                  C[i][j] = C[i-1][j-1] + 1;
              } else {
                  C[i][j] = Math.max(C[i][j-1], C[i-1][j]);
              }
          }
      }
  }
 
  public static void bt(int[][] C, List<EditorRow> X, List<EditorRow> Y, int i, int j, List<EditorRow> list) {
      if (i == 0 || j == 0) {
        return;
      } else if (rowsAreEqual(X.get(i-1), Y.get(j-1))) {
        bt(C, X, Y, i-1, j-1, list);
        list.add(X.get(i-1));
      } else {
          if (C[i][j-1] > C[i-1][j]) {
            bt(C, X, Y, i, j-1, list);
          } else {
            bt(C, X, Y, i-1, j, list);
          }
      }
  }

}
TOP

Related Classes of net.sf.rej.gui.compare.ComparePanel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.