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