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