001    package org.apache.maven.tools.plugin.generator;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.maven.plugin.descriptor.MojoDescriptor;
023    import org.apache.maven.plugin.descriptor.PluginDescriptor;
024    import org.apache.maven.project.MavenProject;
025    import org.apache.maven.tools.plugin.PluginToolsRequest;
026    import org.apache.velocity.VelocityContext;
027    import org.codehaus.plexus.logging.AbstractLogEnabled;
028    import org.codehaus.plexus.logging.Logger;
029    import org.codehaus.plexus.logging.console.ConsoleLogger;
030    import org.codehaus.plexus.util.FileUtils;
031    import org.codehaus.plexus.util.IOUtil;
032    import org.codehaus.plexus.util.PropertyUtils;
033    import org.codehaus.plexus.util.StringUtils;
034    import org.codehaus.plexus.velocity.VelocityComponent;
035    import org.objectweb.asm.ClassReader;
036    import org.objectweb.asm.ClassVisitor;
037    import org.objectweb.asm.ClassWriter;
038    import org.objectweb.asm.commons.Remapper;
039    import org.objectweb.asm.commons.RemappingClassAdapter;
040    import org.objectweb.asm.commons.SimpleRemapper;
041    
042    import java.io.File;
043    import java.io.FileInputStream;
044    import java.io.FileOutputStream;
045    import java.io.IOException;
046    import java.io.InputStream;
047    import java.io.InputStreamReader;
048    import java.io.StringWriter;
049    import java.util.List;
050    import java.util.Properties;
051    
052    /**
053     * Generates an <code>HelpMojo</code> class.
054     *
055     * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
056     * @version $Id: PluginHelpGenerator.java 1354250 2012-06-26 21:36:53Z hboutemy $
057     * @since 2.4
058     */
059    public class PluginHelpGenerator
060        extends AbstractLogEnabled
061        implements Generator
062    {
063        /**
064         * Default generated class name
065         */
066        private static final String HELP_MOJO_CLASS_NAME = "HelpMojo";
067    
068        /**
069         * Help properties file, to store data about generated source.
070         */
071        private static final String HELP_PROPERTIES_FILENAME = "maven-plugin-help.properties";
072    
073        /**
074         * Default goal
075         */
076        private static final String HELP_GOAL = "help";
077    
078        private String helpPackageName;
079    
080        private VelocityComponent velocityComponent;
081    
082        /**
083         * Default constructor
084         */
085        public PluginHelpGenerator()
086        {
087            this.enableLogging( new ConsoleLogger( Logger.LEVEL_INFO, "PluginHelpGenerator" ) );
088        }
089    
090        // ----------------------------------------------------------------------
091        // Public methods
092        // ----------------------------------------------------------------------
093    
094        /**
095         * {@inheritDoc}
096         */
097        public void execute( File destinationDirectory, PluginToolsRequest request )
098            throws GeneratorException
099        {
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    }