1
2
3
4 package net.sourceforge.pmd.cpd;
5
6 import java.beans.IntrospectionException;
7 import java.beans.PropertyDescriptor;
8 import java.io.File;
9 import java.io.FilenameFilter;
10 import java.io.Reader;
11 import java.lang.reflect.InvocationTargetException;
12 import java.lang.reflect.Method;
13 import java.util.Arrays;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Locale;
18 import java.util.Map;
19 import java.util.Properties;
20 import java.util.Set;
21
22 import net.sourceforge.pmd.AbstractConfiguration;
23 import net.sourceforge.pmd.util.FileFinder;
24 import net.sourceforge.pmd.util.FileUtil;
25
26 import com.beust.jcommander.IStringConverter;
27 import com.beust.jcommander.Parameter;
28 import com.beust.jcommander.converters.FileConverter;
29
30
31
32
33
34
35 public class CPDConfiguration extends AbstractConfiguration {
36
37 public final static String DEFAULT_LANGUAGE = "java";
38
39 public final static String DEFAULT_RENDERER = "text";
40
41 @Parameter(names = "--language", description = "Sources code language. Default value is " + DEFAULT_LANGUAGE, required = false, converter = LanguageConverter.class)
42 private Language language;
43
44 @Parameter(names = "--minimum-tokens", description = "The minimum token length which should be reported as a duplicate.", required = true)
45 private int minimumTileSize;
46
47 @Parameter(names = "--skip-duplicate-files", description = "Ignore multiple copies of files of the same name and length in comparison", required = false)
48 private boolean skipDuplicates;
49
50 @Parameter(names = "--format", description = "Report format. Default value is " + DEFAULT_RENDERER, required = false)
51 private String rendererName;
52
53
54
55
56
57 private Renderer renderer;
58
59 private String encoding;
60
61 @Parameter(names = "--ignore-literals", description = "Ignore number values and string contents when comparing text", required = false)
62 private boolean ignoreLiterals;
63
64 @Parameter(names = "--ignore-identifiers", description = "Ignore constant and variable names when comparing text", required = false)
65 private boolean ignoreIdentifiers;
66
67 @Parameter(names = "--ignore-annotations", description = "Ignore language annotations when comparing text", required = false)
68 private boolean ignoreAnnotations;
69
70 @Parameter(names = "--skip-lexical-errors", description = "Skip files which can't be tokenized due to invalid characters instead of aborting CPD", required = false)
71 private boolean skipLexicalErrors = false;
72
73 @Parameter(names = "--no-skip-blocks", description = "Do not skip code blocks marked with --skip-blocks-pattern (e.g. #if 0 until #endif)", required = false)
74 private boolean noSkipBlocks = false;
75
76 @Parameter(names = "--skip-blocks-pattern", description = "Pattern to find the blocks to skip. Start and End pattern separated by |. "
77 + "Default is \"" + Tokenizer.DEFAULT_SKIP_BLOCKS_PATTERN + "\".", required = false)
78 private String skipBlocksPattern = Tokenizer.DEFAULT_SKIP_BLOCKS_PATTERN;
79
80 @Parameter(names = "--files", variableArity = true, description = "List of files and directories to process", required = false, converter = FileConverter.class)
81 private List<File> files;
82
83 @Parameter(names = "--exclude", variableArity = true, description = "Files to be excluded from CPD check", required = false, converter = FileConverter.class)
84 private List<File> excludes;
85
86 @Parameter(names = "--non-recursive", description = "Don't scan subdirectiories", required = false)
87 private boolean nonRecursive;
88
89 @Parameter(names = "--uri", description = "URI to process", required = false)
90 private String uri;
91
92 @Parameter(names = { "--help", "-h" }, description = "Print help text", required = false, help = true)
93 private boolean help;
94
95
96 public static class LanguageConverter implements IStringConverter<Language> {
97
98 public Language convert(String languageString) {
99 if (languageString == null || "".equals(languageString)) {
100 languageString = DEFAULT_LANGUAGE;
101 }
102 return LanguageFactory.createLanguage(languageString);
103 }
104 }
105
106 public CPDConfiguration() {
107 }
108
109 @Deprecated
110 public CPDConfiguration(int minimumTileSize, Language language, String encoding) {
111 setMinimumTileSize(minimumTileSize);
112 setLanguage(language);
113 setEncoding(encoding);
114 }
115
116 @Parameter(names = "--encoding", description = "Character encoding to use when processing files", required = false)
117 public void setEncoding(String encoding) {
118 this.encoding = encoding;
119 setSourceEncoding(encoding);
120 }
121
122 public SourceCode sourceCodeFor(File file) {
123 return new SourceCode(new SourceCode.FileCodeLoader(file, getSourceEncoding()));
124 }
125
126 public SourceCode sourceCodeFor(Reader reader, String sourceCodeName) {
127 return new SourceCode(new SourceCode.ReaderCodeLoader(reader, sourceCodeName));
128 }
129
130 public void postContruct() {
131 if (this.getLanguage() == null) {
132 this.setLanguage(CPDConfiguration.getLanguageFromString(DEFAULT_LANGUAGE));
133 }
134 if (this.getRendererName() == null) {
135 this.setRendererName(DEFAULT_RENDERER);
136 }
137 if (this.getRenderer() == null) {
138 this.setRenderer(getRendererFromString(getRendererName(), this.getEncoding()));
139 }
140 }
141
142
143
144
145
146
147
148
149 @Deprecated
150 public static Renderer getRendererFromString(String name) {
151 return getRendererFromString(name, System.getProperty("file.encoding"));
152 }
153
154 private static final Map<String, Class<? extends Renderer>> RENDERERS = new HashMap<String, Class<? extends Renderer>>();
155 static {
156 RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class);
157 RENDERERS.put("xml", XMLRenderer.class);
158 RENDERERS.put("csv", CSVRenderer.class);
159 RENDERERS.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class);
160 RENDERERS.put("vs", VSRenderer.class);
161 }
162
163 public static Renderer getRendererFromString(String name, String encoding) {
164 String clazzname = name;
165 if (clazzname == null || "".equals(clazzname)) {
166 clazzname = DEFAULT_RENDERER;
167 }
168 Class<? extends Renderer> clazz = RENDERERS.get(clazzname.toLowerCase(Locale.ROOT));
169 if (clazz == null) {
170 try {
171 clazz = Class.forName(clazzname).asSubclass(Renderer.class);
172 } catch (ClassNotFoundException e) {
173 System.err.println("Can't find class '" + name + "', defaulting to SimpleRenderer.");
174 clazz = SimpleRenderer.class;
175 }
176 }
177 try {
178 Renderer renderer = clazz.getDeclaredConstructor().newInstance();
179 setRendererEncoding(renderer, encoding);
180 return renderer;
181 } catch (Exception e) {
182 System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e);
183 return new SimpleRenderer();
184 }
185 }
186
187 private static void setRendererEncoding(Renderer renderer, String encoding)
188 throws IllegalAccessException, InvocationTargetException {
189 try {
190 PropertyDescriptor encodingProperty = new PropertyDescriptor("encoding", renderer.getClass());
191 Method method = encodingProperty.getWriteMethod();
192 if (method != null) {
193 method.invoke(renderer, encoding);
194 }
195 } catch (IntrospectionException e) {
196
197 }
198 }
199
200 public static String[] getRenderers() {
201 String[] result = RENDERERS.keySet().toArray(new String[RENDERERS.size()]);
202 Arrays.sort(result);
203 return result;
204 }
205
206 public static Language getLanguageFromString(String languageString) {
207 return LanguageFactory.createLanguage(languageString);
208 }
209
210 public static void setSystemProperties(CPDConfiguration configuration) {
211 Properties properties = new Properties();
212 if (configuration.isIgnoreLiterals()) {
213 properties.setProperty(Tokenizer.IGNORE_LITERALS, "true");
214 } else {
215 properties.remove(Tokenizer.IGNORE_LITERALS);
216 }
217 if (configuration.isIgnoreIdentifiers()) {
218 properties.setProperty(Tokenizer.IGNORE_IDENTIFIERS, "true");
219 } else {
220 properties.remove(Tokenizer.IGNORE_IDENTIFIERS);
221 }
222 if (configuration.isIgnoreAnnotations()) {
223 properties.setProperty(Tokenizer.IGNORE_ANNOTATIONS, "true");
224 } else {
225 properties.remove(Tokenizer.IGNORE_ANNOTATIONS);
226 }
227 properties.setProperty(Tokenizer.OPTION_SKIP_BLOCKS, Boolean.toString(!configuration.isNoSkipBlocks()));
228 properties.setProperty(Tokenizer.OPTION_SKIP_BLOCKS_PATTERN, configuration.getSkipBlocksPattern());
229 configuration.getLanguage().setProperties(properties);
230 }
231
232 public Language getLanguage() {
233 return language;
234 }
235
236 public void setLanguage(Language language) {
237 this.language = language;
238 }
239
240 public int getMinimumTileSize() {
241 return minimumTileSize;
242 }
243
244 public void setMinimumTileSize(int minimumTileSize) {
245 this.minimumTileSize = minimumTileSize;
246 }
247
248 public boolean isSkipDuplicates() {
249 return skipDuplicates;
250 }
251
252 public void setSkipDuplicates(boolean skipDuplicates) {
253 this.skipDuplicates = skipDuplicates;
254 }
255
256 public String getRendererName() {
257 return rendererName;
258 }
259
260 public void setRendererName(String rendererName) {
261 this.rendererName = rendererName;
262 }
263
264 public Renderer getRenderer() {
265 return renderer;
266 }
267
268 public Tokenizer tokenizer() {
269 if (language == null) {
270 throw new IllegalStateException("Language is null.");
271 }
272 return language.getTokenizer();
273 }
274
275 public FilenameFilter filenameFilter() {
276 if (language == null) {
277 throw new IllegalStateException("Language is null.");
278 }
279
280 final FilenameFilter languageFilter = language.getFileFilter();
281 final Set<String> exclusions = new HashSet<String>();
282
283 if (excludes != null) {
284 FileFinder finder = new FileFinder();
285 for (File excludedFile : excludes) {
286 if (excludedFile.isDirectory()) {
287 List<File> files = finder.findFilesFrom(excludedFile, languageFilter, true);
288 for (File f : files) {
289 exclusions.add(FileUtil.normalizeFilename(f.getAbsolutePath()));
290 }
291 } else {
292 exclusions.add(FileUtil.normalizeFilename(excludedFile.getAbsolutePath()));
293 }
294 }
295 }
296
297 FilenameFilter filter = new FilenameFilter() {
298 public boolean accept(File dir, String name) {
299 File f = new File(dir, name);
300 if (exclusions.contains(FileUtil.normalizeFilename(f.getAbsolutePath()))) {
301 System.err.println("Excluding " + f.getAbsolutePath());
302 return false;
303 }
304 return languageFilter.accept(dir, name);
305 }
306 };
307 return filter;
308 }
309
310 public void setRenderer(Renderer renderer) {
311 this.renderer = renderer;
312 }
313
314 public boolean isIgnoreLiterals() {
315 return ignoreLiterals;
316 }
317
318 public void setIgnoreLiterals(boolean ignoreLiterals) {
319 this.ignoreLiterals = ignoreLiterals;
320 }
321
322 public boolean isIgnoreIdentifiers() {
323 return ignoreIdentifiers;
324 }
325
326 public void setIgnoreIdentifiers(boolean ignoreIdentifiers) {
327 this.ignoreIdentifiers = ignoreIdentifiers;
328 }
329
330 public boolean isIgnoreAnnotations() {
331 return ignoreAnnotations;
332 }
333
334 public void setIgnoreAnnotations(boolean ignoreAnnotations) {
335 this.ignoreAnnotations = ignoreAnnotations;
336 }
337
338 public boolean isSkipLexicalErrors() {
339 return skipLexicalErrors;
340 }
341
342 public void setSkipLexicalErrors(boolean skipLexicalErrors) {
343 this.skipLexicalErrors = skipLexicalErrors;
344 }
345
346 public List<File> getFiles() {
347 return files;
348 }
349
350 public void setFiles(List<File> files) {
351 this.files = files;
352 }
353
354 public String getURI() {
355 return uri;
356 }
357
358 public void setURI(String uri) {
359 this.uri = uri;
360 }
361
362 public List<File> getExcludes() {
363 return excludes;
364 }
365
366 public void setExcludes(List<File> excludes) {
367 this.excludes = excludes;
368 }
369
370 public boolean isNonRecursive() {
371 return nonRecursive;
372 }
373
374 public void setNonRecursive(boolean nonRecursive) {
375 this.nonRecursive = nonRecursive;
376 }
377
378 public boolean isHelp() {
379 return help;
380 }
381
382 public void setHelp(boolean help) {
383 this.help = help;
384 }
385
386 public String getEncoding() {
387 return encoding;
388 }
389
390 public boolean isNoSkipBlocks() {
391 return noSkipBlocks;
392 }
393
394 public void setNoSkipBlocks(boolean noSkipBlocks) {
395 this.noSkipBlocks = noSkipBlocks;
396 }
397
398 public String getSkipBlocksPattern() {
399 return skipBlocksPattern;
400 }
401
402 public void setSkipBlocksPattern(String skipBlocksPattern) {
403 this.skipBlocksPattern = skipBlocksPattern;
404 }
405 }