View Javadoc
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.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   * @author Brian Remedios
33   * @author Romain Pelisse - <belaran@gmail.com>
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       * The actual renderer. constructed by using the {@link #rendererName}. This
55       * property is only valid after {@link #postContruct()} has been called!
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      @Parameter(names = {"--failOnViolation", "-failOnViolation"}, arity = 1, description = "By default CPD exits with status 4 if code duplications are found. Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report.")
99      private boolean failOnViolation = true;
100 
101     // this has to be a public static class, so that JCommander can use it!
102     public static class LanguageConverter implements IStringConverter<Language> {
103 
104         public Language convert(String languageString) {
105             if (languageString == null || "".equals(languageString)) {
106                 languageString = DEFAULT_LANGUAGE;
107             }
108             return LanguageFactory.createLanguage(languageString);
109         }
110     }
111 
112     public CPDConfiguration() {
113     }
114 
115     @Deprecated
116     public CPDConfiguration(int minimumTileSize, Language language, String encoding) {
117         setMinimumTileSize(minimumTileSize);
118         setLanguage(language);
119         setEncoding(encoding);
120     }
121 
122     @Parameter(names = "--encoding", description = "Character encoding to use when processing files", required = false)
123     public void setEncoding(String encoding) {
124         this.encoding = encoding;
125         setSourceEncoding(encoding);
126     }
127 
128     public SourceCode sourceCodeFor(File file) {
129         return new SourceCode(new SourceCode.FileCodeLoader(file, getSourceEncoding()));
130     }
131 
132     public SourceCode sourceCodeFor(Reader reader, String sourceCodeName) {
133         return new SourceCode(new SourceCode.ReaderCodeLoader(reader, sourceCodeName));
134     }
135 
136     public void postContruct() {
137         if (this.getLanguage() == null) {
138             this.setLanguage(CPDConfiguration.getLanguageFromString(DEFAULT_LANGUAGE));
139         }
140         if (this.getRendererName() == null) {
141             this.setRendererName(DEFAULT_RENDERER);
142         }
143         if (this.getRenderer() == null) {
144             this.setRenderer(getRendererFromString(getRendererName(), this.getEncoding()));
145         }
146     }
147 
148     /**
149      * Gets a renderer with the platform's default encoding.
150      * 
151      * @param name renderer name
152      * @return a fresh renderer instance
153      * @deprecated use {@link #getRendererFromString(String, String)} instead
154      */
155     @Deprecated
156     public static Renderer getRendererFromString(String name) {
157         return getRendererFromString(name, System.getProperty("file.encoding"));
158     }
159 
160     private static final Map<String, Class<? extends Renderer>> RENDERERS = new HashMap<>();
161     static {
162         RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class);
163         RENDERERS.put("xml", XMLRenderer.class);
164         RENDERERS.put("csv", CSVRenderer.class);
165         RENDERERS.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class);
166         RENDERERS.put("vs", VSRenderer.class);
167     }
168 
169     public static Renderer getRendererFromString(String name, String encoding) {
170         String clazzname = name;
171         if (clazzname == null || "".equals(clazzname)) {
172             clazzname = DEFAULT_RENDERER;
173         }
174         Class<? extends Renderer> clazz = RENDERERS.get(clazzname.toLowerCase(Locale.ROOT));
175         if (clazz == null) {
176             try {
177                 clazz = Class.forName(clazzname).asSubclass(Renderer.class);
178             } catch (ClassNotFoundException e) {
179                 System.err.println("Can't find class '" + name + "', defaulting to SimpleRenderer.");
180                 clazz = SimpleRenderer.class;
181             }
182         }
183         try {
184             Renderer renderer = clazz.getDeclaredConstructor().newInstance();
185             setRendererEncoding(renderer, encoding);
186             return renderer;
187         } catch (Exception e) {
188             System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e);
189             return new SimpleRenderer();
190         }
191     }
192 
193     private static void setRendererEncoding(Renderer renderer, String encoding)
194             throws IllegalAccessException, InvocationTargetException {
195         try {
196             PropertyDescriptor encodingProperty = new PropertyDescriptor("encoding", renderer.getClass());
197             Method method = encodingProperty.getWriteMethod();
198             if (method != null) {
199                 method.invoke(renderer, encoding);
200             }
201         } catch (IntrospectionException e) {
202             // ignored - maybe this renderer doesn't have a encoding property
203         }
204     }
205 
206     public static String[] getRenderers() {
207         String[] result = RENDERERS.keySet().toArray(new String[RENDERERS.size()]);
208         Arrays.sort(result);
209         return result;
210     }
211 
212     public static Language getLanguageFromString(String languageString) {
213         return LanguageFactory.createLanguage(languageString);
214     }
215 
216     public static void setSystemProperties(CPDConfiguration configuration) {
217         Properties properties = new Properties();
218         if (configuration.isIgnoreLiterals()) {
219             properties.setProperty(Tokenizer.IGNORE_LITERALS, "true");
220         } else {
221             properties.remove(Tokenizer.IGNORE_LITERALS);
222         }
223         if (configuration.isIgnoreIdentifiers()) {
224             properties.setProperty(Tokenizer.IGNORE_IDENTIFIERS, "true");
225         } else {
226             properties.remove(Tokenizer.IGNORE_IDENTIFIERS);
227         }
228         if (configuration.isIgnoreAnnotations()) {
229             properties.setProperty(Tokenizer.IGNORE_ANNOTATIONS, "true");
230         } else {
231             properties.remove(Tokenizer.IGNORE_ANNOTATIONS);
232         }
233         if (configuration.isIgnoreUsings()) {
234             properties.setProperty(Tokenizer.IGNORE_USINGS, "true");
235         } else {
236             properties.remove(Tokenizer.IGNORE_USINGS);
237         }
238         properties.setProperty(Tokenizer.OPTION_SKIP_BLOCKS, Boolean.toString(!configuration.isNoSkipBlocks()));
239         properties.setProperty(Tokenizer.OPTION_SKIP_BLOCKS_PATTERN, configuration.getSkipBlocksPattern());
240         configuration.getLanguage().setProperties(properties);
241     }
242 
243     public Language getLanguage() {
244         return language;
245     }
246 
247     public void setLanguage(Language language) {
248         this.language = language;
249     }
250 
251     public int getMinimumTileSize() {
252         return minimumTileSize;
253     }
254 
255     public void setMinimumTileSize(int minimumTileSize) {
256         this.minimumTileSize = minimumTileSize;
257     }
258 
259     public boolean isSkipDuplicates() {
260         return skipDuplicates;
261     }
262 
263     public void setSkipDuplicates(boolean skipDuplicates) {
264         this.skipDuplicates = skipDuplicates;
265     }
266 
267     public String getRendererName() {
268         return rendererName;
269     }
270 
271     public void setRendererName(String rendererName) {
272         this.rendererName = rendererName;
273     }
274 
275     public Renderer getRenderer() {
276         return renderer;
277     }
278 
279     public Tokenizer tokenizer() {
280         if (language == null) {
281             throw new IllegalStateException("Language is null.");
282         }
283         return language.getTokenizer();
284     }
285 
286     public FilenameFilter filenameFilter() {
287         if (language == null) {
288             throw new IllegalStateException("Language is null.");
289         }
290 
291         final FilenameFilter languageFilter = language.getFileFilter();
292         final Set<String> exclusions = new HashSet<>();
293 
294         if (excludes != null) {
295             FileFinder finder = new FileFinder();
296             for (File excludedFile : excludes) {
297                 if (excludedFile.isDirectory()) {
298                     List<File> files = finder.findFilesFrom(excludedFile, languageFilter, true);
299                     for (File f : files) {
300                         exclusions.add(FileUtil.normalizeFilename(f.getAbsolutePath()));
301                     }
302                 } else {
303                     exclusions.add(FileUtil.normalizeFilename(excludedFile.getAbsolutePath()));
304                 }
305             }
306         }
307 
308         FilenameFilter filter = new FilenameFilter() {
309             public boolean accept(File dir, String name) {
310                 File f = new File(dir, name);
311                 if (exclusions.contains(FileUtil.normalizeFilename(f.getAbsolutePath()))) {
312                     System.err.println("Excluding " + f.getAbsolutePath());
313                     return false;
314                 }
315                 return languageFilter.accept(dir, name);
316             }
317         };
318         return filter;
319     }
320 
321     public void setRenderer(Renderer renderer) {
322         this.renderer = renderer;
323     }
324 
325     public boolean isIgnoreLiterals() {
326         return ignoreLiterals;
327     }
328 
329     public void setIgnoreLiterals(boolean ignoreLiterals) {
330         this.ignoreLiterals = ignoreLiterals;
331     }
332 
333     public boolean isIgnoreIdentifiers() {
334         return ignoreIdentifiers;
335     }
336 
337     public void setIgnoreIdentifiers(boolean ignoreIdentifiers) {
338         this.ignoreIdentifiers = ignoreIdentifiers;
339     }
340 
341     public boolean isIgnoreAnnotations() {
342         return ignoreAnnotations;
343     }
344 
345     public void setIgnoreAnnotations(boolean ignoreAnnotations) {
346         this.ignoreAnnotations = ignoreAnnotations;
347     }
348 
349     public boolean isIgnoreUsings() {
350         return ignoreUsings;
351     }
352 
353     public void setIgnoreUsings(boolean ignoreUsings) {
354         this.ignoreUsings = ignoreUsings;
355     }
356 
357     public boolean isSkipLexicalErrors() {
358         return skipLexicalErrors;
359     }
360 
361     public void setSkipLexicalErrors(boolean skipLexicalErrors) {
362         this.skipLexicalErrors = skipLexicalErrors;
363     }
364 
365     public List<File> getFiles() {
366         return files;
367     }
368 
369     public void setFiles(List<File> files) {
370         this.files = files;
371     }
372 
373     public String getURI() {
374         return uri;
375     }
376 
377     public void setURI(String uri) {
378         this.uri = uri;
379     }
380 
381     public List<File> getExcludes() {
382         return excludes;
383     }
384 
385     public void setExcludes(List<File> excludes) {
386         this.excludes = excludes;
387     }
388 
389     public boolean isNonRecursive() {
390         return nonRecursive;
391     }
392 
393     public void setNonRecursive(boolean nonRecursive) {
394         this.nonRecursive = nonRecursive;
395     }
396 
397     public boolean isHelp() {
398         return help;
399     }
400 
401     public void setHelp(boolean help) {
402         this.help = help;
403     }
404 
405     public String getEncoding() {
406         return encoding;
407     }
408 
409     public boolean isNoSkipBlocks() {
410         return noSkipBlocks;
411     }
412 
413     public void setNoSkipBlocks(boolean noSkipBlocks) {
414         this.noSkipBlocks = noSkipBlocks;
415     }
416 
417     public String getSkipBlocksPattern() {
418         return skipBlocksPattern;
419     }
420 
421     public void setSkipBlocksPattern(String skipBlocksPattern) {
422         this.skipBlocksPattern = skipBlocksPattern;
423     }
424 
425     public boolean isFailOnViolation() {
426         return failOnViolation;
427     }
428 
429     public void setFailOnViolation(boolean failOnViolation) {
430         this.failOnViolation = failOnViolation;
431     }
432 }