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