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