View Javadoc

1   package org.apache.maven.tools.plugin.generator;
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 org.apache.maven.plugin.descriptor.MojoDescriptor;
23  import org.apache.maven.plugin.descriptor.PluginDescriptor;
24  import org.apache.maven.project.MavenProject;
25  import org.apache.maven.tools.plugin.PluginToolsRequest;
26  import org.apache.velocity.VelocityContext;
27  import org.codehaus.plexus.logging.AbstractLogEnabled;
28  import org.codehaus.plexus.logging.Logger;
29  import org.codehaus.plexus.logging.console.ConsoleLogger;
30  import org.codehaus.plexus.util.FileUtils;
31  import org.codehaus.plexus.util.IOUtil;
32  import org.codehaus.plexus.util.PropertyUtils;
33  import org.codehaus.plexus.util.StringUtils;
34  import org.codehaus.plexus.velocity.VelocityComponent;
35  import org.objectweb.asm.ClassReader;
36  import org.objectweb.asm.ClassVisitor;
37  import org.objectweb.asm.ClassWriter;
38  import org.objectweb.asm.commons.Remapper;
39  import org.objectweb.asm.commons.RemappingClassAdapter;
40  import org.objectweb.asm.commons.SimpleRemapper;
41  
42  import java.io.File;
43  import java.io.FileInputStream;
44  import java.io.FileOutputStream;
45  import java.io.IOException;
46  import java.io.InputStream;
47  import java.io.InputStreamReader;
48  import java.io.StringWriter;
49  import java.util.List;
50  import java.util.Properties;
51  
52  /**
53   * Generates an <code>HelpMojo</code> class.
54   *
55   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
56   * @version $Id: PluginHelpGenerator.java 1354250 2012-06-26 21:36:53Z hboutemy $
57   * @since 2.4
58   */
59  public class PluginHelpGenerator
60      extends AbstractLogEnabled
61      implements Generator
62  {
63      /**
64       * Default generated class name
65       */
66      private static final String HELP_MOJO_CLASS_NAME = "HelpMojo";
67  
68      /**
69       * Help properties file, to store data about generated source.
70       */
71      private static final String HELP_PROPERTIES_FILENAME = "maven-plugin-help.properties";
72  
73      /**
74       * Default goal
75       */
76      private static final String HELP_GOAL = "help";
77  
78      private String helpPackageName;
79  
80      private VelocityComponent velocityComponent;
81  
82      /**
83       * Default constructor
84       */
85      public PluginHelpGenerator()
86      {
87          this.enableLogging( new ConsoleLogger( Logger.LEVEL_INFO, "PluginHelpGenerator" ) );
88      }
89  
90      // ----------------------------------------------------------------------
91      // Public methods
92      // ----------------------------------------------------------------------
93  
94      /**
95       * {@inheritDoc}
96       */
97      public void execute( File destinationDirectory, PluginToolsRequest request )
98          throws GeneratorException
99      {
100         PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
101 
102         String helpImplementation = getImplementation( pluginDescriptor );
103 
104         @SuppressWarnings( "unchecked" )
105         List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos();
106 
107         if ( mojoDescriptors != null )
108         {
109             // Verify that no help goal already exists
110             MojoDescriptor descriptor = pluginDescriptor.getMojo( HELP_GOAL );
111 
112             if ( ( descriptor != null ) && !descriptor.getImplementation().equals( helpImplementation ) )
113             {
114                 if ( getLogger().isWarnEnabled() )
115                 {
116                     getLogger().warn( "\n\nA help goal (" + descriptor.getImplementation()
117                                           + ") already exists in this plugin. SKIPPED THE " + helpImplementation
118                                           + " GENERATION.\n" );
119                 }
120 
121                 return;
122             }
123         }
124 
125         writeHelpPropertiesFile( request );
126 
127         try
128         {
129             String sourcePath = helpImplementation.replace( '.', File.separatorChar ) + ".java";
130 
131             File helpClass = new File( destinationDirectory, sourcePath );
132             helpClass.getParentFile().mkdirs();
133 
134             MavenProject mavenProject = request.getProject();
135             String pluginResourcesPath = "META-INF/maven/" + mavenProject.getGroupId() + "/" + mavenProject.getArtifactId();
136 
137             String helpClassSources = getHelpClassSources( pluginResourcesPath, pluginDescriptor );
138 
139             FileUtils.fileWrite( helpClass, request.getEncoding(), helpClassSources );
140         }
141         catch ( IOException e )
142         {
143             throw new GeneratorException( e.getMessage(), e );
144         }
145     }
146 
147     public PluginHelpGenerator setHelpPackageName( String helpPackageName )
148     {
149         this.helpPackageName = helpPackageName;
150         return this;
151     }
152 
153     public VelocityComponent getVelocityComponent()
154     {
155         return velocityComponent;
156     }
157 
158     public PluginHelpGenerator setVelocityComponent( VelocityComponent velocityComponent )
159     {
160         this.velocityComponent = velocityComponent;
161         return this;
162     }
163 
164     // ----------------------------------------------------------------------
165     // Private methods
166     // ----------------------------------------------------------------------
167 
168     private String getHelpClassSources( String pluginResourcesPath, PluginDescriptor pluginDescriptor )
169     {
170         Properties properties = new Properties();
171         VelocityContext context = new VelocityContext( properties );
172         if ( this.helpPackageName != null )
173         {
174             properties.put( "helpPackageName", this.helpPackageName );
175         }
176         else
177         {
178             properties.put( "helpPackageName", "" );
179         }
180         properties.put( "pluginHelpPath", pluginResourcesPath + "/plugin-help.xml" );
181         properties.put( "artifactId", pluginDescriptor.getArtifactId() );
182         properties.put( "goalPrefix", pluginDescriptor.getGoalPrefix() );
183 
184         // FIXME encoding !
185 
186         StringWriter stringWriter = new StringWriter();
187 
188         InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( "help-class-source.vm" );
189         InputStreamReader isReader = new InputStreamReader( is );
190         velocityComponent.getEngine().evaluate( context, stringWriter, "", isReader );
191 
192         return stringWriter.toString();
193     }
194 
195     /**
196      * @param pluginDescriptor The descriptor of the plugin for which to generate a help goal, must not be
197      *                         <code>null</code>.
198      * @return The implementation.
199      */
200     private String getImplementation( PluginDescriptor pluginDescriptor )
201     {
202         String packageName = helpPackageName;
203         if ( StringUtils.isEmpty( packageName ) )
204         {
205             packageName = GeneratorUtils.discoverPackageName( pluginDescriptor );
206         }
207 
208         return StringUtils.isEmpty( packageName ) ? HELP_MOJO_CLASS_NAME : packageName + '.' + HELP_MOJO_CLASS_NAME;
209     }
210 
211     /**
212      * Write help properties files for later use to eventually rewrite Help Mojo.
213      *
214      * @param request
215      * @throws GeneratorException
216      * @see {@link #rewriteHelpMojo(PluginToolsRequest)}
217      */
218     private void writeHelpPropertiesFile( PluginToolsRequest request )
219         throws GeneratorException
220     {
221         Properties properties = new Properties();
222         properties.put( "helpPackageName", helpPackageName == null ? "" : helpPackageName );
223 
224         File tmpPropertiesFile =
225             new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME );
226 
227         if ( tmpPropertiesFile.exists() )
228         {
229             tmpPropertiesFile.delete();
230         }
231         else if ( !tmpPropertiesFile.getParentFile().exists() )
232         {
233             tmpPropertiesFile.getParentFile().mkdirs();
234         }
235 
236         FileOutputStream fos = null;
237         try
238         {
239             fos = new FileOutputStream( tmpPropertiesFile );
240             properties.store( fos, "maven plugin help mojo generation informations" );
241         }
242         catch ( IOException e )
243         {
244             throw new GeneratorException( e.getMessage(), e );
245         }
246         finally
247         {
248             IOUtil.close( fos );
249         }
250     }
251 
252     /**
253      * Rewrite Help Mojo to match actual Mojos package name if it was not available at source generation
254      * time. This is used at descriptor generation time.
255      *
256      * @param request
257      * @throws GeneratorException
258      */
259     static void rewriteHelpMojo( PluginToolsRequest request )
260         throws GeneratorException
261     {
262         File tmpPropertiesFile =
263             new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME );
264 
265         if ( !tmpPropertiesFile.exists() )
266         {
267             return;
268         }
269 
270         Properties properties = PropertyUtils.loadProperties( tmpPropertiesFile );
271 
272         String helpPackageName = properties.getProperty( "helpPackageName" );
273 
274         // if helpPackageName property is empty, we have to rewrite the class with a better package name than empty
275         if ( StringUtils.isEmpty( helpPackageName ) )
276         {
277             String helpMojoImplementation = rewriteHelpClassToMojoPackage( request );
278 
279             if ( helpMojoImplementation != null )
280             {
281                 // rewrite plugin descriptor with new HelpMojo implementation class
282                 updateHelpMojoDescriptor( request.getPluginDescriptor(), helpMojoImplementation );
283             }
284         }
285     }
286 
287     private static String rewriteHelpClassToMojoPackage( PluginToolsRequest request )
288         throws GeneratorException
289     {
290         String destinationPackage = GeneratorUtils.discoverPackageName( request.getPluginDescriptor() );
291         if ( StringUtils.isEmpty( destinationPackage ) )
292         {
293             return null;
294         }
295         String packageAsDirectory = StringUtils.replace( destinationPackage, '.', '/' );
296 
297         String outputDirectory = request.getProject().getBuild().getOutputDirectory();
298         File helpClassFile = new File( outputDirectory, HELP_MOJO_CLASS_NAME + ".class" );
299         if ( !helpClassFile.exists() )
300         {
301             return null;
302         }
303 
304         File rewriteHelpClassFile =
305             new File( outputDirectory + '/' + packageAsDirectory, HELP_MOJO_CLASS_NAME + ".class" );
306         if ( !rewriteHelpClassFile.getParentFile().exists() )
307         {
308             rewriteHelpClassFile.getParentFile().mkdirs();
309         }
310 
311         FileInputStream fileInputStream = null;
312         ClassReader cr = null;
313         try
314         {
315             fileInputStream = new FileInputStream( helpClassFile );
316             cr = new ClassReader( fileInputStream );
317         }
318         catch ( IOException e )
319         {
320             throw new GeneratorException( e.getMessage(), e );
321         }
322         finally
323         {
324             IOUtil.close( fileInputStream );
325         }
326 
327         ClassWriter cw = new ClassWriter( 0 );
328 
329         Remapper packageRemapper =
330             new SimpleRemapper( HELP_MOJO_CLASS_NAME, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME );
331         ClassVisitor cv = new RemappingClassAdapter( cw, packageRemapper );
332 
333         try
334         {
335             cr.accept( cv, ClassReader.EXPAND_FRAMES );
336         }
337         catch ( Throwable e )
338         {
339             throw new GeneratorException( "ASM issue processing class-file " + helpClassFile.getPath(), e );
340         }
341 
342         byte[] renamedClass = cw.toByteArray();
343         FileOutputStream fos = null;
344         try
345         {
346             fos = new FileOutputStream( rewriteHelpClassFile );
347             fos.write( renamedClass );
348         }
349         catch ( IOException e )
350         {
351             throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e );
352         }
353         finally
354         {
355             IOUtil.close( fos );
356         }
357 
358         helpClassFile.delete();
359 
360         return destinationPackage + ".HelpMojo";
361     }
362 
363     private static void updateHelpMojoDescriptor( PluginDescriptor pluginDescriptor, String helpMojoImplementation )
364     {
365         MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( HELP_GOAL );
366 
367         if ( mojoDescriptor != null )
368         {
369             mojoDescriptor.setImplementation( helpMojoImplementation );
370         }
371     }
372 }