View Javadoc

1   package org.apache.maven.plugin.pmd;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayOutputStream;
23  import java.io.File;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStreamWriter;
27  import java.io.UnsupportedEncodingException;
28  import java.io.Writer;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.ResourceBundle;
33  
34  import net.sourceforge.pmd.cpd.CPD;
35  import net.sourceforge.pmd.cpd.CSVRenderer;
36  import net.sourceforge.pmd.cpd.JavaLanguage;
37  import net.sourceforge.pmd.cpd.JavaTokenizer;
38  import net.sourceforge.pmd.cpd.Renderer;
39  import net.sourceforge.pmd.cpd.XMLRenderer;
40  
41  import org.apache.maven.reporting.MavenReportException;
42  import org.codehaus.plexus.util.FileUtils;
43  import org.codehaus.plexus.util.IOUtil;
44  import org.codehaus.plexus.util.StringUtils;
45  import org.codehaus.plexus.util.WriterFactory;
46  
47  /**
48   * Creates a report for PMD's CPD tool.  See
49   * <a href="http://pmd.sourceforge.net/cpd.html">http://pmd.sourceforge.net/cpd.html</a>
50   * for more detail.
51   *
52   * @author Mike Perham
53   * @version $Id: CpdReport.html 816696 2012-05-08 15:19:20Z hboutemy $
54   * @since 2.0
55   * @goal cpd
56   * @threadSafe
57   */
58  public class CpdReport
59      extends AbstractPmdReport
60  {
61      /**
62       * The minimum number of tokens that need to be duplicated before it causes a violation.
63       *
64       * @parameter expression="${minimumTokens}" default-value="100"
65       */
66      private int minimumTokens;
67  
68      /**
69       * Skip the CPD report generation.  Most useful on the command line
70       * via "-Dcpd.skip=true".
71       *
72       * @parameter expression="${cpd.skip}" default-value="false"
73       * @since 2.1
74       */
75      private boolean skip;
76  
77      /**
78       * If true, CPD ignores literal value differences when evaluating a duplicate block.
79       * This means that <code>foo=42;</code> and <code>foo=43;</code> will be seen as equivalent.
80       * You may want to run PMD with this option off to start with and then switch it on to see what it turns up.
81       *
82       * @parameter expression="${cpd.ignoreLiterals}" default-value="false"
83       * @since 2.5
84       */
85      private boolean ignoreLiterals;
86  
87      /**
88       * Similar to <code>ignoreLiterals</code> but for identifiers; i.e., variable names, methods names, and so forth.
89       *
90       * @parameter expression="${cpd.ignoreIdentifiers}" default-value="false"
91       * @since 2.5
92       */
93      private boolean ignoreIdentifiers;
94  
95      /** {@inheritDoc} */
96      public String getName( Locale locale )
97      {
98          return getBundle( locale ).getString( "report.cpd.name" );
99      }
100 
101     /** {@inheritDoc} */
102     public String getDescription( Locale locale )
103     {
104         return getBundle( locale ).getString( "report.cpd.description" );
105     }
106 
107     /** {@inheritDoc} */
108     public void executeReport( Locale locale )
109         throws MavenReportException
110     {
111         try
112         {
113             execute( locale );
114         }
115         finally
116         {
117             if ( getSink() != null )
118             {
119                 getSink().close();
120             }
121         }
122     }
123 
124     private void execute( Locale locale )
125         throws MavenReportException
126     {
127         if ( !skip && canGenerateReport() )
128         {
129             ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
130             try
131             {
132                 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
133 
134                 CPD cpd = generateReport( locale );
135 
136                 if ( !isHtml() )
137                 {
138                     writeNonHtml( cpd );
139                 }
140             }
141             finally
142             {
143                 Thread.currentThread().setContextClassLoader( origLoader );
144             }
145 
146         }
147     }
148 
149     private CPD generateReport( Locale locale )
150         throws MavenReportException
151     {
152         Properties p = new Properties();
153         if ( ignoreLiterals )
154         {
155             p.setProperty( JavaTokenizer.IGNORE_LITERALS, "true" );
156         }
157         if ( ignoreIdentifiers )
158         {
159             p.setProperty( JavaTokenizer.IGNORE_IDENTIFIERS, "true" );
160         }
161         CPD cpd = new CPD( minimumTokens, new JavaLanguage( p ) );
162 
163         Map<File, PmdFileInfo> files = null;
164         try
165         {
166             files = getFilesToProcess();
167 
168             if ( StringUtils.isNotEmpty( getSourceEncoding() ) )
169             {
170                 cpd.setEncoding( getSourceEncoding() );
171 
172                 // test encoding as CPD will convert exception into a RuntimeException
173                 WriterFactory.newWriter( new ByteArrayOutputStream(), getSourceEncoding() );
174             }
175             else if ( !files.isEmpty() )
176             {
177                 getLog().warn(
178                                "File encoding has not been set, using platform encoding "
179                                    + WriterFactory.FILE_ENCODING + ", i.e. build is platform dependent!" );
180             }
181 
182             for ( File file : files.keySet() )
183             {
184                 cpd.add( file );
185             }
186         }
187         catch ( UnsupportedEncodingException e )
188         {
189             throw new MavenReportException( "Encoding '" + getSourceEncoding() + "' is not supported.", e );
190         }
191         catch ( IOException e )
192         {
193             throw new MavenReportException( e.getMessage(), e );
194         }
195         cpd.go();
196 
197         CpdReportGenerator gen =
198             new CpdReportGenerator( getSink(), files, getBundle( locale ), aggregate );
199         gen.generate( cpd.getMatches() );
200 
201         return cpd;
202     }
203 
204     void writeNonHtml( CPD cpd )
205         throws MavenReportException
206     {
207         Renderer r = createRenderer();
208 
209         if ( r == null )
210         {
211             return;
212         }
213 
214         String buffer = r.render( cpd.getMatches() );
215         Writer writer = null;
216         try
217         {
218             targetDirectory.mkdirs();
219             File targetFile = new File( targetDirectory, "cpd." + format );
220             FileOutputStream tStream = new FileOutputStream( targetFile );
221             writer = new OutputStreamWriter( tStream, getOutputEncoding() );
222             writer.write( buffer );
223             writer.close();
224 
225             File siteDir = getReportOutputDirectory();
226             siteDir.mkdirs();
227             FileUtils.copyFile( targetFile, new File( siteDir, "cpd." + format ) );
228         }
229         catch ( IOException ioe )
230         {
231             throw new MavenReportException( ioe.getMessage(), ioe );
232         }
233         finally
234         {
235             IOUtil.close( writer );
236         }
237     }
238 
239     /** {@inheritDoc} */
240     public String getOutputName()
241     {
242         return "cpd";
243     }
244 
245     private static ResourceBundle getBundle( Locale locale )
246     {
247         return ResourceBundle.getBundle( "cpd-report", locale, CpdReport.class.getClassLoader() );
248     }
249 
250     /**
251      * Create and return the correct renderer for the output type.
252      *
253      * @return the renderer based on the configured output
254      * @throws org.apache.maven.reporting.MavenReportException
255      *          if no renderer found for the output type
256      */
257     public Renderer createRenderer()
258         throws MavenReportException
259     {
260         Renderer renderer = null;
261         if ( "xml".equals( format ) )
262         {
263             renderer = new XMLRenderer( getOutputEncoding() );
264         }
265         else if ( "csv".equals( format ) )
266         {
267             renderer = new CSVRenderer();
268         }
269         else if ( !"".equals( format ) && !"none".equals( format ) )
270         {
271             try
272             {
273                 renderer = (Renderer) Class.forName( format ).newInstance();
274             }
275             catch ( Exception e )
276             {
277                 throw new MavenReportException(
278                     "Can't find CPD custom format " + format + ": " + e.getClass().getName(), e );
279             }
280         }
281 
282         return renderer;
283     }
284 }