1 package org.apache.maven.tools.plugin.generator;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import static java.nio.charset.StandardCharsets.UTF_8;
23
24 import org.apache.maven.plugin.descriptor.MojoDescriptor;
25 import org.apache.maven.plugin.descriptor.PluginDescriptor;
26 import org.apache.maven.plugin.logging.Log;
27 import org.apache.maven.project.MavenProject;
28 import org.apache.maven.tools.plugin.PluginToolsRequest;
29 import org.apache.velocity.VelocityContext;
30 import org.codehaus.plexus.logging.AbstractLogEnabled;
31 import org.codehaus.plexus.logging.Logger;
32 import org.codehaus.plexus.logging.console.ConsoleLogger;
33 import org.codehaus.plexus.util.FileUtils;
34 import org.codehaus.plexus.util.IOUtil;
35 import org.codehaus.plexus.util.PropertyUtils;
36 import org.codehaus.plexus.util.StringUtils;
37 import org.codehaus.plexus.velocity.VelocityComponent;
38 import org.objectweb.asm.ClassReader;
39 import org.objectweb.asm.ClassVisitor;
40 import org.objectweb.asm.ClassWriter;
41 import org.objectweb.asm.commons.ClassRemapper;
42 import org.objectweb.asm.commons.Remapper;
43 import org.objectweb.asm.commons.SimpleRemapper;
44
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.InputStreamReader;
51 import java.io.OutputStreamWriter;
52 import java.io.PrintWriter;
53 import java.io.Reader;
54 import java.io.StringWriter;
55 import java.nio.charset.Charset;
56 import java.util.List;
57 import java.util.Properties;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 public class PluginHelpGenerator
73 extends AbstractLogEnabled
74 implements Generator
75 {
76
77
78
79 private static final String HELP_MOJO_CLASS_NAME = "HelpMojo";
80
81
82
83
84 private static final String HELP_PROPERTIES_FILENAME = "maven-plugin-help.properties";
85
86
87
88
89 private static final String HELP_GOAL = "help";
90
91 private String helpPackageName;
92
93 private boolean useAnnotations;
94
95 private VelocityComponent velocityComponent;
96
97
98
99
100 public PluginHelpGenerator()
101 {
102 this.enableLogging( new ConsoleLogger( Logger.LEVEL_INFO, "PluginHelpGenerator" ) );
103 }
104
105
106
107
108
109
110
111
112 @Override
113 public void execute( File destinationDirectory, PluginToolsRequest request )
114 throws GeneratorException
115 {
116 PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
117
118 String helpImplementation = getImplementation( pluginDescriptor );
119
120 List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos();
121
122 if ( mojoDescriptors != null )
123 {
124
125 MojoDescriptor descriptor = pluginDescriptor.getMojo( HELP_GOAL );
126
127 if ( ( descriptor != null ) && !descriptor.getImplementation().equals( helpImplementation ) )
128 {
129 if ( getLogger().isWarnEnabled() )
130 {
131 getLogger().warn( "\n\nA help goal (" + descriptor.getImplementation()
132 + ") already exists in this plugin. SKIPPED THE " + helpImplementation
133 + " GENERATION.\n" );
134 }
135
136 return;
137 }
138 }
139
140 writeHelpPropertiesFile( request, destinationDirectory );
141
142 useAnnotations = request.getProject().getArtifactMap().containsKey(
143 "org.apache.maven.plugin-tools:maven-plugin-annotations" );
144
145 try
146 {
147 String sourcePath = helpImplementation.replace( '.', File.separatorChar ) + ".java";
148
149 File helpClass = new File( destinationDirectory, sourcePath );
150 helpClass.getParentFile().mkdirs();
151
152 String helpClassSources =
153 getHelpClassSources( getPluginHelpPath( request.getProject() ), pluginDescriptor );
154
155 FileUtils.fileWrite( helpClass, request.getEncoding(), helpClassSources );
156 }
157 catch ( IOException e )
158 {
159 throw new GeneratorException( e.getMessage(), e );
160 }
161 }
162
163 public PluginHelpGenerator setHelpPackageName( String helpPackageName )
164 {
165 this.helpPackageName = helpPackageName;
166 return this;
167 }
168
169 public VelocityComponent getVelocityComponent()
170 {
171 return velocityComponent;
172 }
173
174 public PluginHelpGenerator setVelocityComponent( VelocityComponent velocityComponent )
175 {
176 this.velocityComponent = velocityComponent;
177 return this;
178 }
179
180
181
182
183
184 private String getHelpClassSources( String pluginHelpPath, PluginDescriptor pluginDescriptor )
185 throws IOException
186 {
187 Properties properties = new Properties();
188 VelocityContext context = new VelocityContext( properties );
189 if ( this.helpPackageName != null )
190 {
191 properties.put( "helpPackageName", this.helpPackageName );
192 }
193 else
194 {
195 properties.put( "helpPackageName", "" );
196 }
197 properties.put( "pluginHelpPath", pluginHelpPath );
198 properties.put( "artifactId", pluginDescriptor.getArtifactId() );
199 properties.put( "goalPrefix", pluginDescriptor.getGoalPrefix() );
200 properties.put( "useAnnotations", useAnnotations );
201
202 StringWriter stringWriter = new StringWriter();
203
204
205 try ( InputStream is =
206 Thread.currentThread().getContextClassLoader().getResourceAsStream( "help-class-source.vm" );
207 InputStreamReader isReader = new InputStreamReader( is, UTF_8 ) )
208 {
209
210 velocityComponent.getEngine().evaluate( context, stringWriter, "", isReader );
211 }
212
213
214 return stringWriter.toString().replaceAll( "(\r\n|\n|\r)", System.lineSeparator() );
215 }
216
217
218
219
220
221
222 private String getImplementation( PluginDescriptor pluginDescriptor )
223 {
224 if ( StringUtils.isEmpty( helpPackageName ) )
225 {
226 helpPackageName = GeneratorUtils.discoverPackageName( pluginDescriptor );
227 }
228
229 return StringUtils.isEmpty( helpPackageName )
230 ? HELP_MOJO_CLASS_NAME
231 : helpPackageName + '.' + HELP_MOJO_CLASS_NAME;
232 }
233
234
235
236
237
238
239
240
241 private void writeHelpPropertiesFile( PluginToolsRequest request, File destinationDirectory )
242 throws GeneratorException
243 {
244 Properties properties = new Properties();
245 properties.put( "helpPackageName", helpPackageName == null ? "" : helpPackageName );
246 properties.put( "destinationDirectory", destinationDirectory.getAbsolutePath() );
247
248 File tmpPropertiesFile = new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME );
249
250 if ( tmpPropertiesFile.exists() )
251 {
252 tmpPropertiesFile.delete();
253 }
254 else if ( !tmpPropertiesFile.getParentFile().exists() )
255 {
256 tmpPropertiesFile.getParentFile().mkdirs();
257 }
258
259 try ( FileOutputStream fos = new FileOutputStream( tmpPropertiesFile ) )
260 {
261 properties.store( fos, "maven plugin help mojo generation informations" );
262 }
263 catch ( IOException e )
264 {
265 throw new GeneratorException( e.getMessage(), e );
266 }
267 }
268
269 static String getPluginHelpPath( MavenProject mavenProject )
270 {
271 return mavenProject.getGroupId() + "/" + mavenProject.getArtifactId() + "/plugin-help.xml";
272 }
273
274
275
276
277
278
279
280
281 static void rewriteHelpMojo( PluginToolsRequest request, Log log )
282 throws GeneratorException
283 {
284 File tmpPropertiesFile = new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME );
285
286 if ( !tmpPropertiesFile.exists() )
287 {
288 return;
289 }
290
291 Properties properties;
292 try
293 {
294 properties = PropertyUtils.loadProperties( tmpPropertiesFile );
295 }
296 catch ( IOException e )
297 {
298 throw new GeneratorException( e.getMessage(), e );
299 }
300
301 String helpPackageName = properties.getProperty( "helpPackageName" );
302
303
304 if ( StringUtils.isEmpty( helpPackageName ) )
305 {
306 String destDir = properties.getProperty( "destinationDirectory" );
307 File destinationDirectory;
308 if ( StringUtils.isEmpty( destDir ) )
309 {
310
311 log.warn( "\n\nUnexpected situation: destinationDirectory not defined in " + HELP_PROPERTIES_FILENAME
312 + " during help mojo source generation but expected during XML descriptor generation." );
313 log.warn( "Please check helpmojo goal version used in previous build phase." );
314 log.warn( "If you just upgraded to plugin-tools >= 3.2 you must run a clean build at least once." );
315 destinationDirectory = new File( "target/generated-sources/plugin" );
316 log.warn( "Trying default location: " + destinationDirectory );
317 }
318 else
319 {
320 destinationDirectory = new File( destDir );
321 }
322 String helpMojoImplementation = rewriteHelpClassToMojoPackage( request, destinationDirectory, log );
323
324 if ( helpMojoImplementation != null )
325 {
326
327 updateHelpMojoDescriptor( request.getPluginDescriptor(), helpMojoImplementation );
328 }
329 }
330 }
331
332 private static String rewriteHelpClassToMojoPackage( PluginToolsRequest request, File destinationDirectory,
333 Log log )
334 throws GeneratorException
335 {
336 String destinationPackage = GeneratorUtils.discoverPackageName( request.getPluginDescriptor() );
337 if ( StringUtils.isEmpty( destinationPackage ) )
338 {
339 return null;
340 }
341 String packageAsDirectory = StringUtils.replace( destinationPackage, '.', '/' );
342
343 String outputDirectory = request.getProject().getBuild().getOutputDirectory();
344 File helpClassFile = new File( outputDirectory, HELP_MOJO_CLASS_NAME + ".class" );
345 if ( !helpClassFile.exists() )
346 {
347 return null;
348 }
349
350
351 File helpSourceFile = new File( destinationDirectory, HELP_MOJO_CLASS_NAME + ".java" );
352 if ( !helpSourceFile.exists() )
353 {
354 log.warn( "HelpMojo.java not found in default location: " + helpSourceFile.getAbsolutePath() );
355 log.warn( "Help goal source won't be moved to package: " + destinationPackage );
356 }
357 else
358 {
359 File helpSourceFileNew =
360 new File( destinationDirectory, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME + ".java" );
361 if ( !helpSourceFileNew.getParentFile().exists() )
362 {
363 helpSourceFileNew.getParentFile().mkdirs();
364 }
365 Charset encoding = Charset.forName( request.getEncoding() );
366 try ( Reader sourceReader = new InputStreamReader( new FileInputStream( helpSourceFile ),
367 encoding );
368 PrintWriter sourceWriter = new PrintWriter(
369 new OutputStreamWriter( new FileOutputStream( helpSourceFileNew ),
370 encoding ) ) )
371 {
372 sourceWriter.println( "package " + destinationPackage + ";" );
373 IOUtil.copy( sourceReader, sourceWriter );
374 }
375 catch ( IOException e )
376 {
377 throw new GeneratorException( e.getMessage(), e );
378 }
379 helpSourceFileNew.setLastModified( helpSourceFile.lastModified() );
380 helpSourceFile.delete();
381 }
382
383
384 File rewriteHelpClassFile =
385 new File( outputDirectory + '/' + packageAsDirectory, HELP_MOJO_CLASS_NAME + ".class" );
386 if ( !rewriteHelpClassFile.getParentFile().exists() )
387 {
388 rewriteHelpClassFile.getParentFile().mkdirs();
389 }
390
391 ClassReader cr;
392 try ( FileInputStream fileInputStream = new FileInputStream( helpClassFile ) )
393 {
394 cr = new ClassReader( fileInputStream );
395 }
396 catch ( IOException e )
397 {
398 throw new GeneratorException( e.getMessage(), e );
399 }
400
401 ClassWriter cw = new ClassWriter( 0 );
402
403 Remapper packageRemapper =
404 new SimpleRemapper( HELP_MOJO_CLASS_NAME, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME );
405 ClassVisitor cv = new ClassRemapper( cw, packageRemapper );
406
407 try
408 {
409 cr.accept( cv, ClassReader.EXPAND_FRAMES );
410 }
411 catch ( Throwable e )
412 {
413 throw new GeneratorException( "ASM issue processing class-file " + helpClassFile.getPath(), e );
414 }
415
416 byte[] renamedClass = cw.toByteArray();
417 try ( FileOutputStream fos = new FileOutputStream( rewriteHelpClassFile ) )
418 {
419 fos.write( renamedClass );
420 }
421 catch ( IOException e )
422 {
423 throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e );
424 }
425
426 helpClassFile.delete();
427
428 return destinationPackage + ".HelpMojo";
429 }
430
431 private static void updateHelpMojoDescriptor( PluginDescriptor pluginDescriptor, String helpMojoImplementation )
432 {
433 MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( HELP_GOAL );
434
435 if ( mojoDescriptor != null )
436 {
437 mojoDescriptor.setImplementation( helpMojoImplementation );
438 }
439 }
440 }