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 }