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