View Javadoc

    1   /***
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.cpd;
    5
    6   import javax.swing.*;
    7   import javax.swing.event.ListSelectionEvent;
    8   import javax.swing.event.ListSelectionListener;
    9   import javax.swing.event.TableModelListener;
    10  import javax.swing.table.DefaultTableCellRenderer;
    11  import javax.swing.table.JTableHeader;
    12  import javax.swing.table.TableColumn;
    13  import javax.swing.table.TableColumnModel;
    14  import javax.swing.table.TableModel;
    15  import java.awt.BorderLayout;
    16  import java.awt.Component;
    17  import java.awt.Dimension;
    18  import java.awt.Point;
    19  import java.awt.Toolkit;
    20  import java.awt.datatransfer.StringSelection;
    21  import java.awt.event.ActionEvent;
    22  import java.awt.event.ActionListener;
    23  import java.awt.event.ItemEvent;
    24  import java.awt.event.ItemListener;
    25  import java.awt.event.KeyEvent;
    26  import java.awt.event.MouseAdapter;
    27  import java.awt.event.MouseEvent;
    28  import java.io.File;
    29  import java.io.FileOutputStream;
    30  import java.io.IOException;
    31  import java.io.PrintWriter;
    32  import java.util.ArrayList;
    33  import java.util.Collections;
    34  import java.util.Comparator;
    35  import java.util.HashMap;
    36  import java.util.HashSet;
    37  import java.util.Iterator;
    38  import java.util.List;
    39  import java.util.Map;
    40  import java.util.Properties;
    41  import java.util.Set;
    42
    43  public class GUI implements CPDListener {
    44
    45  	private interface Renderer {
    46  		String render(Iterator items);
    47  	}
    48
    49  	private static final Object[][] rendererSets = new Object[][] {
    50  		{ "Text", 		new Renderer() { public String render(Iterator items) { return new SimpleRenderer().render(items); } } },
    51  		{ "XML", 		new Renderer() { public String render(Iterator items) { return new XMLRenderer().render(items); } } },
    52  		{ "CSV (comma)",new Renderer() { public String render(Iterator items) { return new CSVRenderer(',').render(items); } } },
    53  		{ "CSV (tab)",	new Renderer() { public String render(Iterator items) { return new CSVRenderer('\t').render(items); } } }
    54  		};
    55
    56  	private interface LanguageConfig {
    57  		Language languageFor(LanguageFactory lf, Properties p);
    58  		boolean ignoreLiteralsByDefault();
    59  		String[] extensions();
    60  	};
    61
    62  	private static final Object[][] languageSets = new Object[][] {
    63  		{"Java", 			new LanguageConfig() {
    64  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.JAVA_KEY); }
    65  									public boolean ignoreLiteralsByDefault() { return true; }
    66  									public String[] extensions() { return new String[] {".java", ".class" }; }; } },
    67  		{"JSP", 			new LanguageConfig() {
    68  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.JSP_KEY); }
    69  									public boolean ignoreLiteralsByDefault() { return false; }
    70  									public String[] extensions() { return new String[] {".jsp" }; }; } },
    71  		{"C++", 			new LanguageConfig() {
    72  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.CPP_KEY); }
    73  									public boolean ignoreLiteralsByDefault() { return false; }
    74  									public String[] extensions() { return new String[] {".cpp", ".c" }; }; } },
    75  		{"Ruby",			new LanguageConfig() {
    76  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.RUBY_KEY); }
    77  									public boolean ignoreLiteralsByDefault() { return false; }
    78  									public String[] extensions() { return new String[] {".rb" }; }; } },
    79  		{"by extension...", new LanguageConfig() {
    80  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.BY_EXTENSION, p); }
    81  									public boolean ignoreLiteralsByDefault() { return false; }
    82  									public String[] extensions() { return new String[] {"" }; }; } },
    83  		{"PHP", 			new LanguageConfig() {
    84  									public Language languageFor(LanguageFactory lf, Properties p) { return lf.createLanguage(LanguageFactory.PHP_KEY); }
    85  									public boolean ignoreLiteralsByDefault() { return false; }
    86  									public String[] extensions() { return new String[] {".php" }; };	} },
    87  		};
    88
    89  	private static final int		defaultCPDMinimumLength = 75;
    90  	private static final Map		langConfigsByLabel = new HashMap(languageSets.length);
    91  	private static final KeyStroke	copy = KeyStroke.getKeyStroke(KeyEvent.VK_C,ActionEvent.CTRL_MASK,false);
    92  	private static final KeyStroke	delete = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
    93
    94  	private class ColumnSpec {
    95  		private String label;
    96  		private int alignment;
    97  		private int width;
    98  		private Comparator sorter;
    99
    100 		public ColumnSpec(String aLabel, int anAlignment, int aWidth, Comparator aSorter) {
    101 			label = aLabel;
    102 			alignment = anAlignment;
    103 			width = aWidth;
    104 			sorter = aSorter;
    105 		}
    106 		public String label() { return label; };
    107 		public int alignment() { return alignment; };
    108 		public int width() { return width; };
    109 		public Comparator sorter() { return sorter; };
    110 	}
    111
    112 	private final ColumnSpec[] matchColumns = new ColumnSpec[] {
    113 		new ColumnSpec("Source", 	SwingConstants.LEFT, -1, Match.LabelComparator),
    114 		new ColumnSpec("Matches", 	SwingConstants.RIGHT, 60, Match.MatchesComparator),
    115 		new ColumnSpec("Lines", 	SwingConstants.RIGHT, 45, Match.LinesComparator),
    116 		};
    117
    118 	static {
    119 		for (int i=0; i<languageSets.length; i++) {
    120 			langConfigsByLabel.put(languageSets[i][0], languageSets[i][1]);
    121 		}
    122 	}
    123
    124 	private static LanguageConfig languageConfigFor(String label) {
    125 		return (LanguageConfig)langConfigsByLabel.get(label);
    126 	}
    127
    128     private static class CancelListener implements ActionListener {
    129         public void actionPerformed(ActionEvent e) {
    130             System.exit(0);
    131         }
    132     }
    133
    134     private class GoListener implements ActionListener {
    135         public void actionPerformed(ActionEvent e) {
    136             new Thread(new Runnable() {
    137                 public void run() {
    138                     tokenizingFilesBar.setValue(0);
    139                     tokenizingFilesBar.setString("");
    140                     resultsTextArea.setText("");
    141                     phaseLabel.setText("");
    142                     timeField.setText("");
    143                     go();
    144                 }
    145             }).start();
    146         }
    147     }
    148
    149     private class SaveListener implements ActionListener {
    150
    151     	final Renderer renderer;
    152
    153     	public SaveListener(Renderer theRenderer) {
    154     		renderer = theRenderer;
    155     	}
    156
    157         public void actionPerformed(ActionEvent evt) {
    158             JFileChooser fcSave	= new JFileChooser();
    159             int ret = fcSave.showSaveDialog(GUI.this.frame);
    160             File f = fcSave.getSelectedFile();
    161             if (f == null || ret != JFileChooser.APPROVE_OPTION) return;
    162
    163             if (!f.canWrite()) {
    164                 PrintWriter pw = null;
    165                 try {
    166                     pw = new PrintWriter(new FileOutputStream(f));
    167                     pw.write(renderer.render(matches.iterator()));
    168                     pw.flush();
    169                     JOptionPane.showMessageDialog(frame, "Saved " + matches.size() + " matches");
    170                 } catch (IOException e) {
    171                     error("Couldn't save file" + f.getAbsolutePath(), e);
    172                 } finally {
    173                     if (pw != null) pw.close();
    174                 }
    175             } else {
    176                 error("Could not write to file " + f.getAbsolutePath(), null);
    177             }
    178         }
    179
    180         private void error(String message, Exception e) {
    181             if (e != null) {
    182                 e.printStackTrace();
    183             }
    184             JOptionPane.showMessageDialog(GUI.this.frame, message);
    185         }
    186
    187     }
    188
    189     private class BrowseListener implements ActionListener {
    190         public void actionPerformed(ActionEvent e) {
    191             JFileChooser fc = new JFileChooser(rootDirectoryField.getText());
    192             fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
    193             fc.showDialog(frame, "Select");
    194             if (fc.getSelectedFile() != null) {
    195                 rootDirectoryField.setText(fc.getSelectedFile().getAbsolutePath());
    196             }
    197         }
    198     }
    199
    200 	private class AlignmentRenderer extends DefaultTableCellRenderer {
    201
    202 		private int[] alignments;
    203
    204 		public AlignmentRenderer(int[] theAlignments) {
    205 			alignments = theAlignments;
    206 		};
    207
    208 		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    209 			super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    210
    211 			setHorizontalAlignment(alignments[column]);
    212
    213 			return this;
    214 		}
    215 	}
    216
    217     private JTextField rootDirectoryField	= new JTextField(System.getProperty("user.home"));
    218     private JTextField minimumLengthField	= new JTextField(Integer.toString(defaultCPDMinimumLength));
    219     private JTextField timeField			= new JTextField(6);
    220     private JLabel phaseLabel				= new JLabel();
    221     private JProgressBar tokenizingFilesBar = new JProgressBar();
    222     private JTextArea resultsTextArea		= new JTextArea();
    223     private JCheckBox recurseCheckbox		= new JCheckBox("", true);
    224     private JCheckBox ignoreLiteralsCheckbox = new JCheckBox("", false);
    225     private JComboBox languageBox			= new JComboBox();
    226     private JTextField extensionField		= new JTextField();
    227     private JLabel extensionLabel			= new JLabel("Extension:", SwingConstants.RIGHT);
    228     private JTable resultsTable				= new JTable();
    229     private JButton goButton;
    230     private JButton cancelButton;
    231     private JPanel progressPanel;
    232     private JFrame frame;
    233     private boolean trimLeadingWhitespace;
    234
    235     private List matches = new ArrayList();
    236
    237     private void addSaveOptionsTo(JMenu menu) {
    238
    239         JMenuItem saveItem;
    240
    241         for (int i=0; i<rendererSets.length; i++) {
    242         	saveItem = new JMenuItem("Save as " + rendererSets[i][0]);
    243         	saveItem.addActionListener(new SaveListener((Renderer)rendererSets[i][1]));
    244         	menu.add(saveItem);
    245         }
    246     }
    247
    248     public GUI() {
    249         frame = new JFrame("PMD Duplicate Code Detector");
    250
    251         timeField.setEditable(false);
    252
    253         JMenu fileMenu = new JMenu("File");
    254         fileMenu.setMnemonic('f');
    255
    256         addSaveOptionsTo(fileMenu);
    257
    258         JMenuItem exitItem = new JMenuItem("Exit");
    259         exitItem.setMnemonic('x');
    260         exitItem.addActionListener(new CancelListener());
    261         fileMenu.add(exitItem);
    262         JMenu viewMenu = new JMenu("View");
    263         fileMenu.setMnemonic('v');
    264         JMenuItem trimItem = new JCheckBoxMenuItem("Trim leading whitespace");
    265         trimItem.addItemListener(new ItemListener() {
    266             public void itemStateChanged(ItemEvent e) {
    267                 AbstractButton button = (AbstractButton)e.getItem();
    268                 GUI.this.trimLeadingWhitespace = button.isSelected();
    269             }
    270         });
    271         viewMenu.add(trimItem);
    272         JMenuBar menuBar = new JMenuBar();
    273         menuBar.add(fileMenu);
    274         menuBar.add(viewMenu);
    275         frame.setJMenuBar(menuBar);
    276
    277         // first make all the buttons
    278         JButton browseButton = new JButton("Browse");
    279         browseButton.setMnemonic('b');
    280         browseButton.addActionListener(new BrowseListener());
    281         goButton = new JButton("Go");
    282         goButton.setMnemonic('g');
    283         goButton.addActionListener(new GoListener());
    284         cancelButton = new JButton("Cancel");
    285         cancelButton.addActionListener(new CancelListener());
    286
    287         JPanel settingsPanel = makeSettingsPanel(browseButton, goButton, cancelButton);
    288         progressPanel = makeProgressPanel();
    289         JPanel resultsPanel = makeResultsPanel();
    290
    291         adjustLanguageControlsFor((LanguageConfig)languageSets[0][1]);
    292
    293         frame.getContentPane().setLayout(new BorderLayout());
    294         JPanel topPanel = new JPanel();
    295         topPanel.setLayout(new BorderLayout());
    296         topPanel.add(settingsPanel, BorderLayout.NORTH);
    297         topPanel.add(progressPanel, BorderLayout.CENTER);
    298         setProgressControls(false);	// not running now
    299         frame.getContentPane().add(topPanel, BorderLayout.NORTH);
    300         frame.getContentPane().add(resultsPanel, BorderLayout.CENTER);
    301         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    302         frame.pack();
    303         frame.setVisible(true);
    304     }
    305
    306     private void adjustLanguageControlsFor(LanguageConfig current) {
    307     	 ignoreLiteralsCheckbox.setEnabled(current.ignoreLiteralsByDefault());
    308          extensionField.setText(current.extensions()[0]);
    309          boolean enableExtension = current.extensions()[0].length() == 0;
    310          extensionField.setEnabled(enableExtension);
    311          extensionLabel.setEnabled(enableExtension);
    312     }
    313
    314     private JPanel makeSettingsPanel(JButton browseButton, JButton goButton, JButton cxButton) {
    315         JPanel settingsPanel = new JPanel();
    316         GridBagHelper helper = new GridBagHelper(settingsPanel, new double[]{0.2, 0.7, 0.1, 0.1});
    317         helper.addLabel("Root source directory:");
    318         helper.add(rootDirectoryField);
    319         helper.add(browseButton, 2);
    320         helper.nextRow();
    321         helper.addLabel("Report duplicate chunks larger than:");
    322         minimumLengthField.setColumns(4);
    323         helper.add(minimumLengthField);
    324         helper.addLabel("Language:");
    325         for (int i=0; i<languageSets.length; i++) {
    326         	languageBox.addItem(languageSets[i][0]);
    327         }
    328         languageBox.addActionListener(new ActionListener() {
    329             public void actionPerformed(ActionEvent e) {
    330             	adjustLanguageControlsFor(
    331             			languageConfigFor((String)languageBox.getSelectedItem())
    332             			);
    333             }
    334         });
    335         helper.add(languageBox);
    336         helper.nextRow();
    337         helper.addLabel("Also scan subdirectories?");
    338         helper.add(recurseCheckbox);
    339
    340         helper.add(extensionLabel);
    341         helper.add(extensionField);
    342
    343         helper.nextRow();
    344         helper.addLabel("Ignore literals and identifiers?");
    345         helper.add(ignoreLiteralsCheckbox);
    346         helper.add(goButton);
    347         helper.add(cxButton);
    348         helper.nextRow();
    349 //        settingsPanel.setBorder(BorderFactory.createTitledBorder("Settings"));
    350         return settingsPanel;
    351     }
    352
    353     private JPanel makeProgressPanel() {
    354         JPanel progressPanel = new JPanel();
    355         final double[] weights = {0.0, 0.8, 0.4, 0.2};
    356         GridBagHelper helper = new GridBagHelper(progressPanel, weights);
    357         helper.addLabel("Tokenizing files:");
    358         helper.add(tokenizingFilesBar, 3);
    359         helper.nextRow();
    360         helper.addLabel("Phase:");
    361         helper.add(phaseLabel);
    362         helper.addLabel("Time elapsed:");
    363         helper.add(timeField);
    364         helper.nextRow();
    365         progressPanel.setBorder(BorderFactory.createTitledBorder("Progress"));
    366         return progressPanel;
    367     }
    368
    369     private JPanel makeResultsPanel() {
    370         JPanel resultsPanel = new JPanel();
    371         resultsPanel.setLayout(new BorderLayout());
    372         JScrollPane areaScrollPane = new JScrollPane(resultsTextArea);
    373         resultsTextArea.setEditable(false);
    374         areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
    375         areaScrollPane.setPreferredSize(new Dimension(600, 300));
    376
    377         resultsPanel.add(makeMatchList(), BorderLayout.WEST);
    378         resultsPanel.add(areaScrollPane, BorderLayout.CENTER);
    379         return resultsPanel;
    380     }
    381
    382     private void populateResultArea() {
    383     	int[] selectionIndices = resultsTable.getSelectedRows();
    384     	TableModel model = resultsTable.getModel();
    385     	List selections = new ArrayList(selectionIndices.length);
    386     	for (int i=0; i<selectionIndices.length; i++) {
    387     		selections.add(model.getValueAt(selectionIndices[i], 99));
    388     	}
    389     	String report = new SimpleRenderer(trimLeadingWhitespace).render(selections.iterator());
    390     	resultsTextArea.setText(report);
    391     	resultsTextArea.setCaretPosition(0);	// move to the top
    392     }
    393
    394     private void copyMatchListSelectionsToClipboard() {
    395
    396     	int[] selectionIndices = resultsTable.getSelectedRows();
    397     	int colCount = resultsTable.getColumnCount();
    398
    399     	StringBuffer sb = new StringBuffer();
    400
    401     	for (int r=0; r<selectionIndices.length; r++) {
    402 			if (r > 0) sb.append('\n');
    403 			sb.append(resultsTable.getValueAt(selectionIndices[r], 0));
    404     		for (int c=1; c<colCount; c++) {
    405     			sb.append('\t');
    406     			sb.append(resultsTable.getValueAt(selectionIndices[r], c));
    407     		}
    408     	}
    409
    410     	StringSelection ss = new StringSelection(sb.toString());
    411         Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
    412     }
    413
    414     private void deleteMatchlistSelections() {
    415
    416     	int[] selectionIndices = resultsTable.getSelectedRows();
    417
    418     	for (int i=selectionIndices.length-1; i >=0; i--) {
    419     		matches.remove(selectionIndices[i]);
    420     	}
    421
    422     	resultsTable.getSelectionModel().clearSelection();
    423     	resultsTable.addNotify();
    424     }
    425
    426     private JComponent makeMatchList() {
    427
    428     	resultsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
    429 			public void valueChanged(ListSelectionEvent e) {
    430 				populateResultArea();
    431 			}});
    432
    433     	resultsTable.registerKeyboardAction(new ActionListener() {
    434 			public void actionPerformed(ActionEvent e) { copyMatchListSelectionsToClipboard(); }
    435     		},"Copy", copy, JComponent.WHEN_FOCUSED);
    436
    437     	resultsTable.registerKeyboardAction(new ActionListener() {
    438 			public void actionPerformed(ActionEvent e) { deleteMatchlistSelections(); }
    439     		},"Del", delete, JComponent.WHEN_FOCUSED);
    440
    441     	int[] alignments = new int[matchColumns.length];
    442     	for (int i=0; i<alignments.length; i++) alignments[i] = matchColumns[i].alignment();
    443
    444     	resultsTable.setDefaultRenderer(Object.class, new AlignmentRenderer(alignments));
    445
    446     	final JTableHeader header = resultsTable.getTableHeader();
    447     	header.addMouseListener( new MouseAdapter() {
    448 			public void mouseClicked(MouseEvent e) {
    449 				sortOnColumn(header.columnAtPoint(new Point(e.getX(), e.getY())));
    450 				}
    451 			});
    452
    453         return new JScrollPane(resultsTable);
    454     }
    455
    456     private boolean isLegalPath(String path, LanguageConfig config) {
    457     	String[] extensions = config.extensions();
    458     	for (int i=0; i<extensions.length; i++) {
    459     		if (path.endsWith(extensions[i]) && extensions[i].length() > 0) return true;
    460     	}
    461     	return false;
    462     }
    463
    464     private String setLabelFor(Match match) {
    465
    466     	Set sourceIDs = new HashSet(match.getMarkCount());
    467     	for (Iterator occurrences = match.iterator(); occurrences.hasNext();) {
    468              sourceIDs.add( ((TokenEntry) occurrences.next()).getTokenSrcID());
    469           }
    470     	String label;
    471
    472     	if (sourceIDs.size() == 1) {
    473     		String sourceId = (String)sourceIDs.iterator().next();
    474     		int separatorPos = sourceId.lastIndexOf(File.separatorChar);
    475     		label = "..." + sourceId.substring(separatorPos);
    476     		} else {
    477     	    	label = "(" + sourceIDs.size() + " separate files)";
    478     		}
    479
    480     	match.setLabel(label);
    481     	return label;
    482     }
    483
    484     private void setProgressControls(boolean isRunning) {
    485         progressPanel.setVisible(isRunning);
    486         goButton.setEnabled(!isRunning);
    487         cancelButton.setEnabled(isRunning);
    488     }
    489
    490     private void go() {
    491     	String dirPath = rootDirectoryField.getText();
    492         try {
    493             if (!(new File(dirPath)).exists()) {
    494                 JOptionPane.showMessageDialog(frame,
    495                         "Can't read from that root source directory",
    496                         "Error", JOptionPane.ERROR_MESSAGE);
    497                 return;
    498             }
    499
    500             setProgressControls(true);
    501
    502             Properties p = new Properties();
    503             p.setProperty(JavaTokenizer.IGNORE_LITERALS, String.valueOf(ignoreLiteralsCheckbox.isSelected()));
    504             p.setProperty(LanguageFactory.EXTENSION, extensionField.getText());
    505             LanguageConfig conf = languageConfigFor((String)languageBox.getSelectedItem());
    506             Language language = conf.languageFor(new LanguageFactory(), p);
    507             CPD cpd = new CPD(Integer.parseInt(minimumLengthField.getText()), language);
    508             cpd.setCpdListener(this);
    509             tokenizingFilesBar.setMinimum(0);
    510             phaseLabel.setText("");
    511             if (isLegalPath(dirPath, conf)) {	// should use the language file filter instead?
    512             	cpd.add(new File(dirPath));
    513             } else {
    514                 if (recurseCheckbox.isSelected()) {
    515                     cpd.addRecursively(dirPath);
    516                 } else {
    517                     cpd.addAllInDirectory(dirPath);
    518                 }
    519             }
    520             final long start = System.currentTimeMillis();
    521             Timer t = new Timer(1000, new ActionListener() {
    522                 public void actionPerformed(ActionEvent e) {
    523                     long now = System.currentTimeMillis();
    524                     long elapsedMillis = now - start;
    525                     long elapsedSeconds = elapsedMillis / 1000;
    526                     long minutes = (long) Math.floor(elapsedSeconds / 60);
    527                     long seconds = elapsedSeconds - (minutes * 60);
    528                     timeField.setText(""
    529                             + munge(String.valueOf(minutes))
    530                             + ':'
    531                             + munge(String.valueOf(seconds)));
    532                 }
    533
    534                 private String munge(String in) {
    535                     if (in.length() < 2) {
    536                         in = "0" + in;
    537                     }
    538                     return in;
    539                 }
    540             });
    541             t.start();
    542             cpd.go();
    543             t.stop();
    544
    545         	matches = new ArrayList();
    546         	Match match;
    547         	for (Iterator i = cpd.getMatches(); i.hasNext();) {
    548         		match = (Match)i.next();
    549         		setLabelFor(match);
    550         		matches.add(match);
    551         	}
    552
    553             String report = new SimpleRenderer().render(cpd.getMatches());
    554             if (report.length() == 0) {
    555                 JOptionPane.showMessageDialog(frame,
    556                         "Done; couldn't find any duplicates longer than " + minimumLengthField.getText() + " tokens");
    557             } else {
    558                 resultsTextArea.setText(report);
    559                 setListDataFrom(cpd.getMatches());
    560
    561             }
    562         } catch (Throwable t) {
    563             t.printStackTrace();
    564             JOptionPane.showMessageDialog(frame, "Halted due to " + t.getClass().getName() + "; " + t.getMessage());
    565         }
    566         setProgressControls(false);
    567     }
    568
    569     private interface SortingTableModel extends TableModel {
    570     	public int sortColumn();
    571     	public void sortColumn(int column);
    572     	public boolean sortDescending();
    573     	public void sortDescending(boolean flag);
    574     	public void sort(Comparator comparator);
    575     }
    576
    577     private TableModel tableModelFrom(final List items) {
    578
    579     	TableModel model = new SortingTableModel() {
    580
    581     		private int sortColumn;
    582     		private boolean sortDescending;
    583
    584     		 public Object getValueAt(int rowIndex, int columnIndex) {
    585     			Match match = (Match) items.get(rowIndex);
    586     			switch (columnIndex) {
    587     				case 0: return match.getLabel();
    588     				case 2: return Integer.toString(match.getLineCount());
    589     				case 1: return match.getMarkCount() > 2 ? Integer.toString(match.getMarkCount()) : "";
    590     				case 99: return match;
    591     				}
    592     			return "";
    593     		 	}
    594 			public int getColumnCount() { return matchColumns.length;	}
    595 			public int getRowCount() {	return items.size(); }
    596 			public boolean isCellEditable(int rowIndex, int columnIndex) {	return false;	}
    597 			public Class getColumnClass(int columnIndex) { return Object.class;	}
    598 			public void setValueAt(Object aValue, int rowIndex, int columnIndex) {	}
    599 			public String getColumnName(int i) {	return matchColumns[i].label();	}
    600 			public void addTableModelListener(TableModelListener l) { }
    601 			public void removeTableModelListener(TableModelListener l) { }
    602 			public int sortColumn() { return sortColumn; };
    603 			public void sortColumn(int column) { sortColumn = column; };
    604 			public boolean sortDescending() { return sortDescending; };
    605 			public void sortDescending(boolean flag) { sortDescending = flag; };
    606 			public void sort(Comparator comparator) {
    607 				Collections.sort(items, comparator);
    608 				if (sortDescending) Collections.reverse(items);
    609 				}
    610     		};
    611
    612     	return model;
    613     }
    614
    615     private void sortOnColumn(int columnIndex) {
    616     	Comparator comparator = matchColumns[columnIndex].sorter();
    617     	SortingTableModel model = (SortingTableModel)resultsTable.getModel();
    618     	if (model.sortColumn() == columnIndex) {
    619     		model.sortDescending(!model.sortDescending());
    620     	}
    621     	model.sortColumn(columnIndex);
    622     	model.sort(comparator);
    623
    624     	resultsTable.getSelectionModel().clearSelection();
    625     	resultsTable.repaint();
    626     }
    627
    628     private void setListDataFrom(Iterator iter) {
    629
    630     	resultsTable.setModel(tableModelFrom(matches));
    631
    632     	TableColumnModel colModel = resultsTable.getColumnModel();
    633     	TableColumn column;
    634     	int width;
    635
    636     	for (int i=0; i<matchColumns.length; i++) {
    637     		if (matchColumns[i].width() > 0) {
    638     			column = colModel.getColumn(i);
    639     			width = matchColumns[i].width();
    640     			column.setPreferredWidth(width);
    641     			column.setMinWidth(width);
    642     			column.setMaxWidth(width);
    643     		}
    644     	}
    645     }
    646
    647     // CPDListener
    648     public void phaseUpdate(int phase) {
    649         phaseLabel.setText(getPhaseText(phase));
    650     }
    651
    652     public String getPhaseText(int phase) {
    653         switch (phase) {
    654             case CPDListener.INIT:
    655                 return "Initializing";
    656             case CPDListener.HASH:
    657                 return "Hashing";
    658             case CPDListener.MATCH:
    659                 return "Matching";
    660             case CPDListener.GROUPING:
    661                 return "Grouping";
    662             case CPDListener.DONE:
    663                 return "Done";
    664             default :
    665                 return "Unknown";
    666         }
    667     }
    668
    669     public void addedFile(int fileCount, File file) {
    670         tokenizingFilesBar.setMaximum(fileCount);
    671         tokenizingFilesBar.setValue(tokenizingFilesBar.getValue() + 1);
    672     }
    673     // CPDListener
    674
    675
    676     public static void main(String[] args) {
    677     	//this should prevent the disk not found popup
    678         // System.setSecurityManager(null);
    679         new GUI();
    680     }
    681
    682 }