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  
25  import com.beust.jcommander.IStringConverter;
26  import com.beust.jcommander.Parameter;
27  import com.beust.jcommander.converters.FileConverter;
28  
29  /**
30   *
31   * @author Brian Remedios
32   * @author Romain Pelisse - <belaran@gmail.com>
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       * The actual renderer. constructed by using the {@link #rendererName}. This
54       * property is only valid after {@link #postContruct()} has been called!
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      // this has to be a public static class, so that JCommander can use it!
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      * Gets a renderer with the platform's default encoding.
143      * 
144      * @param name renderer name
145      * @return a fresh renderer instance
146      * @deprecated use {@link #getRendererFromString(String, String)} instead
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             // ignored - maybe this renderer doesn't have a encoding property
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 }