View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.ant;
    5
    6   import java.io.IOException;
    7   import java.io.File;
    8   import java.io.PrintWriter;
    9   import java.io.StringWriter;
    10  import java.io.Writer;
    11  import java.util.ArrayList;
    12  import java.util.Collection;
    13  import java.util.Iterator;
    14  import java.util.LinkedList;
    15  import java.util.List;
    16  import java.util.concurrent.atomic.AtomicInteger;
    17  import java.util.logging.Handler;
    18  import java.util.logging.Level;
    19
    20  import net.sourceforge.pmd.DataSource;
    21  import net.sourceforge.pmd.FileDataSource;
    22  import net.sourceforge.pmd.PMD;
    23  import net.sourceforge.pmd.Report;
    24  import net.sourceforge.pmd.Rule;
    25  import net.sourceforge.pmd.RuleContext;
    26  import net.sourceforge.pmd.RuleSet;
    27  import net.sourceforge.pmd.RuleSetFactory;
    28  import net.sourceforge.pmd.RuleSetNotFoundException;
    29  import net.sourceforge.pmd.RuleSets;
    30  import net.sourceforge.pmd.SimpleRuleSetNameMapper;
    31  import net.sourceforge.pmd.SourceType;
    32  import net.sourceforge.pmd.renderers.AbstractRenderer;
    33  import net.sourceforge.pmd.renderers.Renderer;
    34  import net.sourceforge.pmd.ScopedLogHandlersManager;
    35  import net.sourceforge.pmd.util.AntLogHandler;
    36  import net.sourceforge.pmd.util.ClasspathClassLoader;
    37
    38  import org.apache.tools.ant.AntClassLoader;
    39  import org.apache.tools.ant.BuildException;
    40  import org.apache.tools.ant.DirectoryScanner;
    41  import org.apache.tools.ant.Project;
    42  import org.apache.tools.ant.Task;
    43  import org.apache.tools.ant.types.FileSet;
    44  import org.apache.tools.ant.types.Path;
    45  import org.apache.tools.ant.types.Reference;
    46
    47  public class PMDTask extends Task {
    48
    49      private Path classpath;
    50      private Path auxClasspath;
    51      private List<Formatter> formatters = new ArrayList<Formatter>();
    52      private List<FileSet> filesets = new ArrayList<FileSet>();
    53      private int minPriority = Rule.LOWEST_PRIORITY;
    54      private boolean shortFilenames;
    55      private String ruleSetFiles;
    56      private String encoding = System.getProperty("file.encoding");
    57      private boolean failOnError;
    58      private boolean failOnRuleViolation;
    59  	private int maxRuleViolations = 0;
    60      private String targetJDK = "1.5";
    61      private String failuresPropertyName;
    62      private String excludeMarker = PMD.EXCLUDE_MARKER;
    63      private int cpus = Runtime.getRuntime().availableProcessors();
    64      private final Collection<RuleSetWrapper> nestedRules = new ArrayList<RuleSetWrapper>();
    65
    66      public void setShortFilenames(boolean value) {
    67          this.shortFilenames = value;
    68      }
    69
    70      public void setTargetJDK(String value) {
    71          this.targetJDK = value;
    72      }
    73
    74      public void setExcludeMarker(String value) {
    75          this.excludeMarker = value;
    76      }
    77
    78      public void setFailOnError(boolean fail) {
    79          this.failOnError = fail;
    80      }
    81
    82      public void setFailOnRuleViolation(boolean fail) {
    83          this.failOnRuleViolation = fail;
    84      }
    85
    86  	public void setMaxRuleViolations(int max) {
    87  	    if (max >= 0) {
    88  		    this.maxRuleViolations = max;
    89  		    this.failOnRuleViolation = true;
    90  		}
    91  	}
    92
    93
    94      public void setRuleSetFiles(String ruleSetFiles) {
    95          this.ruleSetFiles = ruleSetFiles;
    96      }
    97
    98      public void setEncoding(String encoding) {
    99          this.encoding = encoding;
    100     }
    101
    102     public void setCpus(int cpus) {
    103         this.cpus = cpus;
    104     }
    105
    106     public void setFailuresPropertyName(String failuresPropertyName) {
    107         this.failuresPropertyName = failuresPropertyName;
    108     }
    109
    110     public void setMinimumPriority(int minPriority) {
    111         this.minPriority = minPriority;
    112     }
    113
    114     public void addFileset(FileSet set) {
    115         filesets.add(set);
    116     }
    117
    118     public void addFormatter(Formatter f) {
    119         formatters.add(f);
    120     }
    121
    122     public void setClasspath(Path classpath) {
    123         this.classpath = classpath;
    124     }
    125
    126     public Path getClasspath() {
    127         return classpath;
    128     }
    129
    130     public Path createClasspath() {
    131         if (classpath == null) {
    132             classpath = new Path(getProject());
    133         }
    134         return classpath.createPath();
    135     }
    136
    137     public void setClasspathRef(Reference r) {
    138         createLongClasspath().setRefid(r);
    139     }
    140
    141     public void setAuxClasspath(Path auxClasspath) {
    142         this.auxClasspath = auxClasspath;
    143     }
    144
    145     public Path getAuxClasspath() {
    146         return auxClasspath;
    147     }
    148
    149     public Path createAuxClasspath() {
    150         if (auxClasspath == null) {
    151             auxClasspath = new Path(getProject());
    152         }
    153         return auxClasspath.createPath();
    154     }
    155
    156     public void setAuxClasspathRef(Reference r) {
    157         createLongAuxClasspath().setRefid(r);
    158     }
    159
    160     private class AntTaskNameMapper extends SimpleRuleSetNameMapper {
    161         public AntTaskNameMapper(String s) {
    162             super(s);
    163         }
    164
    165         protected void check(String name) {
    166             if (name.indexOf("rulesets") == -1 && nameMap.containsKey(name)) {
    167                 append(nameMap.get(name));
    168             } else {
    169                 // substitute env variables/properties
    170                 append(getProject().replaceProperties(name));
    171             }
    172         }
    173
    174     }
    175
    176     private void doTask(){
    177         ruleSetFiles = new AntTaskNameMapper(ruleSetFiles).getRuleSets();
    178
    179         ClassLoader cl;
    180         if (classpath == null) {
    181             log("Using the normal ClassLoader", Project.MSG_VERBOSE);
    182             cl = getClass().getClassLoader();
    183         } else {
    184             log("Using the AntClassLoader", Project.MSG_VERBOSE);
    185             cl = new AntClassLoader(getProject(), classpath);
    186         }
    187         /*
    188          * 'basedir' is added to the path to make sure that relative paths
    189          * such as "<ruleset>resources/custom_ruleset.xml</ruleset>" still
    190          * work when ant is invoked from a different directory using "-f"
    191          */
    192         String extraPath = getProject().getBaseDir().toString();
    193
    194         if (auxClasspath != null) {
    195             log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
    196             extraPath = auxClasspath.toString() + File.pathSeparator + extraPath;
    197         }
    198
    199         try {
    200             cl = new ClasspathClassLoader(extraPath, cl);
    201         } catch (IOException ioe) {
    202             throw new BuildException(ioe.getMessage());
    203         }
    204
    205         final ClassLoader classLoader = cl;
    206         RuleSetFactory ruleSetFactory = new RuleSetFactory() {
    207             public RuleSets createRuleSets(String ruleSetFileNames) throws RuleSetNotFoundException {
    208                 return createRuleSets(ruleSetFiles, classLoader);
    209             }
    210         };
    211         for (Formatter formatter: formatters) {
    212             log("Sending a report to " + formatter, Project.MSG_VERBOSE);
    213             formatter.start(getProject().getBaseDir().toString());
    214         }
    215
    216         try {
    217             // This is just used to validate and display rules. Each thread will create its own ruleset
    218             RuleSets rules;
    219             ruleSetFactory.setMinimumPriority(minPriority);
    220             rules = ruleSetFactory.createRuleSets(ruleSetFiles, classLoader);
    221             logRulesUsed(rules);
    222         } catch (RuleSetNotFoundException e) {
    223             throw new BuildException(e.getMessage());
    224         }
    225
    226         SourceType sourceType;
    227         if (targetJDK.equals("1.3")) {
    228             log("Targeting Java language version 1.3", Project.MSG_VERBOSE);
    229             sourceType = SourceType.JAVA_13;
    230         } else if (targetJDK.equals("1.5")) {
    231             log("Targeting Java language version 1.5", Project.MSG_VERBOSE);
    232             sourceType = SourceType.JAVA_15;
    233         } else if (targetJDK.equals("1.6")) {
    234             log("Targeting Java language version 1.6", Project.MSG_VERBOSE);
    235             sourceType = SourceType.JAVA_16;
    236         } else if (targetJDK.equals("1.7")) {
    237             log("Targeting Java language version 1.7", Project.MSG_VERBOSE);
    238             sourceType = SourceType.JAVA_17;
    239         } else if(targetJDK.equals("jsp")){
    240             log("Targeting JSP", Project.MSG_VERBOSE);
    241             sourceType = SourceType.JSP;
    242         } else {
    243             log("Targeting Java language version 1.4", Project.MSG_VERBOSE);
    244             sourceType = SourceType.JAVA_14;
    245         }
    246
    247         if (excludeMarker != null) {
    248             log("Setting exclude marker to be " + excludeMarker, Project.MSG_VERBOSE);
    249         }
    250
    251         RuleContext ctx = new RuleContext();
    252         Report errorReport = new Report();
    253         final AtomicInteger reportSize = new AtomicInteger();
    254         for (FileSet fs: filesets) {
    255             List<DataSource> files = new LinkedList<DataSource>();
    256             DirectoryScanner ds = fs.getDirectoryScanner(getProject());
    257             String[] srcFiles = ds.getIncludedFiles();
    258             for (int j = 0; j < srcFiles.length; j++) {
    259                 File file = new File(ds.getBasedir() + System.getProperty("file.separator") + srcFiles[j]);
    260                 files.add(new FileDataSource(file));
    261             }
    262
    263             final String inputPath = ds.getBasedir().getPath();
    264
    265             Renderer logRenderer = new AbstractRenderer() {
    266                 public void start() {}
    267
    268                 public void startFileAnalysis(DataSource dataSource) {
    269                     log("Processing file " + dataSource.getNiceFileName(false, inputPath), Project.MSG_VERBOSE);
    270                 }
    271
    272                 public void renderFileReport(Report r) {
    273                     int size = r.size();
    274                     if (size > 0) {
    275                         reportSize.addAndGet(size);
    276                     }
    277                 }
    278
    279                 public void end() {}
    280
    281                 public void render(Writer writer, Report r) {}
    282             };
    283             List<Renderer> renderers = new LinkedList<Renderer>();
    284             renderers.add(logRenderer);
    285             for (Formatter formatter: formatters) {
    286                 renderers.add(formatter.getRenderer());
    287             }
    288             try {
    289                 PMD.processFiles(cpus, ruleSetFactory, sourceType, files, ctx,
    290                     renderers, ruleSetFiles,
    291                     shortFilenames, inputPath,
    292                     encoding, excludeMarker, classLoader);
    293             } catch (RuntimeException pmde) {
    294                 pmde.printStackTrace();
    295                 log(pmde.toString(), Project.MSG_VERBOSE);
    296                 if (pmde.getCause() != null) {
    297                     StringWriter strWriter = new StringWriter();
    298                     PrintWriter printWriter = new PrintWriter(strWriter);
    299                     pmde.getCause().printStackTrace(printWriter);
    300                     log(strWriter.toString(), Project.MSG_VERBOSE);
    301                 }
    302                 if (pmde.getCause() != null && pmde.getCause().getMessage() != null) {
    303                     log(pmde.getCause().getMessage(), Project.MSG_VERBOSE);
    304                 }
    305                 if (failOnError) {
    306                     throw new BuildException(pmde);
    307                 }
    308                 errorReport.addError(new Report.ProcessingError(pmde.getMessage(), ctx.getSourceCodeFilename()));
    309             }
    310         }
    311
    312         int problemCount = reportSize.get();
    313         log(problemCount + " problems found", Project.MSG_VERBOSE);
    314
    315         for (Formatter formatter: formatters) {
    316             formatter.end(errorReport);
    317         }
    318
    319         if (failuresPropertyName != null && problemCount > 0) {
    320             getProject().setProperty(failuresPropertyName, String.valueOf(problemCount));
    321             log("Setting property " + failuresPropertyName + " to " + problemCount, Project.MSG_VERBOSE);
    322         }
    323
    324         if (failOnRuleViolation && problemCount > maxRuleViolations) {
    325             throw new BuildException("Stopping build since PMD found " + problemCount + " rule violations in the code");
    326         }
    327     }
    328
    329     public void execute() throws BuildException {
    330         validate();
    331         final Handler antLogHandler = new AntLogHandler(this);
    332         final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST,antLogHandler);
    333         try{
    334             doTask();
    335         }finally{
    336             logManager.close();
    337         }
    338     }
    339
    340     private void logRulesUsed(RuleSets rules) {
    341         log("Using these rulesets: " + ruleSetFiles, Project.MSG_VERBOSE);
    342
    343         RuleSet[] ruleSets = rules.getAllRuleSets();
    344         for (int j = 0; j < ruleSets.length; j++) {
    345             RuleSet ruleSet = ruleSets[j];
    346
    347             for (Rule rule: ruleSet.getRules()) {
    348                 log("Using rule " + rule.getName(), Project.MSG_VERBOSE);
    349             }
    350         }
    351     }
    352
    353     private void validate() throws BuildException {
    354         // TODO - check for empty Formatters List here?
    355         for (Formatter f: formatters) {
    356             if (f.isNoOutputSupplied()) {
    357                 throw new BuildException("toFile or toConsole needs to be specified in Formatter");
    358             }
    359         }
    360
    361         if (ruleSetFiles == null) {
    362             if (nestedRules.isEmpty()) {
    363                 throw new BuildException("No rulesets specified");
    364             }
    365             ruleSetFiles = getNestedRuleSetFiles();
    366         }
    367
    368         if (!targetJDK.equals("1.3") && !targetJDK.equals("1.4") && !targetJDK.equals("1.5") && !targetJDK.equals("1.6") && !targetJDK.equals("1.7") && !targetJDK.equals("jsp")) {
    369             throw new BuildException("The targetjdk attribute, if used, must be set to either '1.3', '1.4', '1.5', '1.6', '1.7' or 'jsp'");
    370         }
    371     }
    372
    373     private String getNestedRuleSetFiles() {
    374         final StringBuffer sb = new StringBuffer();
    375         for (Iterator<RuleSetWrapper> it = nestedRules.iterator(); it.hasNext();) {
    376             RuleSetWrapper rs = it.next();
    377             sb.append(rs.getFile());
    378             if (it.hasNext()) {
    379                 sb.append(',');
    380             }
    381         }
    382         return sb.toString();
    383     }
    384
    385     private Path createLongClasspath() {
    386         if (classpath == null) {
    387             classpath = new Path(getProject());
    388         }
    389         return classpath.createPath();
    390     }
    391
    392     private Path createLongAuxClasspath() {
    393         if (auxClasspath == null) {
    394             auxClasspath = new Path(getProject());
    395         }
    396         return auxClasspath.createPath();
    397     }
    398
    399     public void addRuleset(RuleSetWrapper r) {
    400         nestedRules.add(r);
    401     }
    402
    403 }