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 java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.StringReader;
27 import java.io.UnsupportedEncodingException;
28 import java.net.MalformedURLException;
29 import java.net.URL;
30 import java.net.URLClassLoader;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Stack;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40 import javax.swing.text.MutableAttributeSet;
41 import javax.swing.text.html.HTML;
42 import javax.swing.text.html.HTMLEditorKit;
43 import javax.swing.text.html.parser.ParserDelegator;
44
45 import org.apache.maven.artifact.DependencyResolutionRequiredException;
46 import org.apache.maven.model.Dependency;
47 import org.apache.maven.plugin.descriptor.MojoDescriptor;
48 import org.apache.maven.plugin.descriptor.PluginDescriptor;
49 import org.apache.maven.project.MavenProject;
50 import org.apache.maven.reporting.MavenReport;
51 import org.codehaus.plexus.component.repository.ComponentDependency;
52 import org.codehaus.plexus.util.StringUtils;
53 import org.codehaus.plexus.util.xml.XMLWriter;
54 import org.w3c.tidy.Tidy;
55
56
57
58
59
60
61 public final class GeneratorUtils
62 {
63 private GeneratorUtils()
64 {
65
66 }
67
68
69
70
71
72 public static void writeDependencies( XMLWriter w, PluginDescriptor pluginDescriptor )
73 {
74 w.startElement( "dependencies" );
75
76 @SuppressWarnings( "unchecked" )
77 List<ComponentDependency> deps = pluginDescriptor.getDependencies();
78 for ( ComponentDependency dep : deps )
79 {
80 w.startElement( "dependency" );
81
82 element( w, "groupId", dep.getGroupId() );
83
84 element( w, "artifactId", dep.getArtifactId() );
85
86 element( w, "type", dep.getType() );
87
88 element( w, "version", dep.getVersion() );
89
90 w.endElement();
91 }
92
93 w.endElement();
94 }
95
96
97
98
99
100
101 public static void element( XMLWriter w, String name, String value )
102 {
103 w.startElement( name );
104
105 if ( value == null )
106 {
107 value = "";
108 }
109
110 w.writeText( value );
111
112 w.endElement();
113 }
114
115 public static void element( XMLWriter w, String name, String value, boolean asText )
116 {
117 element( w, name, asText ? GeneratorUtils.toText( value ) : value );
118 }
119
120
121
122
123
124 public static List<ComponentDependency> toComponentDependencies( List<Dependency> dependencies )
125 {
126 List<ComponentDependency> componentDeps = new LinkedList<>();
127
128 for ( Dependency dependency : dependencies )
129 {
130 ComponentDependency cd = new ComponentDependency();
131
132 cd.setArtifactId( dependency.getArtifactId() );
133 cd.setGroupId( dependency.getGroupId() );
134 cd.setVersion( dependency.getVersion() );
135 cd.setType( dependency.getType() );
136
137 componentDeps.add( cd );
138 }
139
140 return componentDeps;
141 }
142
143
144
145
146
147
148
149
150
151
152
153
154
155 private static String quoteReplacement( String s )
156 {
157 if ( ( s.indexOf( '\\' ) == -1 ) && ( s.indexOf( '$' ) == -1 ) )
158 {
159 return s;
160 }
161
162 StringBuilder sb = new StringBuilder();
163 for ( int i = 0; i < s.length(); i++ )
164 {
165 char c = s.charAt( i );
166 if ( c == '\\' )
167 {
168 sb.append( '\\' );
169 sb.append( '\\' );
170 }
171 else if ( c == '$' )
172 {
173 sb.append( '\\' );
174 sb.append( '$' );
175 }
176 else
177 {
178 sb.append( c );
179 }
180 }
181
182 return sb.toString();
183 }
184
185
186
187
188
189
190
191
192 static String decodeJavadocTags( String description )
193 {
194 if ( StringUtils.isEmpty( description ) )
195 {
196 return "";
197 }
198
199 StringBuffer decoded = new StringBuffer( description.length() + 1024 );
200
201 Matcher matcher = Pattern.compile( "\\{@(\\w+)\\s*([^\\}]*)\\}" ).matcher( description );
202 while ( matcher.find() )
203 {
204 String tag = matcher.group( 1 );
205 String text = matcher.group( 2 );
206 text = StringUtils.replace( text, "&", "&" );
207 text = StringUtils.replace( text, "<", "<" );
208 text = StringUtils.replace( text, ">", ">" );
209 if ( "code".equals( tag ) )
210 {
211 text = "<code>" + text + "</code>";
212 }
213 else if ( "link".equals( tag ) || "linkplain".equals( tag ) || "value".equals( tag ) )
214 {
215 String pattern = "(([^#\\.\\s]+\\.)*([^#\\.\\s]+))?" + "(#([^\\(\\s]*)(\\([^\\)]*\\))?\\s*(\\S.*)?)?";
216 final int label = 7;
217 final int clazz = 3;
218 final int member = 5;
219 final int args = 6;
220 Matcher link = Pattern.compile( pattern ).matcher( text );
221 if ( link.matches() )
222 {
223 text = link.group( label );
224 if ( StringUtils.isEmpty( text ) )
225 {
226 text = link.group( clazz );
227 if ( StringUtils.isEmpty( text ) )
228 {
229 text = "";
230 }
231 if ( StringUtils.isNotEmpty( link.group( member ) ) )
232 {
233 if ( StringUtils.isNotEmpty( text ) )
234 {
235 text += '.';
236 }
237 text += link.group( member );
238 if ( StringUtils.isNotEmpty( link.group( args ) ) )
239 {
240 text += "()";
241 }
242 }
243 }
244 }
245 if ( !"linkplain".equals( tag ) )
246 {
247 text = "<code>" + text + "</code>";
248 }
249 }
250 matcher.appendReplacement( decoded, ( text != null ) ? quoteReplacement( text ) : "" );
251 }
252 matcher.appendTail( decoded );
253
254 return decoded.toString();
255 }
256
257
258
259
260
261
262
263 public static String makeHtmlValid( String description )
264 {
265 if ( StringUtils.isEmpty( description ) )
266 {
267 return "";
268 }
269
270 String commentCleaned = decodeJavadocTags( description );
271
272
273 Tidy tidy = new Tidy();
274 tidy.setDocType( "loose" );
275 tidy.setXHTML( true );
276 tidy.setXmlOut( true );
277 tidy.setInputEncoding( "UTF-8" );
278 tidy.setOutputEncoding( "UTF-8" );
279 tidy.setMakeClean( true );
280 tidy.setNumEntities( true );
281 tidy.setQuoteNbsp( false );
282 tidy.setQuiet( true );
283 tidy.setShowWarnings( false );
284 try
285 {
286 ByteArrayOutputStream out = new ByteArrayOutputStream( commentCleaned.length() + 256 );
287 tidy.parse( new ByteArrayInputStream( commentCleaned.getBytes( "UTF-8" ) ), out );
288 commentCleaned = out.toString( "UTF-8" );
289 }
290 catch ( UnsupportedEncodingException e )
291 {
292
293 }
294
295 if ( StringUtils.isEmpty( commentCleaned ) )
296 {
297 return "";
298 }
299
300
301 String ls = System.getProperty( "line.separator" );
302 int startPos = commentCleaned.indexOf( "<body>" + ls ) + 6 + ls.length();
303 int endPos = commentCleaned.indexOf( ls + "</body>" );
304 commentCleaned = commentCleaned.substring( startPos, endPos );
305
306 return commentCleaned;
307 }
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326 public static String toText( String html )
327 {
328 if ( StringUtils.isEmpty( html ) )
329 {
330 return "";
331 }
332
333 final StringBuilder sb = new StringBuilder();
334
335 HTMLEditorKit.Parser parser = new ParserDelegator();
336 HTMLEditorKit.ParserCallback htmlCallback = new MojoParserCallback( sb );
337
338 try
339 {
340 parser.parse( new StringReader( makeHtmlValid( html ) ), htmlCallback, true );
341 }
342 catch ( IOException e )
343 {
344 throw new RuntimeException( e );
345 }
346
347 return sb.toString().replace( '\"', '\'' );
348 }
349
350
351
352
353 private static class MojoParserCallback
354 extends HTMLEditorKit.ParserCallback
355 {
356
357
358
359 class Counter
360 {
361 int value;
362 }
363
364
365
366
367 private boolean body;
368
369
370
371
372 private int preformatted;
373
374
375
376
377 private int depth;
378
379
380
381
382
383 private Stack<Counter> numbering = new Stack<>();
384
385
386
387
388
389
390 private boolean pendingNewline;
391
392
393
394
395 private boolean simpleTag;
396
397
398
399
400 private final StringBuilder sb;
401
402
403
404
405 MojoParserCallback( StringBuilder sb )
406 {
407 this.sb = sb;
408 }
409
410
411 public void handleSimpleTag( HTML.Tag t, MutableAttributeSet a, int pos )
412 {
413 simpleTag = true;
414 if ( body && HTML.Tag.BR.equals( t ) )
415 {
416 newline( false );
417 }
418 }
419
420
421 public void handleStartTag( HTML.Tag t, MutableAttributeSet a, int pos )
422 {
423 simpleTag = false;
424 if ( body && ( t.breaksFlow() || t.isBlock() ) )
425 {
426 newline( true );
427 }
428 if ( HTML.Tag.OL.equals( t ) )
429 {
430 numbering.push( new Counter() );
431 }
432 else if ( HTML.Tag.UL.equals( t ) )
433 {
434 numbering.push( null );
435 }
436 else if ( HTML.Tag.LI.equals( t ) )
437 {
438 Counter counter = numbering.peek();
439 if ( counter == null )
440 {
441 text( "-\t" );
442 }
443 else
444 {
445 text( ++counter.value + ".\t" );
446 }
447 depth++;
448 }
449 else if ( HTML.Tag.DD.equals( t ) )
450 {
451 depth++;
452 }
453 else if ( t.isPreformatted() )
454 {
455 preformatted++;
456 }
457 else if ( HTML.Tag.BODY.equals( t ) )
458 {
459 body = true;
460 }
461 }
462
463
464 public void handleEndTag( HTML.Tag t, int pos )
465 {
466 if ( HTML.Tag.OL.equals( t ) || HTML.Tag.UL.equals( t ) )
467 {
468 numbering.pop();
469 }
470 else if ( HTML.Tag.LI.equals( t ) || HTML.Tag.DD.equals( t ) )
471 {
472 depth--;
473 }
474 else if ( t.isPreformatted() )
475 {
476 preformatted--;
477 }
478 else if ( HTML.Tag.BODY.equals( t ) )
479 {
480 body = false;
481 }
482 if ( body && ( t.breaksFlow() || t.isBlock() ) && !HTML.Tag.LI.equals( t ) )
483 {
484 if ( ( HTML.Tag.P.equals( t ) || HTML.Tag.PRE.equals( t ) || HTML.Tag.OL.equals( t )
485 || HTML.Tag.UL.equals( t ) || HTML.Tag.DL.equals( t ) )
486 && numbering.isEmpty() )
487 {
488 pendingNewline = false;
489 newline( pendingNewline );
490 }
491 else
492 {
493 newline( true );
494 }
495 }
496 }
497
498
499 public void handleText( char[] data, int pos )
500 {
501
502
503
504
505 int offset = 0;
506 if ( simpleTag && data[0] == '>' )
507 {
508 simpleTag = false;
509 for ( ++offset; offset < data.length && data[offset] <= ' '; )
510 {
511 offset++;
512 }
513 }
514 if ( offset < data.length )
515 {
516 String text = new String( data, offset, data.length - offset );
517 text( text );
518 }
519 }
520
521
522 public void flush()
523 {
524 flushPendingNewline();
525 }
526
527
528
529
530
531
532
533
534 private void newline( boolean implicit )
535 {
536 if ( implicit )
537 {
538 pendingNewline = true;
539 }
540 else
541 {
542 flushPendingNewline();
543 sb.append( '\n' );
544 }
545 }
546
547
548
549
550 private void flushPendingNewline()
551 {
552 if ( pendingNewline )
553 {
554 pendingNewline = false;
555 if ( sb.length() > 0 )
556 {
557 sb.append( '\n' );
558 }
559 }
560 }
561
562
563
564
565
566
567
568 private void text( String data )
569 {
570 flushPendingNewline();
571 if ( sb.length() <= 0 || sb.charAt( sb.length() - 1 ) == '\n' )
572 {
573 for ( int i = 0; i < depth; i++ )
574 {
575 sb.append( '\t' );
576 }
577 }
578 String text;
579 if ( preformatted > 0 )
580 {
581 text = data;
582 }
583 else
584 {
585 text = data.replace( '\n', ' ' );
586 }
587 sb.append( text );
588 }
589 }
590
591
592
593
594
595
596
597 public static String discoverPackageName( PluginDescriptor pluginDescriptor )
598 {
599 Map<String, Integer> packageNames = new HashMap<>();
600
601 List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos();
602 if ( mojoDescriptors == null )
603 {
604 return "";
605 }
606 for ( MojoDescriptor descriptor : mojoDescriptors )
607 {
608
609 String impl = descriptor.getImplementation();
610 if ( StringUtils.equals( descriptor.getGoal(), "help" ) && StringUtils.equals( "HelpMojo", impl ) )
611 {
612 continue;
613 }
614 if ( impl.lastIndexOf( '.' ) != -1 )
615 {
616 String name = impl.substring( 0, impl.lastIndexOf( '.' ) );
617 if ( packageNames.get( name ) != null )
618 {
619 int next = ( packageNames.get( name ) ).intValue() + 1;
620 packageNames.put( name, Integer.valueOf( next ) );
621 }
622 else
623 {
624 packageNames.put( name, Integer.valueOf( 1 ) );
625 }
626 }
627 else
628 {
629 packageNames.put( "", Integer.valueOf( 1 ) );
630 }
631 }
632
633 String packageName = "";
634 int max = 0;
635 for ( Map.Entry<String, Integer> entry : packageNames.entrySet() )
636 {
637 int value = entry.getValue().intValue();
638 if ( value > max )
639 {
640 max = value;
641 packageName = entry.getKey();
642 }
643 }
644
645 return packageName;
646 }
647
648
649
650
651
652
653
654
655 @SuppressWarnings( "unchecked" )
656 public static boolean isMavenReport( String impl, MavenProject project )
657 throws IllegalArgumentException
658 {
659 if ( impl == null )
660 {
661 throw new IllegalArgumentException( "mojo implementation should be declared" );
662 }
663
664 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
665 if ( project != null )
666 {
667 List<String> classPathStrings;
668 try
669 {
670 classPathStrings = project.getCompileClasspathElements();
671 if ( project.getExecutionProject() != null )
672 {
673 classPathStrings.addAll( project.getExecutionProject().getCompileClasspathElements() );
674 }
675 }
676 catch ( DependencyResolutionRequiredException e )
677 {
678 throw new IllegalArgumentException( e );
679 }
680
681 List<URL> urls = new ArrayList<>( classPathStrings.size() );
682 for ( String classPathString : classPathStrings )
683 {
684 try
685 {
686 urls.add( new File( classPathString ).toURL() );
687 }
688 catch ( MalformedURLException e )
689 {
690 throw new IllegalArgumentException( e );
691 }
692 }
693
694 classLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), classLoader );
695 }
696
697 try
698 {
699 Class<?> clazz = Class.forName( impl, false, classLoader );
700
701 return MavenReport.class.isAssignableFrom( clazz );
702 }
703 catch ( ClassNotFoundException e )
704 {
705 return false;
706 }
707 }
708
709 }