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      // this has to be a public static class, so that JCommander can use it!
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      * Gets a renderer with the platform's default encoding.
147      * 
148      * @param name renderer name
149      * @return a fresh renderer instance
150      * @deprecated use {@link #getRendererFromString(String, String)} instead
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             // ignored - maybe this renderer doesn't have a encoding property
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 }