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 java.awt.BorderLayout;
8 import java.awt.Dimension;
9 import java.awt.event.ActionEvent;
10 import java.awt.event.ActionListener;
11 import java.io.File;
12 import java.io.FileOutputStream;
13 import java.io.IOException;
14 import java.io.PrintWriter;
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Properties;
19
20 public class GUI implements CPDListener {
21
22 private static class CancelListener implements ActionListener {
23 public void actionPerformed(ActionEvent e) {
24 System.exit(0);
25 }
26 }
27
28 private class GoListener implements ActionListener {
29 public void actionPerformed(ActionEvent e) {
30 new Thread(new Runnable() {
31 public void run() {
32 tokenizingFilesBar.setValue(0);
33 tokenizingFilesBar.setString("");
34 resultsTextArea.setText("");
35 phaseLabel.setText("");
36 timeField.setText("");
37 go();
38 }
39 }).start();
40 }
41 }
42
43 private class SaveListener implements ActionListener {
44 public void actionPerformed(ActionEvent evt) {
45 int ret = fcSave.showSaveDialog(GUI.this.frame);
46 File f = fcSave.getSelectedFile();
47 if (f == null || ret != JFileChooser.APPROVE_OPTION) {
48 return;
49 }
50 if (!f.canWrite()) {
51 PrintWriter pw = null;
52 try {
53 pw = new PrintWriter(new FileOutputStream(f));
54 if (evt.getActionCommand().equals(".txt")) {
55 pw.write(new SimpleRenderer().render(matches.iterator()));
56 } else if (evt.getActionCommand().equals(".xml")) {
57 pw.write(new XMLRenderer().render(matches.iterator()));
58 } else if (evt.getActionCommand().equals(".csv")) {
59 pw.write(new CSVRenderer().render(matches.iterator()));
60 }
61 pw.flush();
62 JOptionPane.showMessageDialog(frame, "File saved");
63 } catch (IOException e) {
64 error("Couldn't save file" + f.getAbsolutePath(), e);
65 } finally {
66 if (pw != null) {
67 pw.close();
68 }
69 }
70 } else {
71 error("Could not write to file " + f.getAbsolutePath(), null);
72 }
73 }
74
75 private void error(String message, Exception e) {
76 if (e != null) {
77 e.printStackTrace();
78 }
79 JOptionPane.showMessageDialog(GUI.this.frame, message);
80 }
81
82 }
83
84 private class BrowseListener implements ActionListener {
85 public void actionPerformed(ActionEvent e) {
86 JFileChooser fc = new JFileChooser(rootDirectoryField.getText());
87 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
88 fc.showDialog(frame, "Select");
89 if (fc.getSelectedFile() != null) {
90 rootDirectoryField.setText(fc.getSelectedFile().getAbsolutePath());
91 }
92 }
93 }
94
95 private JTextField rootDirectoryField = new JTextField(System.getProperty("user.home"));
96 private JTextField minimumLengthField = new JTextField("75");
97 private JTextField timeField = new JTextField(6);
98 private JLabel phaseLabel = new JLabel();
99 private JProgressBar tokenizingFilesBar = new JProgressBar();
100 private JTextArea resultsTextArea = new JTextArea();
101 private JCheckBox recurseCheckbox = new JCheckBox("", true);
102 private JCheckBox ignoreLiteralsCheckbox = new JCheckBox("", false);
103 private JComboBox languageBox = new JComboBox();
104 private JTextField extensionField = new JTextField(".java");
105 private JFileChooser fcSave = new JFileChooser();
106
107 private JFrame frame;
108
109 private List matches = new ArrayList();
110
111 public GUI() {
112 frame = new JFrame("PMD Duplicate Code Detector");
113
114 timeField.setEditable(false);
115
116 JMenu fileMenu = new JMenu("File");
117 fileMenu.setMnemonic('f');
118 JMenuItem saveItem = new JMenuItem("Save Text");
119 saveItem.setMnemonic('s');
120 saveItem.addActionListener(new SaveListener());
121 fileMenu.add(saveItem);
122 saveItem.setActionCommand(".txt");
123 saveItem = new JMenuItem("Save XML");
124 saveItem.addActionListener(new SaveListener());
125 fileMenu.add(saveItem);
126 saveItem.setActionCommand(".xml");
127 saveItem = new JMenuItem("Save CSV");
128 saveItem.setActionCommand(".csv");
129 saveItem.addActionListener(new SaveListener());
130 fileMenu.add(saveItem);
131 JMenuItem exitItem = new JMenuItem("Exit");
132 exitItem.setMnemonic('x');
133 exitItem.addActionListener(new CancelListener());
134 fileMenu.add(exitItem);
135 JMenuBar menuBar = new JMenuBar();
136 menuBar.add(fileMenu);
137 frame.setJMenuBar(menuBar);
138
139
140 JButton browseButton = new JButton("Browse");
141 browseButton.setMnemonic('b');
142 browseButton.addActionListener(new BrowseListener());
143 JButton goButton = new JButton("Go");
144 goButton.setMnemonic('g');
145 goButton.addActionListener(new GoListener());
146 JButton cxButton = new JButton("Cancel");
147 cxButton.addActionListener(new CancelListener());
148
149 JPanel settingsPanel = makeSettingsPanel(browseButton, goButton, cxButton);
150 JPanel progressPanel = makeProgressPanel();
151 JPanel resultsPanel = makeResultsPanel();
152
153 frame.getContentPane().setLayout(new BorderLayout());
154 JPanel topPanel = new JPanel();
155 topPanel.setLayout(new BorderLayout());
156 topPanel.add(settingsPanel, BorderLayout.NORTH);
157 topPanel.add(progressPanel, BorderLayout.CENTER);
158 frame.getContentPane().add(topPanel, BorderLayout.NORTH);
159 frame.getContentPane().add(resultsPanel, BorderLayout.CENTER);
160 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
161 frame.pack();
162 frame.show();
163 }
164
165 private JPanel makeSettingsPanel(JButton browseButton, JButton goButton, JButton cxButton) {
166 JPanel settingsPanel = new JPanel();
167 GridBagHelper helper = new GridBagHelper(settingsPanel, new double[]{0.2, 0.7, 0.1, 0.1});
168 helper.addLabel("Root source directory:");
169 helper.add(rootDirectoryField);
170 helper.add(browseButton, 2);
171 helper.nextRow();
172 helper.addLabel("Report duplicate chunks larger than:");
173 minimumLengthField.setColumns(4);
174 helper.add(minimumLengthField);
175 helper.addLabel("Language:");
176 languageBox.addItem("Java");
177 languageBox.addItem("C++");
178 languageBox.addItem("PHP");
179 languageBox.addItem("Ruby");
180 languageBox.addItem("by extension...");
181 languageBox.addActionListener(new ActionListener() {
182 public void actionPerformed(ActionEvent e) {
183 ignoreLiteralsCheckbox.setEnabled(languageBox.getSelectedItem().equals("Java"));
184 extensionField.setEnabled(languageBox.getSelectedItem().equals("by extension..."));
185 }
186 });
187 helper.add(languageBox);
188 helper.nextRow();
189 helper.addLabel("Also scan subdirectories?");
190 helper.add(recurseCheckbox);
191
192 helper.addLabel("Extension:");
193 helper.add(extensionField);
194 extensionField.setEnabled(false);
195
196 helper.nextRow();
197 helper.addLabel("Ignore literals and identifiers?");
198 helper.add(ignoreLiteralsCheckbox);
199 helper.add(goButton);
200 helper.add(cxButton);
201 helper.nextRow();
202 settingsPanel.setBorder(BorderFactory.createTitledBorder("Settings"));
203 return settingsPanel;
204 }
205
206 private JPanel makeProgressPanel() {
207 JPanel progressPanel = new JPanel();
208 final double[] weights = {0.0, 0.8, 0.4, 0.2};
209 GridBagHelper helper = new GridBagHelper(progressPanel, weights);
210 helper.addLabel("Tokenizing files:");
211 helper.add(tokenizingFilesBar, 3);
212 helper.nextRow();
213 helper.addLabel("Phase:");
214 helper.add(phaseLabel);
215 helper.addLabel("Time elapsed:");
216 helper.add(timeField);
217 helper.nextRow();
218 progressPanel.setBorder(BorderFactory.createTitledBorder("Progress"));
219 return progressPanel;
220 }
221
222 private JPanel makeResultsPanel() {
223 JPanel resultsPanel = new JPanel();
224 resultsPanel.setLayout(new BorderLayout());
225 JScrollPane areaScrollPane = new JScrollPane(resultsTextArea);
226 areaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
227 areaScrollPane.setPreferredSize(new Dimension(600, 300));
228 resultsPanel.add(areaScrollPane, BorderLayout.CENTER);
229 resultsPanel.setBorder(BorderFactory.createTitledBorder("Results"));
230 return resultsPanel;
231 }
232
233 private void go() {
234 try {
235 if (!(new File(rootDirectoryField.getText())).exists()) {
236 JOptionPane.showMessageDialog(frame,
237 "Can't read from that root source directory",
238 "Error",
239 JOptionPane.ERROR_MESSAGE);
240 return;
241 }
242
243 Language language = null;
244 LanguageFactory lf = new LanguageFactory();
245 Properties p = new Properties();
246 p.setProperty(JavaTokenizer.IGNORE_LITERALS, String.valueOf(ignoreLiteralsCheckbox.isSelected()));
247 p.setProperty(LanguageFactory.EXTENSION, extensionField.getText());
248 if (languageBox.getSelectedItem().equals("Java")) {
249 language = lf.createLanguage(LanguageFactory.JAVA_KEY, p);
250 } else if (languageBox.getSelectedItem().equals("C++")) {
251 language = lf.createLanguage(LanguageFactory.CPP_KEY);
252 } else if (languageBox.getSelectedItem().equals("Ruby")) {
253 language = lf.createLanguage(LanguageFactory.RUBY_KEY);
254 } else if (languageBox.getSelectedItem().equals("by extension...")) {
255 language = lf.createLanguage(LanguageFactory.BY_EXTENSION, p);
256 } else if (languageBox.getSelectedItem().equals("PHP")) {
257 language = lf.createLanguage(LanguageFactory.PHP_KEY);
258 }
259 CPD cpd = new CPD(Integer.parseInt(minimumLengthField.getText()), language);
260 cpd.setCpdListener(this);
261 tokenizingFilesBar.setMinimum(0);
262 phaseLabel.setText("");
263 if (rootDirectoryField.getText().endsWith(".class")
264 || rootDirectoryField.getText().endsWith(".php")
265 || rootDirectoryField.getText().endsWith(".rb")
266 || rootDirectoryField.getText().endsWith(".java")
267 || rootDirectoryField.getText().endsWith(".cpp")
268 || rootDirectoryField.getText().endsWith(".c")) {
269 cpd.add(new File(rootDirectoryField.getText()));
270 } else {
271 if (recurseCheckbox.isSelected()) {
272 cpd.addRecursively(rootDirectoryField.getText());
273 } else {
274 cpd.addAllInDirectory(rootDirectoryField.getText());
275 }
276 }
277 final long start = System.currentTimeMillis();
278 Timer t = new Timer(1000, new ActionListener() {
279 public void actionPerformed(ActionEvent e) {
280 long now = System.currentTimeMillis();
281 long elapsedMillis = now - start;
282 long elapsedSeconds = elapsedMillis / 1000;
283 long minutes = (long) Math.floor(elapsedSeconds / 60);
284 long seconds = elapsedSeconds - (minutes * 60);
285 timeField.setText(""
286 + munge(String.valueOf(minutes))
287 + ":"
288 + munge(String.valueOf(seconds)));
289 }
290
291 private String munge(String in) {
292 if (in.length() < 2) {
293 in = "0" + in;
294 }
295 return in;
296 }
297
298 });
299 t.start();
300 cpd.go();
301 t.stop();
302 matches = new ArrayList();
303 for (Iterator i = cpd.getMatches(); i.hasNext();) {
304 Match m = (Match) i.next();
305 matches.add(m);
306 }
307
308 String report = new SimpleRenderer().render(cpd.getMatches());
309 if (report.length() == 0) {
310 JOptionPane.showMessageDialog(frame,
311 "Done; couldn't find any duplicates longer than " + minimumLengthField.getText() + " tokens");
312 } else {
313 resultsTextArea.setText(report);
314 }
315 } catch (Throwable t) {
316 t.printStackTrace();
317 JOptionPane.showMessageDialog(frame, "Halted due to " + t.getClass().getName() + "; " + t.getMessage());
318 }
319 }
320
321
322 public void phaseUpdate(int phase) {
323 phaseLabel.setText(getPhaseText(phase));
324 }
325
326 public String getPhaseText(int phase) {
327 switch (phase) {
328 case CPDListener.INIT:
329 return "Initializing";
330 case CPDListener.HASH:
331 return "Hashing";
332 case CPDListener.MATCH:
333 return "Matching";
334 case CPDListener.GROUPING:
335 return "Grouping";
336 case CPDListener.DONE:
337 return "Done";
338 default :
339 return "Unknown";
340 }
341 }
342
343 public void addedFile(int fileCount, File file) {
344 tokenizingFilesBar.setMaximum(fileCount);
345 tokenizingFilesBar.setValue(tokenizingFilesBar.getValue() + 1);
346 }
347
348
349 public static void main(String[] args) {
350
351 System.setSecurityManager(null);
352 new GUI();
353 }
354
355 }