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