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