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 java.io.File;
7 import java.io.FileNotFoundException;
8 import java.io.IOException;
9 import java.util.HashMap;
10 import java.util.HashSet;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Set;
15
16 public class CPD {
17
18 private Map source = new HashMap();
19 private CPDListener listener = new CPDNullListener();
20 private Tokens tokens = new Tokens();
21 private int minimumTileSize;
22 private MatchAlgorithm matchAlgorithm;
23 private Language language;
24 private boolean skipDuplicates;
25
26 public CPD(int minimumTileSize, Language language) {
27 this.minimumTileSize = minimumTileSize;
28 this.language = language;
29 }
30
31 public void skipDuplicates() {
32 this.skipDuplicates = true;
33 }
34
35 public void setCpdListener(CPDListener cpdListener) {
36 this.listener = cpdListener;
37 }
38
39 public void go() {
40 TokenEntry.clearImages();
41 matchAlgorithm = new MatchAlgorithm(source, tokens, minimumTileSize, listener);
42 matchAlgorithm.findMatches();
43 }
44
45 public Iterator getMatches() {
46 return matchAlgorithm.matches();
47 }
48
49 public void add(File file) throws IOException {
50 add(1, file);
51 }
52
53 public void addAllInDirectory(String dir) throws IOException {
54 addDirectory(dir, false);
55 }
56
57 public void addRecursively(String dir) throws IOException {
58 addDirectory(dir, true);
59 }
60
61 public void add(List files) throws IOException {
62 for (Iterator i = files.iterator(); i.hasNext();) {
63 add(files.size(), (File) i.next());
64 }
65 }
66
67 private void addDirectory(String dir, boolean recurse) throws IOException {
68 if (!(new File(dir)).exists()) {
69 throw new FileNotFoundException("Couldn't find directory " + dir);
70 }
71 FileFinder finder = new FileFinder();
72
73 add(finder.findFilesFrom(dir, language.getFileFilter(), recurse));
74 }
75
76 private Set current = new HashSet();
77
78 private void add(int fileCount, File file) throws IOException {
79
80 if (skipDuplicates) {
81
82 String signature = file.getName() + '_' + file.length();
83 if (current.contains(signature)) {
84 System.out.println("Skipping " + file.getAbsolutePath() + " since it appears to be a duplicate file and --skip-duplicate-files is set");
85 return;
86 }
87 current.add(signature);
88 }
89
90 if (!file.getCanonicalPath().equals(file.getAbsolutePath())) {
91 System.out.println("Skipping " + file + " since it appears to be a symlink");
92 return;
93 }
94
95 listener.addedFile(fileCount, file);
96 SourceCode sourceCode = new SourceCode(new SourceCode.FileCodeLoader(file));
97 language.getTokenizer().tokenize(sourceCode, tokens);
98 source.put(sourceCode.getFileName(), sourceCode);
99 }
100
101 public static Renderer getRendererFromString(String name) {
102 if (name.equalsIgnoreCase("text") || name.equals("")) {
103 return new SimpleRenderer();
104 }
105 try {
106 return (Renderer) Class.forName(name).newInstance();
107 } catch (Exception e) {
108 System.out.println("Can't find class '" + name + "', defaulting to SimpleRenderer.");
109 }
110 return new SimpleRenderer();
111 }
112
113 private static boolean findBooleanSwitch(String[] args, String name) {
114 for (int i = 0; i < args.length; i++) {
115 if (args[i].equals(name)) {
116 return true;
117 }
118 }
119 return false;
120 }
121
122 private static String findRequiredStringValue(String[] args, String name) {
123 for (int i = 0; i < args.length; i++) {
124 if (args[i].equals(name)) {
125 return args[i + 1];
126 }
127 }
128 System.out.println("No " + name + " value passed in");
129 usage();
130 throw new RuntimeException();
131 }
132
133 private static String findOptionalStringValue(String[] args, String name, String defaultValue) {
134 for (int i = 0; i < args.length; i++) {
135 if (args[i].equals(name)) {
136 return args[i + 1];
137 }
138 }
139 return defaultValue;
140 }
141
142 public static void main(String[] args) {
143 if (args.length == 0) {
144 usage();
145 }
146
147 try {
148 boolean skipDuplicateFiles = findBooleanSwitch(args, "--skip-duplicate-files");
149 String pathToFiles = findRequiredStringValue(args, "--files");
150 String languageString = findOptionalStringValue(args, "--language", "java");
151 String formatString = findOptionalStringValue(args, "--format", "text");
152 int minimumTokens = Integer.parseInt(findRequiredStringValue(args, "--minimum-tokens"));
153 LanguageFactory f = new LanguageFactory();
154 Language language = f.createLanguage(languageString);
155 Renderer renderer = CPD.getRendererFromString(formatString);
156 CPD cpd = new CPD(minimumTokens, language);
157 if (skipDuplicateFiles) {
158 cpd.skipDuplicates();
159 }
160 cpd.addRecursively(pathToFiles);
161 cpd.go();
162 System.out.println(renderer.render(cpd.getMatches()));
163 } catch (Exception e) {
164 e.printStackTrace();
165 }
166 }
167
168 private static void usage() {
169 System.out.println("Usage:");
170 System.out.println(" java net.sourceforge.pmd.cpd.CPD --minimum-tokens xxx --files xxx [--language xxx] [--format (xml|text|csv)] [--skip-duplicate-files] ");
171 System.out.println("i.e: ");
172 System.out.println(" java net.sourceforge.pmd.cpd.CPD --minimum-tokens 100 --files c://jdk14//src//java ");
173 System.out.println("or: ");
174 System.out.println(" java net.sourceforge.pmd.cpd.CPD --minimum-tokens 100 --files /path/to/c/code --language c ");
175 System.out.println("or: ");
176 System.out.println(" java net.sourceforge.pmd.cpd.CPD --minimum-tokens 100 --files /path/to/java/code --format xml");
177 }
178
179 }