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