PR: MPMD-4
Submitted by: Mark Titorenko
Add location independence to rulesets
Change default rulesets to the same as M1

git-svn-id: https://svn.apache.org/repos/asf/maven/plugins/trunk@367431 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/maven/plugin/pmd/CpdReport.java b/src/main/java/org/apache/maven/plugin/pmd/CpdReport.java
index 5a6d49c..54b6109 100644
--- a/src/main/java/org/apache/maven/plugin/pmd/CpdReport.java
+++ b/src/main/java/org/apache/maven/plugin/pmd/CpdReport.java
@@ -34,7 +34,6 @@
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.reporting.AbstractMavenReport;
 import org.apache.maven.reporting.MavenReportException;
-import org.codehaus.doxia.sink.Sink;
 import org.codehaus.doxia.site.renderer.SiteRenderer;
 
 /**
@@ -147,7 +146,6 @@
     public void executeReport( Locale locale )
         throws MavenReportException
     {
-        Sink sink = getSink();
 
         CPD cpd = new CPD( minimumTokens, new JavaLanguage() );
         String src = getProject().getBuild().getSourceDirectory();
@@ -161,8 +159,9 @@
             throw new MavenReportException( e.getMessage(), e );
         }
         cpd.go();
-        
-        CpdReportGenerator gen = new CpdReportGenerator(getSink(), src, getBundle( locale ), xrefLocation );
+
+        CpdReportGenerator gen = new CpdReportGenerator( getSink(), src, getBundle( locale ), linkXref ? xrefLocation
+                                                                                                      : null );
         gen.generate( cpd.getMatches() );
 
         if ( !isHtml() )
diff --git a/src/main/java/org/apache/maven/plugin/pmd/Locator.java b/src/main/java/org/apache/maven/plugin/pmd/Locator.java
new file mode 100644
index 0000000..769dd87
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugin/pmd/Locator.java
@@ -0,0 +1,140 @@
+package org.apache.maven.plugin.pmd;
+
+/*
+ * Copyright 2004-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugin.logging.SystemStreamLog;
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Performs Locator services for the <code>*Location</code> parameters in the 
+ * Reports.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ */
+public class Locator
+{
+    private Log log;
+
+    /**
+     * Create a Locator object.
+     * 
+     * @param logger the logger object to log with.
+     */
+    public Locator( Log log )
+    {
+        this.log = log;
+    }
+
+    /**
+     * Obtain a Log object.
+     * 
+     * @return the Log object.
+     */
+    private Log getLog()
+    {
+        if ( this.log == null )
+        {
+            this.log = new SystemStreamLog();
+        }
+        return this.log;
+    }
+
+    /**
+     * <p>
+     * Attempts to resolve a location parameter into a real file.
+     * </p>
+     * 
+     * <p>
+     * Checks a location string to for a resource, URL, or File that matches.
+     * If a resource or URL is found, then a local file is created with that
+     * locations contents.
+     * </p>
+     * 
+     * @param location the location string to match against.
+     * @param localfile the local file to use in case of resource or URL.
+     * @return the File of the resolved location.
+     * @throws IOException if file is unable to be found or copied into <code>localfile</code> destination.
+     */
+    public File resolveLocation( String location, String localfile )
+        throws IOException
+    {
+        getLog().debug( "resolveLocation(" + location + ", " + localfile + ")" );
+        if ( StringUtils.isEmpty( location ) )
+        {
+            return null;
+        }
+
+        File retFile = new File( localfile );
+
+        // Attempt a URL
+        if ( location.indexOf( "://" ) > 1 )
+        {
+            // Found a URL
+            URL url = new URL( location );
+            getLog().debug( "Potential URL: " + url.toExternalForm() );
+            FileUtils.copyURLToFile( url, retFile );
+        }
+        else
+        {
+            getLog().debug( "Location is not a URL." );
+            // Attempt a File.
+            File fileLocation = new File( location );
+            if ( fileLocation.exists() )
+            {
+                // Found a File.
+                getLog().debug( "Potential File: " + fileLocation.getAbsolutePath() );
+                FileUtils.copyFile( fileLocation, retFile );
+            }
+            else
+            {
+                getLog().debug( "Location is not a File." );
+                // Attempt a Resource.
+                URL url = this.getClass().getClassLoader().getResource( location );
+                if ( url != null )
+                {
+                    // Found a Resource.
+                    getLog().debug( "Potential Resource: " + url.toExternalForm() );
+                    FileUtils.copyURLToFile( url, retFile );
+                }
+                else
+                {
+                    getLog().debug( "Location is not a Resource." );
+                    throw new IOException( "Unable to find location '" + location + "' as URL, File or Resource." );
+                }
+            }
+        }
+
+        if ( !retFile.exists() )
+        {
+            throw new FileNotFoundException( "Destination file does not exist." );
+        }
+
+        if ( retFile.length() <= 0 )
+        {
+            throw new IOException( "Destination file has no content." );
+        }
+
+        return retFile;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugin/pmd/PmdReport.java b/src/main/java/org/apache/maven/plugin/pmd/PmdReport.java
index da14e60..2edb9dd 100644
--- a/src/main/java/org/apache/maven/plugin/pmd/PmdReport.java
+++ b/src/main/java/org/apache/maven/plugin/pmd/PmdReport.java
@@ -101,11 +101,17 @@
     private String format = "html";
 
     /**
-     * The PMD rulesets to use.  <a href="http://pmd.sourceforge.net/rules/index.html">Stock Rulesets</a> 
+     * The PMD rulesets to use.  <a href="http://pmd.sourceforge.net/rules/index.html">Stock Rulesets</a>
+     * Defaults to the basic, imports and unusedcode rulesets. 
      * 
      * @parameter
      */
-    private String[] rulesets = new String[] { "controversial" };
+    private String[] rulesets = new String[] 
+    {
+        "/rulesets/basic.xml",
+        "/rulesets/unusedcode.xml",
+        "/rulesets/imports.xml",
+    };
 
     /**
      * Link the violation line numbers to the source xref.
@@ -120,13 +126,13 @@
      * @parameter
      */
     private String xrefLocation = "xref";
-    
+
     /**
      * The file encoding to use when reading the java source.
      * @parameter 
      */
     private String sourceEncoding;
-    
+
     /**
      * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
      */
@@ -205,17 +211,25 @@
             throw new MavenReportException( "Can't parse " + sourceDirectory, e );
         }
 
+        Locator locator = new Locator( getLog() );
         RuleSetFactory ruleSetFactory = new RuleSetFactory();
         RuleSet[] sets = new RuleSet[rulesets.length];
-        for ( int idx = 0; idx < rulesets.length; idx++ )
+        try
         {
-            String set = rulesets[idx];
-            String location = "/rulesets/" + set + ".xml";
-            getLog().debug( "Preparing " + set + " ruleset found in classpath:" + location );
-            InputStream rulesInput = pmd.getClass().getResourceAsStream( location );
-            sets[idx] = ruleSetFactory.createRuleSet( rulesInput );
+            for ( int idx = 0; idx < rulesets.length; idx++ )
+            {
+                String set = rulesets[idx];
+                getLog().debug( "Preparing ruleset: " + set );
+                File ruleset = locator.resolveLocation( set, getLocationTemp( set ) );
+                InputStream rulesInput = new FileInputStream( ruleset );
+                sets[idx] = ruleSetFactory.createRuleSet( rulesInput );
+            }
         }
-        
+        catch ( IOException e )
+        {
+            throw new MavenReportException( e.getMessage(), e );
+        }
+
         boolean hasEncoding = sourceEncoding != null;
 
         for ( Iterator i = files.iterator(); i.hasNext(); )
@@ -234,8 +248,8 @@
                     {
                         // PMD closes this Reader even though it did not open it so we have
                         // to open a new one with every call to processFile().
-                        Reader reader = hasEncoding ? new InputStreamReader( new FileInputStream( file ), sourceEncoding ) 
-                                                    : new FileReader( file );
+                        Reader reader = hasEncoding ? new InputStreamReader( new FileInputStream( file ),
+                                                                             sourceEncoding ) : new FileReader( file );
                         pmd.processFile( reader, sets[idx], ruleContext );
                     }
                     catch ( UnsupportedEncodingException e1 )
@@ -260,7 +274,7 @@
             }
         }
         reportSink.endDocument();
-        
+
         if ( !isHtml() )
         {
             // Use the PMD renderers to render in any format aside from HTML.
@@ -279,6 +293,21 @@
         }
     }
 
+    private String getLocationTemp( String name )
+    {
+        String loc = name;
+        if ( loc.indexOf( '/' ) != -1 )
+        {
+            loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
+        }
+        if ( loc.indexOf( '\\' ) != -1 )
+        {
+            loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
+        }
+        getLog().debug( "Before: " + name + " After: " + loc );
+        return project.getBuild().getDirectory() + File.separator + loc;
+    }
+
     /**
      * Constructs the PMD class, passing it an argument
      * that configures the target JDK.
diff --git a/src/site/apt/howto.apt b/src/site/apt/howto.apt
index b15decf..389c7af 100644
--- a/src/site/apt/howto.apt
+++ b/src/site/apt/howto.apt
@@ -16,8 +16,8 @@
 
   The PMD and CPD reports share the same configuration.  For example, the following
   tells Maven to run the PMD and CPD report as part of the site report generation, use the 'basic' and 'controversial'
-  PMD rulesets and output the report in XML format.  The rulesets are assumed to reside in /rulesets/[name].xml 
-  in the classpath.  Note that HTML is always generated in addition to any other alternate format.  Legal formats
+  PMD rulesets and output the report in XML format.  The rulesets may reside in the classpath, filesystem or at a URL.
+  Note that HTML is always generated in addition to any other alternate format.  Legal formats
   are "html", "csv", "xml" and "txt".
   
   The reports will link directly to the cross-referenced source if you enable this with the linkXref parameter.
@@ -37,8 +37,10 @@
                 <artifactId>maven-pmd-plugin</artifactId>
                 <configuration>
                     <rulesets>
-                        <ruleset>basic</ruleset>
-                        <ruleset>controversial</ruleset>
+						<ruleset>/rulesets/basic.xml</ruleset>
+						<ruleset>/rulesets/controversial.xml</ruleset>
+						<ruleset>d:\rulesets\strings.xml</ruleset>
+						<ruleset>http://localhost/design.xml</ruleset>
                     </rulesets>
                     <format>xml</format>
                     <linkXref>true</linkXref>