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