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