CommandLineOptions.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd;
005 
006 import java.io.IOException;
007 import java.util.List;
008 import java.util.Map;
009 import java.util.Properties;
010 
011 import net.sourceforge.pmd.lang.Language;
012 import net.sourceforge.pmd.lang.LanguageVersion;
013 import net.sourceforge.pmd.renderers.Renderer;
014 import net.sourceforge.pmd.renderers.RendererFactory;
015 import net.sourceforge.pmd.util.StringUtil;
016 
017 /**
018  * Command line options parser class.  Produces a Configuration instance to
019  * use with PMD processing.
020  */
021 public class CommandLineOptions {
022 
023     private final static int LANGUAGE_NAME_INDEX = 1;
024     private final static int LANGUAGE_VERSION_INDEX = 2;
025 
026     private final Configuration configuration = new Configuration();
027 
028     private String[] args;
029     private int optionEndIndex;
030 
031     public CommandLineOptions(String[] args) {
032 
033   this.args = args;
034 
035   if (args == null || args.length < 3) {
036       throw new IllegalArgumentException(usage());
037   }
038   int mandatoryIndex = 0;
039   int optionStartIndex = 3;
040   optionEndIndex = args.length;
041   if (args[0].charAt(0== '-') {
042       mandatoryIndex = args.length - 3;
043       optionStartIndex = 0;
044       optionEndIndex = args.length - 3;
045   }
046 
047   configuration.setInputPaths(args[mandatoryIndex]);
048   configuration.setReportFormat(args[mandatoryIndex + 1]);
049   if (StringUtil.isEmpty(configuration.getReportFormat())) {
050       throw new IllegalArgumentException("Report renderer is required.");
051   }
052   configuration.setRuleSets(StringUtil
053     .asString(RuleSetReferenceId.parse(args[mandatoryIndex + 2]).toArray()","));
054 
055   for (int optionsIndex = optionStartIndex; optionsIndex < optionEndIndex; optionsIndex++) {
056       String opt = args[optionsIndex];
057       if ("-debug".equals(opt)) {
058     configuration.setDebug(true);
059       else if ("-stress".equals(opt)) {
060     configuration.setStressTest(true);
061       else if ("-shortnames".equals(opt)) {
062     configuration.setReportShortNames(true);
063       else if ("-encoding".equals(opt)) {
064     checkOption(opt, optionsIndex, 1);
065     configuration.setSourceEncoding(args[++optionsIndex]);
066       else if ("-threads".equals(opt)) {
067     checkOption(opt, optionsIndex, 1);
068     configuration.setThreads(parseInt(opt, args[++optionsIndex]));
069       else if ("-suppressmarker".equals(opt)) {
070     checkOption(opt, optionsIndex, 1);
071     configuration.setSuppressMarker(args[++optionsIndex]);
072       else if ("-version".equals(opt)) {
073     checkOption(opt, optionsIndex, 2);
074     configuration.setDefaultLanguageVersion(parseLanguageVersion(optionsIndex));
075     optionsIndex += 2;
076       else if ("-minimumpriority".equals(opt)) {
077     checkOption(opt, optionsIndex, 1);
078     configuration.setMinimumPriority(parseMinimunPriority(args[++optionsIndex]));
079       else if ("-showsuppressed".equals(opt)) {
080     configuration.setShowSuppressedViolations(true);
081       else if ("-property".equals(opt)) {
082     checkOption(opt, optionsIndex, 2);
083     configuration.getReportProperties().put(args[++optionsIndex], args[++optionsIndex]);
084       else if ("-reportfile".equals(opt)) {
085     checkOption(opt, optionsIndex, 1);
086     configuration.setReportFile(args[++optionsIndex]);
087       else if ("-benchmark".equals(opt)) {
088     configuration.setBenchmark(true);
089       else if ("-auxclasspath".equals(opt)) {
090     checkOption(opt, optionsIndex, 1);
091     try {
092         configuration.prependClasspath(args[++optionsIndex]);
093     catch (IOException e) {
094         throw new IllegalArgumentException("Invalid auxiliary classpath: " + e.getMessage(), e);
095     }
096       else {
097     throw new IllegalArgumentException("Unexpected command line argument: " + opt);
098       }
099   }
100     }
101 
102     private void checkOption(String opt, int index, int count) {
103   boolean valid = true;
104   if (index + count >= optionEndIndex) {
105       valid = false;
106   else {
107       for (int i = 1; i <= count; i++) {
108     if (args[index + i].charAt(0== '-') {
109         valid = false;
110         break;
111     }
112       }
113   }
114   if (!valid) {
115       throw new IllegalArgumentException(opt + " requires " + count + " parameters.\n\n" + usage());
116   }
117     }
118 
119     private RulePriority parseMinimunPriority(String priority) {
120   try {
121       return RulePriority.valueOf(Integer.parseInt(priority));
122   catch (NumberFormatException e) {
123       throw new IllegalArgumentException("Minimum priority must be a whole number between " + RulePriority.HIGH
124         " and " + RulePriority.LOW + ", " + priority + " received", e);
125   }
126     }
127 
128     private int parseInt(String opt, String s) {
129   try {
130       return Integer.parseInt(s);
131   catch (NumberFormatException e) {
132       throw new IllegalArgumentException(opt + " parameter must be a whole number, " + s + " received");
133   }
134     }
135 
136     private LanguageVersion parseLanguageVersion(int optionsIndex) {
137   String languageName = args[optionsIndex + LANGUAGE_NAME_INDEX];
138   Language language = Language.findByTerseName(languageName);
139   if (language == null) {
140       throw new IllegalArgumentException("Unknown Language '" + languageName + "'.  Available Languages are : "
141         + Language.commaSeparatedTerseNames(Language.findWithRuleSupport()));
142   else {
143       if (args.length > (optionsIndex + LANGUAGE_VERSION_INDEX)) {
144     String version = args[optionsIndex + LANGUAGE_VERSION_INDEX];
145     List<LanguageVersion> languageVersions = LanguageVersion.findVersionsForLanguageTerseName(language
146       .getTerseName());
147     // If there is versions for this language, it should be a valid one...
148     if (!languageVersions.isEmpty()) {
149         for (LanguageVersion languageVersion : languageVersions) {
150       if (version.equals(languageVersion.getVersion())) {
151           return languageVersion;
152       }
153         }
154         throw new IllegalArgumentException("Language version '" + version
155           "' is not availaible for Language '" + language.getName()
156           "'.\nAvailable versions are :"
157           + LanguageVersion.commaSeparatedTerseNames(languageVersions));
158     }
159       }
160       return language.getDefaultVersion();
161   }
162     }
163 
164     public Configuration getConfiguration() {
165   return configuration;
166     }
167 
168     public static String usage() {
169         return PMD.EOL + PMD.EOL +
170                 "Mandatory arguments:" + PMD.EOL +
171                 "1) A java source code filename or directory" + PMD.EOL +
172                 "2) A report format " + PMD.EOL +
173                 "3) A ruleset filename or a comma-delimited string of ruleset filenames" + PMD.EOL +
174                 PMD.EOL +
175                 "For example: " + PMD.EOL +
176                 "c:\\> java -jar pmd-" + PMD.VERSION + ".jar c:\\my\\source\\code html unusedcode" + PMD.EOL +
177                 PMD.EOL +
178                 "Optional arguments that may be put before or after the mandatory arguments: " + PMD.EOL +
179                 "-version {name} {version}: specify version of a language PMD should use" + PMD.EOL + 
180                 "-debug: prints debugging information" + PMD.EOL +
181                 "-threads: specifies the number of threads to create" + PMD.EOL +
182                 "-encoding: specifies the character set encoding of the source code files PMD is reading (i.e., UTF-8)" + PMD.EOL +
183                 "-suppressmarker: specifies the String that marks the a line which PMD should ignore; default is NOPMD" + PMD.EOL +
184                 "-shortnames: prints shortened filenames in the report" + PMD.EOL +
185                 "-minimumpriority: rule priority threshold; rules with lower priority than they will not be used" + PMD.EOL +
186                 "-showsuppressed: report should show suppressed rule violations" + PMD.EOL +
187                 "-property {name} {value}: define a property for the report" + PMD.EOL +
188                 "-reportfile: send report output to a file; default to System.out" + PMD.EOL +
189                 "-benchmark: output a benchmark report upon completion; default to System.err" + PMD.EOL +
190                 "-auxclasspath: specifies the classpath for libraries used by the source code (used by type resolution)" + PMD.EOL +
191                 "   (alternatively, a 'file://' URL to a text file containing path elements on consecutive lines)" + PMD.EOL +
192                 PMD.EOL +
193                 "Available report formats and their configuration properties are:" + PMD.EOL +
194                 getReports() +
195                 PMD.EOL +
196                 "For example on windows: " + PMD.EOL +
197                 "c:\\> java -jar pmd-" + PMD.VERSION + ".jar c:\\my\\source\\code text unusedcode,imports -version java 1.5 -debug" + PMD.EOL +
198                 "c:\\> java -jar pmd-" + PMD.VERSION + ".jar c:\\my\\source\\code xml basic,design -encoding UTF-8" + PMD.EOL +
199                 "c:\\> java -jar pmd-" + PMD.VERSION + ".jar c:\\my\\source\\code html typeresolution -auxclasspath commons-collections.jar;derby.jar" + PMD.EOL +                
200                 "c:\\> java -jar pmd-" + PMD.VERSION + ".jar c:\\my\\source\\code html typeresolution -auxclasspath file:///C:/my/classpathfile" + PMD.EOL +
201                 PMD.EOL +
202                 "For example on *nix: " + PMD.EOL +
203                 "$ java -jar pmd-" + PMD.VERSION + ".jar /home/workspace/src/main/java/code nicehtml basic,design" + PMD.EOL +
204                 "$ java -jar pmd-" + PMD.VERSION + ".jar /home/workspace/src/main/java/code nicehtml basic,design -xslt my-own.xsl" + PMD.EOL +
205                 "$ java -jar pmd-" + PMD.VERSION + ".jar /home/workspace/src/main/java/code nicehtml typeresolution -auxclasspath commons-collections.jar:derby.jar" + PMD.EOL +
206                 PMD.EOL;
207     }
208 
209     private static String getReports() {
210   StringBuilder buf = new StringBuilder();
211   for (String reportName : RendererFactory.REPORT_FORMAT_TO_RENDERER.keySet()) {
212       Renderer renderer = RendererFactory.createRenderer(reportName, new Properties());
213       buf.append("   ");
214       buf.append(reportName);
215       buf.append(": ");
216       if (!reportName.equals(renderer.getName())) {
217     buf.append(" Deprecated alias for '" + renderer.getName());
218     buf.append(PMD.EOL);
219     continue;
220       }
221       buf.append(renderer.getDescription());
222       buf.append(PMD.EOL);
223       for (Map.Entry<String, String> entry : renderer.getPropertyDefinitions().entrySet()) {
224     buf.append("       ");
225     buf.append(entry.getKey());
226     buf.append(" - ");
227     buf.append(entry.getValue());
228     buf.append(PMD.EOL);
229       }
230   }
231   return buf.toString();
232     }
233 }