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
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);
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
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);
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)) {
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
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
688
689
690 public static void main(String[] args) {
691
692
693 new GUI();
694 }
695
696 }