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