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