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.net.MalformedURLException;
33 import java.net.URL;
34 import java.net.URLClassLoader;
35 import java.nio.charset.StandardCharsets;
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
58
59
60
61
62 public final class GeneratorUtils
63 {
64 private GeneratorUtils()
65 {
66
67 }
68
69
70
71
72
73 public static void writeDependencies( XMLWriter w, PluginDescriptor pluginDescriptor )
74 {
75 w.startElement( "dependencies" );
76
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
116
117
118
119 public static List<ComponentDependency> toComponentDependencies( Collection<Artifact> artifacts )
120 {
121 List<ComponentDependency> componentDeps = new LinkedList<>();
122
123 for ( Artifact artifact : artifacts )
124 {
125 if ( Artifact.SCOPE_PROVIDED.equals( artifact.getScope() ) )
126 {
127 continue;
128 }
129
130 ComponentDependency cd = new ComponentDependency();
131
132 cd.setArtifactId( artifact.getArtifactId() );
133 cd.setGroupId( artifact.getGroupId() );
134 cd.setVersion( artifact.getVersion() );
135 cd.setType( artifact.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
193 @Deprecated
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
266 @Deprecated
267 public static String makeHtmlValid( String description )
268 {
269
270 if ( StringUtils.isEmpty( description ) )
271 {
272 return "";
273 }
274
275 String commentCleaned = decodeJavadocTags( description );
276
277
278 Tidy tidy = new Tidy();
279 tidy.setDocType( "loose" );
280 tidy.setXHTML( true );
281 tidy.setXmlOut( true );
282 tidy.setInputEncoding( "UTF-8" );
283 tidy.setOutputEncoding( "UTF-8" );
284 tidy.setMakeClean( true );
285 tidy.setNumEntities( true );
286 tidy.setQuoteNbsp( false );
287 tidy.setQuiet( true );
288 tidy.setShowWarnings( true );
289
290 ByteArrayOutputStream out = new ByteArrayOutputStream( commentCleaned.length() + 256 );
291 tidy.parse( new ByteArrayInputStream( commentCleaned.getBytes( StandardCharsets.UTF_8 ) ), out );
292 commentCleaned = new String( out.toByteArray(), StandardCharsets.UTF_8 );
293
294 if ( StringUtils.isEmpty( commentCleaned ) )
295 {
296 return "";
297 }
298
299
300 String ls = System.getProperty( "line.separator" );
301 int startPos = commentCleaned.indexOf( "<body>" + ls ) + 6 + ls.length();
302 int endPos = commentCleaned.indexOf( ls + "</body>" );
303 commentCleaned = commentCleaned.substring( startPos, endPos );
304
305 return commentCleaned;
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326 @Deprecated
327 public static String toText( String html )
328 {
329 if ( StringUtils.isEmpty( html ) )
330 {
331 return "";
332 }
333
334 final StringBuilder sb = new StringBuilder();
335
336 HTMLEditorKit.Parser parser = new ParserDelegator();
337 HTMLEditorKit.ParserCallback htmlCallback = new MojoParserCallback( sb );
338
339 try
340 {
341 parser.parse( new StringReader( makeHtmlValid( html ) ), htmlCallback, true );
342 }
343 catch ( IOException e )
344 {
345 throw new RuntimeException( e );
346 }
347
348 return sb.toString().replace( '\"', '\'' );
349 }
350
351
352
353
354 private static class MojoParserCallback
355 extends HTMLEditorKit.ParserCallback
356 {
357
358
359
360 class Counter
361 {
362 int value;
363 }
364
365
366
367
368 private boolean body;
369
370
371
372
373 private int preformatted;
374
375
376
377
378 private int depth;
379
380
381
382
383
384 private Stack<Counter> numbering = new Stack<>();
385
386
387
388
389
390
391 private boolean pendingNewline;
392
393
394
395
396 private boolean simpleTag;
397
398
399
400
401 private final StringBuilder sb;
402
403
404
405
406 MojoParserCallback( StringBuilder sb )
407 {
408 this.sb = sb;
409 }
410
411
412 @Override
413 public void handleSimpleTag( HTML.Tag t, MutableAttributeSet a, int pos )
414 {
415 simpleTag = true;
416 if ( body && HTML.Tag.BR.equals( t ) )
417 {
418 newline( false );
419 }
420 }
421
422
423 @Override
424 public void handleStartTag( HTML.Tag t, MutableAttributeSet a, int pos )
425 {
426 simpleTag = false;
427 if ( body && ( t.breaksFlow() || t.isBlock() ) )
428 {
429 newline( true );
430 }
431 if ( HTML.Tag.OL.equals( t ) )
432 {
433 numbering.push( new Counter() );
434 }
435 else if ( HTML.Tag.UL.equals( t ) )
436 {
437 numbering.push( null );
438 }
439 else if ( HTML.Tag.LI.equals( t ) )
440 {
441 Counter counter = numbering.peek();
442 if ( counter == null )
443 {
444 text( "-\t" );
445 }
446 else
447 {
448 text( ++counter.value + ".\t" );
449 }
450 depth++;
451 }
452 else if ( HTML.Tag.DD.equals( t ) )
453 {
454 depth++;
455 }
456 else if ( t.isPreformatted() )
457 {
458 preformatted++;
459 }
460 else if ( HTML.Tag.BODY.equals( t ) )
461 {
462 body = true;
463 }
464 }
465
466
467 @Override
468 public void handleEndTag( HTML.Tag t, int pos )
469 {
470 if ( HTML.Tag.OL.equals( t ) || HTML.Tag.UL.equals( t ) )
471 {
472 numbering.pop();
473 }
474 else if ( HTML.Tag.LI.equals( t ) || HTML.Tag.DD.equals( t ) )
475 {
476 depth--;
477 }
478 else if ( t.isPreformatted() )
479 {
480 preformatted--;
481 }
482 else if ( HTML.Tag.BODY.equals( t ) )
483 {
484 body = false;
485 }
486 if ( body && ( t.breaksFlow() || t.isBlock() ) && !HTML.Tag.LI.equals( t ) )
487 {
488 if ( ( HTML.Tag.P.equals( t ) || HTML.Tag.PRE.equals( t ) || HTML.Tag.OL.equals( t )
489 || HTML.Tag.UL.equals( t ) || HTML.Tag.DL.equals( t ) )
490 && numbering.isEmpty() )
491 {
492 pendingNewline = false;
493 newline( pendingNewline );
494 }
495 else
496 {
497 newline( true );
498 }
499 }
500 }
501
502
503 @Override
504 public void handleText( char[] data, int pos )
505 {
506
507
508
509
510 int offset = 0;
511 if ( simpleTag && data[0] == '>' )
512 {
513 simpleTag = false;
514 for ( ++offset; offset < data.length && data[offset] <= ' '; )
515 {
516 offset++;
517 }
518 }
519 if ( offset < data.length )
520 {
521 String text = new String( data, offset, data.length - offset );
522 text( text );
523 }
524 }
525
526
527 @Override
528 public void flush()
529 {
530 flushPendingNewline();
531 }
532
533
534
535
536
537
538
539
540 private void newline( boolean implicit )
541 {
542 if ( implicit )
543 {
544 pendingNewline = true;
545 }
546 else
547 {
548 flushPendingNewline();
549 sb.append( '\n' );
550 }
551 }
552
553
554
555
556 private void flushPendingNewline()
557 {
558 if ( pendingNewline )
559 {
560 pendingNewline = false;
561 if ( sb.length() > 0 )
562 {
563 sb.append( '\n' );
564 }
565 }
566 }
567
568
569
570
571
572
573
574 private void text( String data )
575 {
576 flushPendingNewline();
577 if ( sb.length() <= 0 || sb.charAt( sb.length() - 1 ) == '\n' )
578 {
579 for ( int i = 0; i < depth; i++ )
580 {
581 sb.append( '\t' );
582 }
583 }
584 String text;
585 if ( preformatted > 0 )
586 {
587 text = data;
588 }
589 else
590 {
591 text = data.replace( '\n', ' ' );
592 }
593 sb.append( text );
594 }
595 }
596
597
598
599
600
601
602
603 public static String discoverPackageName( PluginDescriptor pluginDescriptor )
604 {
605 Map<String, Integer> packageNames = new HashMap<>();
606
607 List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos();
608 if ( mojoDescriptors == null )
609 {
610 return "";
611 }
612 for ( MojoDescriptor descriptor : mojoDescriptors )
613 {
614
615 String impl = descriptor.getImplementation();
616 if ( StringUtils.equals( descriptor.getGoal(), "help" ) && StringUtils.equals( "HelpMojo", impl ) )
617 {
618 continue;
619 }
620 if ( impl.lastIndexOf( '.' ) != -1 )
621 {
622 String name = impl.substring( 0, impl.lastIndexOf( '.' ) );
623 if ( packageNames.get( name ) != null )
624 {
625 int next = ( packageNames.get( name ) ).intValue() + 1;
626 packageNames.put( name, Integer.valueOf( next ) );
627 }
628 else
629 {
630 packageNames.put( name, Integer.valueOf( 1 ) );
631 }
632 }
633 else
634 {
635 packageNames.put( "", Integer.valueOf( 1 ) );
636 }
637 }
638
639 String packageName = "";
640 int max = 0;
641 for ( Map.Entry<String, Integer> entry : packageNames.entrySet() )
642 {
643 int value = entry.getValue().intValue();
644 if ( value > max )
645 {
646 max = value;
647 packageName = entry.getKey();
648 }
649 }
650
651 return packageName;
652 }
653
654
655
656
657
658
659
660
661 @SuppressWarnings( "unchecked" )
662 public static boolean isMavenReport( String impl, MavenProject project )
663 throws IllegalArgumentException
664 {
665 if ( impl == null )
666 {
667 throw new IllegalArgumentException( "mojo implementation should be declared" );
668 }
669
670 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
671 if ( project != null )
672 {
673 List<String> classPathStrings;
674 try
675 {
676 classPathStrings = project.getCompileClasspathElements();
677 if ( project.getExecutionProject() != null )
678 {
679 classPathStrings.addAll( project.getExecutionProject().getCompileClasspathElements() );
680 }
681 }
682 catch ( DependencyResolutionRequiredException e )
683 {
684 throw new IllegalArgumentException( e );
685 }
686
687 List<URL> urls = new ArrayList<>( classPathStrings.size() );
688 for ( String classPathString : classPathStrings )
689 {
690 try
691 {
692 urls.add( new File( classPathString ).toURL() );
693 }
694 catch ( MalformedURLException e )
695 {
696 throw new IllegalArgumentException( e );
697 }
698 }
699
700 classLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), classLoader );
701 }
702
703 try
704 {
705 Class<?> clazz = Class.forName( impl, false, classLoader );
706
707 return MavenReport.class.isAssignableFrom( clazz );
708 }
709 catch ( ClassNotFoundException e )
710 {
711 return false;
712 }
713 }
714
715 }