1 package org.apache.maven.plugins.javadoc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import com.thoughtworks.qdox.JavaProjectBuilder;
23 import com.thoughtworks.qdox.library.ClassLibraryBuilder;
24 import com.thoughtworks.qdox.library.OrderedClassLibraryBuilder;
25 import com.thoughtworks.qdox.model.DocletTag;
26 import com.thoughtworks.qdox.model.JavaAnnotatedElement;
27 import com.thoughtworks.qdox.model.JavaAnnotation;
28 import com.thoughtworks.qdox.model.JavaClass;
29 import com.thoughtworks.qdox.model.JavaConstructor;
30 import com.thoughtworks.qdox.model.JavaExecutable;
31 import com.thoughtworks.qdox.model.JavaField;
32 import com.thoughtworks.qdox.model.JavaGenericDeclaration;
33 import com.thoughtworks.qdox.model.JavaMember;
34 import com.thoughtworks.qdox.model.JavaMethod;
35 import com.thoughtworks.qdox.model.JavaParameter;
36 import com.thoughtworks.qdox.model.JavaType;
37 import com.thoughtworks.qdox.model.JavaTypeVariable;
38 import com.thoughtworks.qdox.parser.ParseException;
39 import com.thoughtworks.qdox.type.TypeResolver;
40
41 import org.apache.commons.io.IOUtils;
42 import org.apache.commons.lang3.ClassUtils;
43 import org.apache.maven.artifact.Artifact;
44 import org.apache.maven.artifact.DependencyResolutionRequiredException;
45 import org.apache.maven.artifact.repository.ArtifactRepository;
46 import org.apache.maven.plugin.AbstractMojo;
47 import org.apache.maven.plugin.MojoExecutionException;
48 import org.apache.maven.plugin.MojoFailureException;
49 import org.apache.maven.plugins.annotations.Component;
50 import org.apache.maven.plugins.annotations.Parameter;
51 import org.apache.maven.project.MavenProject;
52 import org.apache.maven.settings.Settings;
53 import org.apache.maven.shared.invoker.MavenInvocationException;
54 import org.codehaus.plexus.components.interactivity.InputHandler;
55 import org.codehaus.plexus.languages.java.version.JavaVersion;
56 import org.codehaus.plexus.util.FileUtils;
57 import org.codehaus.plexus.util.IOUtil;
58 import org.codehaus.plexus.util.ReaderFactory;
59 import org.codehaus.plexus.util.StringUtils;
60
61 import java.io.BufferedReader;
62 import java.io.File;
63 import java.io.IOException;
64 import java.io.InputStream;
65 import java.io.StringReader;
66 import java.io.StringWriter;
67 import java.lang.reflect.Method;
68 import java.net.MalformedURLException;
69 import java.net.URISyntaxException;
70 import java.net.URL;
71 import java.net.URLClassLoader;
72 import java.nio.file.Paths;
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.Collection;
76 import java.util.Collections;
77 import java.util.HashSet;
78 import java.util.Iterator;
79 import java.util.LinkedHashMap;
80 import java.util.LinkedList;
81 import java.util.List;
82 import java.util.Locale;
83 import java.util.Map;
84 import java.util.Properties;
85 import java.util.Set;
86 import java.util.StringTokenizer;
87 import java.util.regex.Matcher;
88 import java.util.regex.Pattern;
89
90
91
92
93
94
95
96
97
98
99 public abstract class AbstractFixJavadocMojo
100 extends AbstractMojo
101 {
102
103
104
105 private static final String EOL = System.getProperty( "line.separator" );
106
107
108
109
110 private static final String AUTHOR_TAG = "author";
111
112
113
114
115 private static final String VERSION_TAG = "version";
116
117
118
119
120 private static final String SINCE_TAG = "since";
121
122
123
124
125 private static final String PARAM_TAG = "param";
126
127
128
129
130 private static final String RETURN_TAG = "return";
131
132
133
134
135 private static final String THROWS_TAG = "throws";
136
137
138
139
140 private static final String LINK_TAG = "link";
141
142
143
144
145 private static final String INHERITED_TAG = "{@inheritDoc}";
146
147
148
149
150 private static final String START_JAVADOC = "/**";
151
152
153
154
155 private static final String END_JAVADOC = "*/";
156
157
158
159
160 private static final String SEPARATOR_JAVADOC = " * ";
161
162
163
164
165 private static final String INHERITED_JAVADOC = START_JAVADOC + " " + INHERITED_TAG + " " + END_JAVADOC;
166
167
168
169
170 private static final String FIX_TAGS_ALL = "all";
171
172
173
174
175 private static final String LEVEL_PUBLIC = "public";
176
177
178
179
180 private static final String LEVEL_PROTECTED = "protected";
181
182
183
184
185 private static final String LEVEL_PACKAGE = "package";
186
187
188
189
190 private static final String LEVEL_PRIVATE = "private";
191
192
193
194
195 private static final String CLIRR_MAVEN_PLUGIN_GROUPID = "org.codehaus.mojo";
196
197
198
199
200 private static final String CLIRR_MAVEN_PLUGIN_ARTIFACTID = "clirr-maven-plugin";
201
202
203
204
205 private static final String CLIRR_MAVEN_PLUGIN_VERSION = "2.2.2";
206
207
208
209
210 private static final String CLIRR_MAVEN_PLUGIN_GOAL = "check";
211
212
213
214
215 public static final String JAVA_FILES = "**\\/*.java";
216
217
218
219
220 public static final String DEFAULT_VERSION_VALUE = "\u0024Id: \u0024Id";
221
222
223
224
225
226
227
228
229 @Component
230 private InputHandler inputHandler;
231
232
233
234
235
236
237
238
239
240
241
242 @Parameter ( property = "comparisonVersion", defaultValue = "(,${project.version})" )
243 private String comparisonVersion;
244
245
246
247
248
249
250 @Parameter ( property = "defaultAuthor" )
251 private String defaultAuthor;
252
253
254
255
256 @Parameter ( property = "defaultSince", defaultValue = "${project.version}" )
257 private String defaultSince;
258
259
260
261
262
263
264
265
266 @Parameter ( property = "defaultVersion", defaultValue = DEFAULT_VERSION_VALUE )
267 private String defaultVersion = "\u0024Id: \u0024";
268
269
270
271
272
273 @Parameter ( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
274 private String encoding;
275
276
277
278
279 @Parameter ( property = "excludes" )
280 private String excludes;
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 @Parameter ( property = "fixTags", defaultValue = "all" )
297 private String fixTags;
298
299
300
301
302 @Parameter ( property = "fixClassComment", defaultValue = "true" )
303 private boolean fixClassComment;
304
305
306
307
308 @Parameter ( property = "fixFieldComment", defaultValue = "true" )
309 private boolean fixFieldComment;
310
311
312
313
314 @Parameter ( property = "fixMethodComment", defaultValue = "true" )
315 private boolean fixMethodComment;
316
317
318
319
320
321
322 @Parameter ( property = "removeUnknownThrows", defaultValue = "true" )
323 private boolean removeUnknownThrows;
324
325
326
327
328 @Parameter ( property = "force" )
329 private boolean force;
330
331
332
333
334 @Parameter ( property = "ignoreClirr", defaultValue = "false" )
335 protected boolean ignoreClirr;
336
337
338
339
340
341
342 @Parameter ( property = "includes", defaultValue = JAVA_FILES )
343 private String includes;
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359 @Parameter ( property = "level", defaultValue = "protected" )
360 private String level;
361
362
363
364
365 @Parameter ( property = "localRepository" )
366 private ArtifactRepository localRepository;
367
368
369
370
371 @Parameter ( property = "outputDirectory", defaultValue = "${project.build.sourceDirectory}" )
372 private File outputDirectory;
373
374
375
376
377 @Parameter( defaultValue = "${project}", readonly = true, required = true )
378 private MavenProject project;
379
380
381
382
383 @Parameter( defaultValue = "${settings}", readonly = true, required = true )
384 private Settings settings;
385
386
387
388
389
390
391
392
393 private ClassLoader projectClassLoader;
394
395
396
397
398
399
400 private String[] fixTagsSplitted;
401
402
403
404
405 private List<String> clirrNewClasses;
406
407
408
409
410 private Map<String, List<String>> clirrNewMethods;
411
412
413
414
415 private List<String> sinceClasses;
416
417
418
419
420 @Override
421 public void execute()
422 throws MojoExecutionException, MojoFailureException
423 {
424 if ( !fixClassComment && !fixFieldComment && !fixMethodComment )
425 {
426 getLog().info( "Specified to NOT fix classes, fields and methods. Nothing to do." );
427 return;
428 }
429
430
431 init();
432
433 if ( fixTagsSplitted.length == 0 )
434 {
435 getLog().info( "No fix tag specified. Nothing to do." );
436 return;
437 }
438
439
440 if ( !preCheck() )
441 {
442 return;
443 }
444
445
446 try
447 {
448 executeClirr();
449 }
450 catch ( MavenInvocationException e )
451 {
452 if ( getLog().isDebugEnabled() )
453 {
454 getLog().error( "MavenInvocationException: " + e.getMessage(), e );
455 }
456 else
457 {
458 getLog().error( "MavenInvocationException: " + e.getMessage() );
459 }
460 getLog().info( "Clirr is ignored." );
461 }
462
463
464 try
465 {
466 Collection<JavaClass> javaClasses = getQdoxClasses();
467
468 if ( javaClasses != null )
469 {
470 for ( JavaClass javaClass : javaClasses )
471 {
472 processFix( javaClass );
473 }
474 }
475 }
476 catch ( IOException e )
477 {
478 throw new MojoExecutionException( "IOException: " + e.getMessage(), e );
479 }
480 }
481
482
483
484
485
486
487
488
489
490 protected String getArtifactType( MavenProject p )
491 {
492 return p.getArtifact().getType();
493 }
494
495
496
497
498
499 protected List<String> getProjectSourceRoots( MavenProject p )
500 {
501 return ( p.getCompileSourceRoots() == null
502 ? Collections.<String>emptyList()
503 : new LinkedList<>( p.getCompileSourceRoots() ) );
504 }
505
506
507
508
509
510
511
512 protected List<String> getCompileClasspathElements( MavenProject p )
513 throws DependencyResolutionRequiredException
514 {
515 return ( p.getCompileClasspathElements() == null
516 ? Collections.<String>emptyList()
517 : new LinkedList<>( p.getCompileClasspathElements() ) );
518 }
519
520
521
522
523
524 protected static String getJavaMethodAsString( JavaExecutable javaExecutable )
525 {
526 return javaExecutable.getDeclaringClass().getFullyQualifiedName() + "#" + javaExecutable.getCallSignature();
527 }
528
529
530
531
532
533
534
535
536 private void init()
537 {
538
539 if ( StringUtils.isEmpty( defaultAuthor ) )
540 {
541 defaultAuthor = System.getProperty( "user.name" );
542 }
543
544
545 int i = defaultSince.indexOf( "-" + Artifact.SNAPSHOT_VERSION );
546 if ( i != -1 )
547 {
548 defaultSince = defaultSince.substring( 0, i );
549 }
550
551
552 if ( !FIX_TAGS_ALL.equalsIgnoreCase( fixTags.trim() ) )
553 {
554 String[] split = StringUtils.split( fixTags, "," );
555 List<String> filtered = new LinkedList<>();
556 for ( String aSplit : split )
557 {
558 String s = aSplit.trim();
559 if ( JavadocUtil.equalsIgnoreCase( s, FIX_TAGS_ALL, AUTHOR_TAG, VERSION_TAG, SINCE_TAG, PARAM_TAG,
560 THROWS_TAG, LINK_TAG, RETURN_TAG ) )
561 {
562 filtered.add( s );
563 }
564 else
565 {
566 if ( getLog().isWarnEnabled() )
567 {
568 getLog().warn( "Unrecognized '" + s + "' for fixTags parameter. Ignored it!" );
569 }
570 }
571 }
572 fixTags = StringUtils.join( filtered.iterator(), "," );
573 }
574 fixTagsSplitted = StringUtils.split( fixTags, "," );
575
576
577 if ( StringUtils.isEmpty( encoding ) )
578 {
579 if ( getLog().isWarnEnabled() )
580 {
581 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
582 + ", i.e. build is platform dependent!" );
583 }
584 encoding = ReaderFactory.FILE_ENCODING;
585 }
586
587
588 level = level.trim();
589 if ( !JavadocUtil.equalsIgnoreCase( level, LEVEL_PUBLIC, LEVEL_PROTECTED, LEVEL_PACKAGE, LEVEL_PRIVATE ) )
590 {
591 if ( getLog().isWarnEnabled() )
592 {
593 getLog().warn( "Unrecognized '" + level + "' for level parameter, using 'protected' level." );
594 }
595 level = "protected";
596 }
597 }
598
599
600
601
602
603 private boolean preCheck()
604 throws MojoExecutionException
605 {
606 if ( force )
607 {
608 return true;
609 }
610
611 if ( outputDirectory != null && !outputDirectory.getAbsolutePath().equals(
612 getProjectSourceDirectory().getAbsolutePath() ) )
613 {
614 return true;
615 }
616
617 if ( !settings.isInteractiveMode() )
618 {
619 getLog().error( "Maven is not attempt to interact with the user for input. "
620 + "Verify the <interactiveMode/> configuration in your settings." );
621 return false;
622 }
623
624 getLog().warn( "" );
625 getLog().warn( " WARRANTY DISCLAIMER" );
626 getLog().warn( "" );
627 getLog().warn( "All warranties with regard to this Maven goal are disclaimed!" );
628 getLog().warn( "The changes will be done directly in the source code." );
629 getLog().warn( "The Maven Team strongly recommends the use of a SCM software BEFORE executing this goal." );
630 getLog().warn( "" );
631
632 while ( true )
633 {
634 getLog().info( "Are you sure to proceed? [Y]es [N]o" );
635
636 try
637 {
638 String userExpression = inputHandler.readLine();
639 if ( userExpression == null || JavadocUtil.equalsIgnoreCase( userExpression, "Y", "Yes" ) )
640 {
641 getLog().info( "OK, let's proceed..." );
642 break;
643 }
644 if ( JavadocUtil.equalsIgnoreCase( userExpression, "N", "No" ) )
645 {
646 getLog().info( "No changes in your sources occur." );
647 return false;
648 }
649 }
650 catch ( IOException e )
651 {
652 throw new MojoExecutionException( "Unable to read from standard input.", e );
653 }
654 }
655
656 return true;
657 }
658
659
660
661
662 private File getProjectSourceDirectory()
663 {
664 return new File( project.getBuild().getSourceDirectory() );
665 }
666
667
668
669
670
671
672 private void executeClirr()
673 throws MavenInvocationException
674 {
675 if ( ignoreClirr )
676 {
677 getLog().info( "Clirr is ignored." );
678 return;
679 }
680
681 String clirrGoal = getFullClirrGoal();
682
683
684 File clirrTextOutputFile =
685 FileUtils.createTempFile( "clirr", ".txt", new File( project.getBuild().getDirectory() ) );
686 Properties properties = new Properties();
687 properties.put( "textOutputFile", clirrTextOutputFile.getAbsolutePath() );
688 properties.put( "comparisonVersion", comparisonVersion );
689 properties.put( "failOnError", "false" );
690 if ( JavaVersion.JAVA_SPECIFICATION_VERSION.isBefore( "8" ) )
691 {
692
693 properties.put( "https.protocols", "TLSv1.2" );
694 }
695
696 File invokerDir = new File( project.getBuild().getDirectory(), "invoker" );
697 invokerDir.mkdirs();
698 File invokerLogFile = FileUtils.createTempFile( "clirr-maven-plugin", ".txt", invokerDir );
699 new File( project.getBuild().getDirectory(), "invoker-clirr-maven-plugin.txt" );
700 JavadocUtil.invokeMaven( getLog(), new File( localRepository.getBasedir() ), project.getFile(),
701 Collections.singletonList( clirrGoal ), properties, invokerLogFile );
702
703 try
704 {
705 if ( invokerLogFile.exists() )
706 {
707 String invokerLogContent =
708 StringUtils.unifyLineSeparators( FileUtils.fileRead( invokerLogFile, "UTF-8" ) );
709
710 final String artifactNotFoundMsg = "Unable to find a previous version of the project in the repository";
711 if ( invokerLogContent.contains( artifactNotFoundMsg ) )
712 {
713 getLog().warn( "No previous artifact has been deployed, Clirr is ignored." );
714 return;
715 }
716 }
717 }
718 catch ( IOException e )
719 {
720 getLog().debug( "IOException: " + e.getMessage() );
721 }
722
723 try
724 {
725 parseClirrTextOutputFile( clirrTextOutputFile );
726 }
727 catch ( IOException e )
728 {
729 if ( getLog().isDebugEnabled() )
730 {
731 getLog().debug( "IOException: " + e.getMessage(), e );
732 }
733 getLog().info( "IOException when parsing Clirr output '" + clirrTextOutputFile.getAbsolutePath()
734 + "', Clirr is ignored." );
735 }
736 }
737
738
739
740
741
742 private void parseClirrTextOutputFile( File clirrTextOutputFile )
743 throws IOException
744 {
745 if ( !clirrTextOutputFile.exists() )
746 {
747 if ( getLog().isInfoEnabled() )
748 {
749 getLog().info(
750 "No Clirr output file '" + clirrTextOutputFile.getAbsolutePath() + "' exists, Clirr is ignored." );
751 }
752 return;
753 }
754
755 if ( getLog().isInfoEnabled() )
756 {
757 getLog().info( "Clirr output file was created: " + clirrTextOutputFile.getAbsolutePath() );
758 }
759
760 clirrNewClasses = new LinkedList<>();
761 clirrNewMethods = new LinkedHashMap<>();
762
763 BufferedReader reader = null;
764 try
765 {
766 reader = new BufferedReader( ReaderFactory.newReader( clirrTextOutputFile, "UTF-8" ) );
767
768 for ( String line = reader.readLine(); line != null; line = reader.readLine() )
769 {
770 String[] split = StringUtils.split( line, ":" );
771 if ( split.length != 4 )
772 {
773 if ( getLog().isDebugEnabled() )
774 {
775 getLog().debug( "Unable to parse the clirr line: " + line );
776 }
777 continue;
778 }
779
780 int code;
781 try
782 {
783 code = Integer.parseInt( split[1].trim() );
784 }
785 catch ( NumberFormatException e )
786 {
787 if ( getLog().isDebugEnabled() )
788 {
789 getLog().debug( "Unable to parse the clirr line: " + line );
790 }
791 continue;
792 }
793
794
795
796
797
798 List<String> list;
799 String[] splits2;
800
801 switch ( code )
802 {
803 case 7011:
804 list = clirrNewMethods.get( split[2].trim() );
805 if ( list == null )
806 {
807 list = new ArrayList<>();
808 }
809 splits2 = StringUtils.split( split[3].trim(), "'" );
810 if ( splits2.length != 3 )
811 {
812 continue;
813 }
814 list.add( splits2[1].trim() );
815 clirrNewMethods.put( split[2].trim(), list );
816 break;
817
818 case 7012:
819 list = clirrNewMethods.get( split[2].trim() );
820 if ( list == null )
821 {
822 list = new ArrayList<>();
823 }
824 splits2 = StringUtils.split( split[3].trim(), "'" );
825 if ( splits2.length != 3 )
826 {
827 continue;
828 }
829 list.add( splits2[1].trim() );
830 clirrNewMethods.put( split[2].trim(), list );
831 break;
832
833 case 8000:
834 clirrNewClasses.add( split[2].trim() );
835 break;
836 default:
837 break;
838 }
839
840 }
841
842 reader.close();
843 reader = null;
844 }
845 finally
846 {
847 IOUtils.closeQuietly( reader );
848 }
849 if ( clirrNewClasses.isEmpty() && clirrNewMethods.isEmpty() )
850 {
851 getLog().info( "Clirr NOT found API differences." );
852 }
853 else
854 {
855 getLog().info( "Clirr found API differences, i.e. new classes/interfaces or methods." );
856 }
857 }
858
859
860
861
862
863 private boolean fixTag( String tag )
864 {
865 if ( fixTagsSplitted.length == 1 && fixTagsSplitted[0].equals( FIX_TAGS_ALL ) )
866 {
867 return true;
868 }
869
870 for ( String aFixTagsSplitted : fixTagsSplitted )
871 {
872 if ( aFixTagsSplitted.trim().equals( tag ) )
873 {
874 return true;
875 }
876 }
877
878 return false;
879 }
880
881
882
883
884
885
886
887
888
889 private Collection<JavaClass> getQdoxClasses()
890 throws IOException, MojoExecutionException
891 {
892 if ( "pom".equalsIgnoreCase( project.getPackaging() ) )
893 {
894 getLog().warn( "This project has 'pom' packaging, no Java sources is available." );
895 return null;
896 }
897
898 List<File> javaFiles = new LinkedList<>();
899 for ( String sourceRoot : getProjectSourceRoots( project ) )
900 {
901 File f = new File( sourceRoot );
902 if ( f.isDirectory() )
903 {
904 javaFiles.addAll( FileUtils.getFiles( f, includes, excludes, true ) );
905 }
906 else
907 {
908 if ( getLog().isWarnEnabled() )
909 {
910 getLog().warn( f + " doesn't exist. Ignored it." );
911 }
912 }
913 }
914
915 ClassLibraryBuilder classLibraryBuilder = new OrderedClassLibraryBuilder();
916 classLibraryBuilder.appendClassLoader( getProjectClassLoader() );
917
918 JavaProjectBuilder builder = new JavaProjectBuilder( classLibraryBuilder );
919 builder.setEncoding( encoding );
920 for ( File f : javaFiles )
921 {
922 if ( !f.getAbsolutePath().toLowerCase( Locale.ENGLISH ).endsWith( ".java" ) && getLog().isWarnEnabled() )
923 {
924 getLog().warn( "'" + f + "' is not a Java file. Ignored it." );
925 continue;
926 }
927
928 try
929 {
930 builder.addSource( f );
931 }
932 catch ( ParseException e )
933 {
934 if ( getLog().isWarnEnabled() )
935 {
936 getLog().warn( "QDOX ParseException: " + e.getMessage() + ". Can't fix it." );
937 }
938 }
939 }
940
941 return builder.getClasses();
942 }
943
944
945
946
947
948 private ClassLoader getProjectClassLoader()
949 throws MojoExecutionException
950 {
951 if ( projectClassLoader == null )
952 {
953 List<String> classPath;
954 try
955 {
956 classPath = getCompileClasspathElements( project );
957 }
958 catch ( DependencyResolutionRequiredException e )
959 {
960 throw new MojoExecutionException( "DependencyResolutionRequiredException: " + e.getMessage(), e );
961 }
962
963 List<URL> urls = new ArrayList<>( classPath.size() );
964 for ( String filename : classPath )
965 {
966 try
967 {
968 urls.add( new File( filename ).toURL() );
969 }
970 catch ( MalformedURLException e )
971 {
972 throw new MojoExecutionException( "MalformedURLException: " + e.getMessage(), e );
973 }
974 }
975
976 projectClassLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null );
977 }
978
979 return projectClassLoader;
980 }
981
982
983
984
985
986
987
988
989 private void processFix( JavaClass javaClass )
990 throws IOException, MojoExecutionException
991 {
992
993 if ( javaClass.isInner() )
994 {
995 return;
996 }
997
998 File javaFile;
999 try
1000 {
1001 javaFile = Paths.get( javaClass.getSource().getURL().toURI() ).toFile();
1002 }
1003 catch ( URISyntaxException e )
1004 {
1005 throw new MojoExecutionException( e.getMessage() );
1006 }
1007
1008
1009 final String originalContent = StringUtils.unifyLineSeparators( FileUtils.fileRead( javaFile, encoding ) );
1010
1011 if ( getLog().isDebugEnabled() )
1012 {
1013 getLog().debug( "Analyzing " + javaClass.getFullyQualifiedName() );
1014 }
1015
1016 final StringWriter stringWriter = new StringWriter();
1017 BufferedReader reader = null;
1018 boolean changeDetected = false;
1019 try
1020 {
1021 reader = new BufferedReader( new StringReader( originalContent ) );
1022
1023 int lineNumber = 0;
1024 for ( String line = reader.readLine(); line != null; line = reader.readLine() )
1025 {
1026 lineNumber++;
1027 final String indent = autodetectIndentation( line );
1028
1029
1030 if ( javaClass.getComment() == null && javaClass.getAnnotations() != null
1031 && !javaClass.getAnnotations().isEmpty() )
1032 {
1033 if ( lineNumber == javaClass.getAnnotations().get( 0 ).getLineNumber() )
1034 {
1035 changeDetected |= fixClassComment( stringWriter, originalContent, javaClass, indent );
1036
1037 takeCareSingleComment( stringWriter, originalContent, javaClass );
1038 }
1039 }
1040 else if ( lineNumber == javaClass.getLineNumber() )
1041 {
1042 changeDetected |= fixClassComment( stringWriter, originalContent, javaClass, indent );
1043
1044 takeCareSingleComment( stringWriter, originalContent, javaClass );
1045 }
1046
1047
1048 if ( javaClass.getFields() != null )
1049 {
1050 for ( JavaField field : javaClass.getFields() )
1051 {
1052 if ( lineNumber == field.getLineNumber() )
1053 {
1054 changeDetected |= fixFieldComment( stringWriter, javaClass, field, indent );
1055 }
1056 }
1057 }
1058
1059
1060 if ( javaClass.getConstructors() != null )
1061 {
1062 for ( JavaConstructor method : javaClass.getConstructors() )
1063 {
1064 if ( lineNumber == method.getLineNumber() )
1065 {
1066 changeDetected |= fixMethodComment( stringWriter, originalContent, method, indent );
1067
1068 takeCareSingleComment( stringWriter, originalContent, method );
1069 }
1070 }
1071 }
1072
1073
1074
1075 for ( JavaMethod method : javaClass.getMethods() )
1076 {
1077 int methodLineNumber;
1078 if ( method.getComment() == null && !method.getAnnotations().isEmpty() )
1079 {
1080 methodLineNumber = method.getAnnotations().get( 0 ).getLineNumber();
1081 }
1082 else
1083 {
1084 methodLineNumber = method.getLineNumber();
1085 }
1086
1087 if ( lineNumber == methodLineNumber )
1088 {
1089 changeDetected |= fixMethodComment( stringWriter, originalContent, method, indent );
1090
1091 takeCareSingleComment( stringWriter, originalContent, method );
1092 }
1093 }
1094
1095 stringWriter.write( line );
1096 stringWriter.write( EOL );
1097 }
1098
1099 reader.close();
1100 reader = null;
1101 }
1102 finally
1103 {
1104 IOUtil.close( reader );
1105 }
1106
1107 if ( changeDetected )
1108 {
1109 if ( getLog().isInfoEnabled() )
1110 {
1111 getLog().info( "Saving changes to " + javaClass.getFullyQualifiedName() );
1112 }
1113
1114 if ( outputDirectory != null && !outputDirectory.getAbsolutePath().equals(
1115 getProjectSourceDirectory().getAbsolutePath() ) )
1116 {
1117 String path = StringUtils.replace( javaFile.getAbsolutePath().replaceAll( "\\\\", "/" ),
1118 project.getBuild().getSourceDirectory().replaceAll( "\\\\", "/" ), "" );
1119 javaFile = new File( outputDirectory, path );
1120 javaFile.getParentFile().mkdirs();
1121 }
1122 writeFile( javaFile, encoding, stringWriter.toString() );
1123 }
1124 else
1125 {
1126 if ( getLog().isDebugEnabled() )
1127 {
1128 getLog().debug( "No changes made to " + javaClass.getFullyQualifiedName() );
1129 }
1130 }
1131 }
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163 private void takeCareSingleComment( final StringWriter stringWriter, final String originalContent,
1164 final JavaAnnotatedElement entity )
1165 throws IOException
1166 {
1167 if ( entity.getComment() == null )
1168 {
1169 return;
1170 }
1171
1172 String javadocComment = trimRight( extractOriginalJavadoc( originalContent, entity ) );
1173 String extraComment = javadocComment.substring( javadocComment.indexOf( END_JAVADOC ) + END_JAVADOC.length() );
1174 if ( StringUtils.isNotEmpty( extraComment ) )
1175 {
1176 if ( extraComment.contains( EOL ) )
1177 {
1178 stringWriter.write( extraComment.substring( extraComment.indexOf( EOL ) + EOL.length() ) );
1179 }
1180 else
1181 {
1182 stringWriter.write( extraComment );
1183 }
1184 stringWriter.write( EOL );
1185 }
1186 }
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199 private boolean fixClassComment( final StringWriter stringWriter, final String originalContent,
1200 final JavaClass javaClass, final String indent )
1201 throws MojoExecutionException, IOException
1202 {
1203 if ( !fixClassComment )
1204 {
1205 return false;
1206 }
1207
1208 if ( !isInLevel( javaClass.getModifiers() ) )
1209 {
1210 return false;
1211 }
1212
1213
1214 if ( javaClass.getComment() == null )
1215 {
1216 addDefaultClassComment( stringWriter, javaClass, indent );
1217 return true;
1218 }
1219
1220
1221 return updateEntityComment( stringWriter, originalContent, javaClass, indent );
1222 }
1223
1224
1225
1226
1227
1228 private boolean isInLevel( List<String> modifiers )
1229 {
1230 if ( LEVEL_PUBLIC.equalsIgnoreCase( level.trim() ) )
1231 {
1232 return modifiers.contains( LEVEL_PUBLIC );
1233 }
1234
1235 if ( LEVEL_PROTECTED.equalsIgnoreCase( level.trim() ) )
1236 {
1237 return ( modifiers.contains( LEVEL_PUBLIC ) || modifiers.contains( LEVEL_PROTECTED ) );
1238 }
1239
1240 if ( LEVEL_PACKAGE.equalsIgnoreCase( level.trim() ) )
1241 {
1242 return !modifiers.contains( LEVEL_PRIVATE );
1243 }
1244
1245
1246 return true;
1247 }
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283 private void addDefaultClassComment( final StringWriter stringWriter, final JavaClass javaClass,
1284 final String indent )
1285 {
1286 StringBuilder sb = new StringBuilder();
1287
1288 sb.append( indent ).append( START_JAVADOC );
1289 sb.append( EOL );
1290 sb.append( indent ).append( SEPARATOR_JAVADOC );
1291 sb.append( getDefaultClassJavadocComment( javaClass ) );
1292 sb.append( EOL );
1293
1294 appendSeparator( sb, indent );
1295
1296 appendDefaultAuthorTag( sb, indent );
1297
1298 appendDefaultVersionTag( sb, indent );
1299
1300 if ( fixTag( SINCE_TAG ) )
1301 {
1302 if ( !ignoreClirr )
1303 {
1304 if ( isNewClassFromLastVersion( javaClass ) )
1305 {
1306 appendDefaultSinceTag( sb, indent );
1307 }
1308 }
1309 else
1310 {
1311 appendDefaultSinceTag( sb, indent );
1312 addSinceClasses( javaClass );
1313 }
1314 }
1315
1316 sb.append( indent ).append( " " ).append( END_JAVADOC );
1317 sb.append( EOL );
1318
1319 stringWriter.write( sb.toString() );
1320 }
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332 private boolean fixFieldComment( final StringWriter stringWriter, final JavaClass javaClass, final JavaField field,
1333 final String indent )
1334 throws IOException
1335 {
1336 if ( !fixFieldComment )
1337 {
1338 return false;
1339 }
1340
1341 if ( !javaClass.isInterface() && ( !isInLevel( field.getModifiers() ) || !field.isStatic() ) )
1342 {
1343 return false;
1344 }
1345
1346
1347 if ( field.getComment() == null )
1348 {
1349 addDefaultFieldComment( stringWriter, field, indent );
1350 return true;
1351 }
1352
1353
1354 return false;
1355 }
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376 private void addDefaultFieldComment( final StringWriter stringWriter, final JavaField field, final String indent )
1377 throws IOException
1378 {
1379 StringBuilder sb = new StringBuilder();
1380
1381 sb.append( indent ).append( START_JAVADOC ).append( " " );
1382 sb.append( "Constant <code>" ).append( field.getName() );
1383
1384 if ( StringUtils.isNotEmpty( field.getInitializationExpression() ) )
1385 {
1386 String qualifiedName = field.getType().getFullyQualifiedName();
1387
1388 if ( qualifiedName.equals( Byte.TYPE.toString() ) || qualifiedName.equals( Short.TYPE.toString() )
1389 || qualifiedName.equals( Integer.TYPE.toString() ) || qualifiedName.equals( Long.TYPE.toString() )
1390 || qualifiedName.equals( Float.TYPE.toString() ) || qualifiedName.equals( Double.TYPE.toString() )
1391 || qualifiedName.equals( Boolean.TYPE.toString() ) || qualifiedName.equals(
1392 Character.TYPE.toString() ) )
1393 {
1394 sb.append( "=" );
1395 sb.append( field.getInitializationExpression().trim() );
1396 }
1397
1398 if ( qualifiedName.equals( String.class.getName() ) )
1399 {
1400 StringBuilder value = new StringBuilder();
1401 String[] lines = getLines( field.getInitializationExpression() );
1402 for ( String line : lines )
1403 {
1404 StringTokenizer token = new StringTokenizer( line.trim(), "\"\n\r" );
1405 while ( token.hasMoreTokens() )
1406 {
1407 String s = token.nextToken();
1408
1409 if ( s.trim().equals( "+" ) )
1410 {
1411 continue;
1412 }
1413 if ( s.trim().endsWith( "\\" ) )
1414 {
1415 s += "\"";
1416 }
1417 value.append( s );
1418 }
1419 }
1420
1421 sb.append( "=\"" );
1422
1423
1424 if ( value.length() < 40 )
1425 {
1426 sb.append( value.toString() ).append( "\"" );
1427 }
1428 else
1429 {
1430 sb.append( value.toString(), 0, 39 ).append( "\"{trunked}" );
1431 }
1432
1433 }
1434 }
1435
1436 sb.append( "</code> " ).append( END_JAVADOC );
1437 sb.append( EOL );
1438
1439 stringWriter.write( sb.toString() );
1440 }
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453 private boolean fixMethodComment( final StringWriter stringWriter, final String originalContent,
1454 final JavaExecutable javaExecutable, final String indent )
1455 throws MojoExecutionException, IOException
1456 {
1457 if ( !fixMethodComment )
1458 {
1459 return false;
1460 }
1461
1462 if ( !javaExecutable.getDeclaringClass().isInterface() && !isInLevel( javaExecutable.getModifiers() ) )
1463 {
1464 return false;
1465 }
1466
1467
1468 if ( javaExecutable.getComment() == null )
1469 {
1470 addDefaultMethodComment( stringWriter, javaExecutable, indent );
1471 return true;
1472 }
1473
1474
1475 return updateEntityComment( stringWriter, originalContent, javaExecutable, indent );
1476 }
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516 private void addDefaultMethodComment( final StringWriter stringWriter, final JavaExecutable javaExecutable,
1517 final String indent )
1518 throws MojoExecutionException
1519 {
1520 StringBuilder sb = new StringBuilder();
1521
1522
1523 if ( isInherited( javaExecutable ) )
1524 {
1525 sb.append( indent ).append( INHERITED_JAVADOC );
1526 sb.append( EOL );
1527
1528 stringWriter.write( sb.toString() );
1529 return;
1530 }
1531
1532 sb.append( indent ).append( START_JAVADOC );
1533 sb.append( EOL );
1534 sb.append( indent ).append( SEPARATOR_JAVADOC );
1535 sb.append( getDefaultMethodJavadocComment( javaExecutable ) );
1536 sb.append( EOL );
1537
1538 boolean separatorAdded = false;
1539 if ( fixTag( PARAM_TAG ) )
1540 {
1541 if ( javaExecutable.getParameters() != null )
1542 {
1543 for ( JavaParameter javaParameter : javaExecutable.getParameters() )
1544 {
1545 separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, javaParameter );
1546 }
1547 }
1548
1549 if ( javaExecutable.getTypeParameters() != null )
1550 {
1551 for ( JavaTypeVariable<JavaGenericDeclaration> typeParam : javaExecutable.getTypeParameters() )
1552 {
1553 separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, typeParam );
1554 }
1555 }
1556 }
1557 if ( javaExecutable instanceof JavaMethod )
1558 {
1559 JavaMethod javaMethod = (JavaMethod) javaExecutable;
1560 if ( fixTag( RETURN_TAG ) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() )
1561 {
1562 separatorAdded = appendDefaultReturnTag( sb, indent, separatorAdded, javaMethod );
1563 }
1564
1565 }
1566 if ( fixTag( THROWS_TAG ) && javaExecutable.getExceptions() != null )
1567 {
1568 for ( JavaType exception : javaExecutable.getExceptions() )
1569 {
1570 separatorAdded = appendDefaultThrowsTag( sb, indent, separatorAdded, exception );
1571 }
1572 }
1573 if ( fixTag( SINCE_TAG ) && isNewMethodFromLastRevision( javaExecutable ) )
1574 {
1575 separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
1576 }
1577
1578 sb.append( indent ).append( " " ).append( END_JAVADOC );
1579 sb.append( EOL );
1580
1581 stringWriter.write( sb.toString() );
1582 }
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593 private boolean updateEntityComment( final StringWriter stringWriter, final String originalContent,
1594 final JavaAnnotatedElement entity, final String indent )
1595 throws MojoExecutionException, IOException
1596 {
1597 boolean changeDetected = false;
1598
1599 String old = null;
1600 String s = stringWriter.toString();
1601 int i = s.lastIndexOf( START_JAVADOC );
1602 if ( i != -1 )
1603 {
1604 String tmp = s.substring( 0, i );
1605 if ( tmp.lastIndexOf( EOL ) != -1 )
1606 {
1607 tmp = tmp.substring( 0, tmp.lastIndexOf( EOL ) );
1608 }
1609
1610 old = stringWriter.getBuffer().substring( i );
1611
1612 stringWriter.getBuffer().delete( 0, stringWriter.getBuffer().length() );
1613 stringWriter.write( tmp );
1614 stringWriter.write( EOL );
1615 }
1616 else
1617 {
1618 changeDetected = true;
1619 }
1620
1621 updateJavadocComment( stringWriter, originalContent, entity, indent );
1622
1623 if ( changeDetected )
1624 {
1625 return true;
1626 }
1627
1628 return !stringWriter.getBuffer().substring( i ).equals( old );
1629 }
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639 private void updateJavadocComment( final StringWriter stringWriter, final String originalContent,
1640 final JavaAnnotatedElement entity, final String indent )
1641 throws MojoExecutionException, IOException
1642 {
1643 if ( entity.getComment() == null && ( entity.getTags() == null || entity.getTags().isEmpty() ) )
1644 {
1645 return;
1646 }
1647
1648 boolean isJavaExecutable = false;
1649 if ( entity instanceof JavaExecutable )
1650 {
1651 isJavaExecutable = true;
1652 }
1653
1654 StringBuilder sb = new StringBuilder();
1655
1656
1657 if ( isJavaExecutable )
1658 {
1659 JavaExecutable javaMethod = (JavaExecutable) entity;
1660
1661 if ( isInherited( javaMethod ) )
1662 {
1663
1664 if ( StringUtils.isEmpty( javaMethod.getComment() ) )
1665 {
1666 sb.append( indent ).append( INHERITED_JAVADOC );
1667 sb.append( EOL );
1668 stringWriter.write( sb.toString() );
1669 return;
1670 }
1671
1672 String javadoc = getJavadocComment( originalContent, javaMethod );
1673
1674
1675 if ( hasInheritedTag( javadoc ) && ( javaMethod.getTags() == null
1676 || javaMethod.getTags().isEmpty() ) )
1677 {
1678 sb.append( indent ).append( INHERITED_JAVADOC );
1679 sb.append( EOL );
1680 stringWriter.write( sb.toString() );
1681 return;
1682 }
1683
1684 if ( javadoc.contains( START_JAVADOC ) )
1685 {
1686 javadoc = javadoc.substring( javadoc.indexOf( START_JAVADOC ) + START_JAVADOC.length() );
1687 }
1688 if ( javadoc.contains( END_JAVADOC ) )
1689 {
1690 javadoc = javadoc.substring( 0, javadoc.indexOf( END_JAVADOC ) );
1691 }
1692
1693 sb.append( indent ).append( START_JAVADOC );
1694 sb.append( EOL );
1695 if ( !javadoc.contains( INHERITED_TAG ) )
1696 {
1697 sb.append( indent ).append( SEPARATOR_JAVADOC ).append( INHERITED_TAG );
1698 sb.append( EOL );
1699 appendSeparator( sb, indent );
1700 }
1701 javadoc = removeLastEmptyJavadocLines( javadoc );
1702 javadoc = alignIndentationJavadocLines( javadoc, indent );
1703 sb.append( javadoc );
1704 sb.append( EOL );
1705 if ( javaMethod.getTags() != null )
1706 {
1707 for ( DocletTag docletTag : javaMethod.getTags() )
1708 {
1709
1710 if ( JavadocUtil.equals( docletTag.getName(), PARAM_TAG, RETURN_TAG, THROWS_TAG ) )
1711 {
1712 continue;
1713 }
1714
1715 String s = getJavadocComment( originalContent, entity, docletTag );
1716 s = removeLastEmptyJavadocLines( s );
1717 s = alignIndentationJavadocLines( s, indent );
1718 sb.append( s );
1719 sb.append( EOL );
1720 }
1721 }
1722 sb.append( indent ).append( " " ).append( END_JAVADOC );
1723 sb.append( EOL );
1724
1725 if ( hasInheritedTag( sb.toString().trim() ) )
1726 {
1727 sb = new StringBuilder();
1728 sb.append( indent ).append( INHERITED_JAVADOC );
1729 sb.append( EOL );
1730 stringWriter.write( sb.toString() );
1731 return;
1732 }
1733
1734 stringWriter.write( sb.toString() );
1735 return;
1736 }
1737 }
1738
1739 sb.append( indent ).append( START_JAVADOC );
1740 sb.append( EOL );
1741
1742
1743 if ( StringUtils.isNotEmpty( entity.getComment() ) )
1744 {
1745 updateJavadocComment( sb, originalContent, entity, indent );
1746 }
1747 else
1748 {
1749 addDefaultJavadocComment( sb, entity, indent, isJavaExecutable );
1750 }
1751
1752
1753 if ( entity.getTags() != null && !entity.getTags().isEmpty() )
1754 {
1755 updateJavadocTags( sb, originalContent, entity, indent, isJavaExecutable );
1756 }
1757 else
1758 {
1759 addDefaultJavadocTags( sb, entity, indent, isJavaExecutable );
1760 }
1761
1762 sb = new StringBuilder( removeLastEmptyJavadocLines( sb.toString() ) ).append( EOL );
1763
1764 sb.append( indent ).append( " " ).append( END_JAVADOC );
1765 sb.append( EOL );
1766
1767 stringWriter.write( sb.toString() );
1768 }
1769
1770
1771
1772
1773
1774
1775
1776
1777 private void updateJavadocComment( final StringBuilder sb, final String originalContent,
1778 final JavaAnnotatedElement entity, final String indent )
1779 throws IOException
1780 {
1781 String comment = getJavadocComment( originalContent, entity );
1782 comment = removeLastEmptyJavadocLines( comment );
1783 comment = alignIndentationJavadocLines( comment, indent );
1784
1785 if ( comment.contains( START_JAVADOC ) )
1786 {
1787 comment = comment.substring( comment.indexOf( START_JAVADOC ) + START_JAVADOC.length() );
1788 comment = indent + SEPARATOR_JAVADOC + comment.trim();
1789 }
1790 if ( comment.contains( END_JAVADOC ) )
1791 {
1792 comment = comment.substring( 0, comment.indexOf( END_JAVADOC ) );
1793 }
1794
1795 if ( fixTag( LINK_TAG ) )
1796 {
1797 comment = replaceLinkTags( comment, entity );
1798 }
1799
1800 String[] lines = getLines( comment );
1801 for ( String line : lines )
1802 {
1803 sb.append( indent ).append( " " ).append( line.trim() );
1804 sb.append( EOL );
1805 }
1806 }
1807
1808 static String replaceLinkTags( String comment, JavaAnnotatedElement entity )
1809 {
1810 StringBuilder resolvedComment = new StringBuilder();
1811
1812 Matcher linktagMatcher = Pattern.compile( "\\{@link\\s" ).matcher( comment );
1813 int startIndex = 0;
1814 while ( linktagMatcher.find() )
1815 {
1816 int startName = linktagMatcher.end();
1817 resolvedComment.append( comment, startIndex, startName );
1818 int endName = comment.indexOf( "}", startName );
1819 if ( endName >= 0 )
1820 {
1821 String name;
1822 String link = comment.substring( startName, endName );
1823 int hashIndex = link.indexOf( '#' );
1824 if ( hashIndex >= 0 )
1825 {
1826 name = link.substring( 0, hashIndex );
1827 }
1828 else
1829 {
1830 name = link;
1831 }
1832 if ( StringUtils.isNotBlank( name ) )
1833 {
1834 String typeName;
1835 if ( entity instanceof JavaClass )
1836 {
1837 JavaClass clazz = (JavaClass) entity;
1838 typeName =
1839 TypeResolver.byClassName( clazz.getBinaryName(), clazz.getJavaClassLibrary(),
1840 clazz.getSource().getImports() ).resolveType( name.trim() );
1841 }
1842 else if ( entity instanceof JavaMember )
1843 {
1844 JavaClass clazz = ( (JavaMember) entity ).getDeclaringClass();
1845 typeName =
1846 TypeResolver.byClassName( clazz.getBinaryName(), clazz.getJavaClassLibrary(),
1847 clazz.getSource().getImports() ).resolveType( name.trim() );
1848 }
1849 else
1850 {
1851 typeName = null;
1852 }
1853
1854 if ( typeName == null )
1855 {
1856 typeName = name.trim();
1857 }
1858 else
1859 {
1860 typeName = typeName.replaceAll( "\\$", "." );
1861 }
1862
1863 resolvedComment.append( typeName );
1864 }
1865 if ( hashIndex >= 0 )
1866 {
1867 resolvedComment.append( link.substring( hashIndex ).trim() );
1868 }
1869 startIndex = endName;
1870 }
1871 else
1872 {
1873 startIndex = startName;
1874 }
1875
1876 }
1877 resolvedComment.append( comment.substring( startIndex ) );
1878 return resolvedComment.toString();
1879
1880 }
1881
1882
1883
1884
1885
1886
1887
1888 private void addDefaultJavadocComment( final StringBuilder sb, final JavaAnnotatedElement entity,
1889 final String indent, final boolean isJavaExecutable )
1890 {
1891 sb.append( indent ).append( SEPARATOR_JAVADOC );
1892 if ( isJavaExecutable )
1893 {
1894 sb.append( getDefaultMethodJavadocComment( (JavaExecutable) entity ) );
1895 }
1896 else
1897 {
1898 sb.append( getDefaultClassJavadocComment( (JavaClass) entity ) );
1899 }
1900 sb.append( EOL );
1901 }
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912 private void updateJavadocTags( final StringBuilder sb, final String originalContent,
1913 final JavaAnnotatedElement entity, final String indent,
1914 final boolean isJavaExecutable )
1915 throws IOException, MojoExecutionException
1916 {
1917 appendSeparator( sb, indent );
1918
1919
1920 JavaEntityTags javaEntityTags = parseJavadocTags( originalContent, entity, indent, isJavaExecutable );
1921
1922
1923 updateJavadocTags( sb, entity, isJavaExecutable, javaEntityTags );
1924
1925
1926 addMissingJavadocTags( sb, entity, indent, isJavaExecutable, javaEntityTags );
1927 }
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939 JavaEntityTags parseJavadocTags( final String originalContent, final JavaAnnotatedElement entity,
1940 final String indent, final boolean isJavaMethod )
1941 throws IOException
1942 {
1943 JavaEntityTags javaEntityTags = new JavaEntityTags( entity, isJavaMethod );
1944 for ( DocletTag docletTag : entity.getTags() )
1945 {
1946 String originalJavadocTag = getJavadocComment( originalContent, entity, docletTag );
1947 originalJavadocTag = removeLastEmptyJavadocLines( originalJavadocTag );
1948 originalJavadocTag = alignIndentationJavadocLines( originalJavadocTag, indent );
1949
1950 javaEntityTags.getNamesTags().add( docletTag.getName() );
1951
1952 if ( isJavaMethod )
1953 {
1954 List<String> params = docletTag.getParameters();
1955 if ( params.size() < 1 )
1956 {
1957 continue;
1958 }
1959
1960 String paramName = params.get( 0 );
1961 switch ( docletTag.getName() )
1962 {
1963 case PARAM_TAG:
1964 javaEntityTags.putJavadocParamTag( paramName, docletTag.getValue(), originalJavadocTag );
1965 break;
1966 case RETURN_TAG:
1967 javaEntityTags.setJavadocReturnTag( originalJavadocTag );
1968 break;
1969 case THROWS_TAG:
1970 javaEntityTags.putJavadocThrowsTag( paramName, originalJavadocTag );
1971 break;
1972 default:
1973 javaEntityTags.getUnknownTags().add( originalJavadocTag );
1974 break;
1975 }
1976 }
1977 else
1978 {
1979 javaEntityTags.getUnknownTags().add( originalJavadocTag );
1980 }
1981 }
1982
1983 return javaEntityTags;
1984 }
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994 private void updateJavadocTags( final StringBuilder sb, final JavaAnnotatedElement entity,
1995 final boolean isJavaExecutable, final JavaEntityTags javaEntityTags )
1996 {
1997 for ( DocletTag docletTag : entity.getTags() )
1998 {
1999 if ( isJavaExecutable )
2000 {
2001 JavaExecutable javaExecutable = (JavaExecutable) entity;
2002
2003 List<String> params = docletTag.getParameters();
2004 if ( params.size() < 1 )
2005 {
2006 continue;
2007 }
2008
2009 if ( docletTag.getName().equals( PARAM_TAG ) )
2010 {
2011 writeParamTag( sb, javaExecutable, javaEntityTags, params.get( 0 ), docletTag.getValue() );
2012 }
2013 else if ( docletTag.getName().equals( RETURN_TAG ) && javaExecutable instanceof JavaMethod )
2014 {
2015 writeReturnTag( sb, (JavaMethod) javaExecutable, javaEntityTags );
2016 }
2017 else if ( docletTag.getName().equals( THROWS_TAG ) )
2018 {
2019 writeThrowsTag( sb, javaExecutable, javaEntityTags, params );
2020 }
2021 else
2022 {
2023
2024 for ( Iterator<String> it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); )
2025 {
2026 String originalJavadocTag = it.next();
2027 String simplified = StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim();
2028
2029 if ( simplified.contains( "@" + docletTag.getName() ) )
2030 {
2031 it.remove();
2032 sb.append( originalJavadocTag );
2033 sb.append( EOL );
2034 }
2035 }
2036 }
2037 }
2038 else
2039 {
2040 for ( Iterator<String> it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); )
2041 {
2042 String originalJavadocTag = it.next();
2043 String simplified = StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim();
2044
2045 if ( simplified.contains( "@" + docletTag.getName() ) )
2046 {
2047 it.remove();
2048 sb.append( originalJavadocTag );
2049 sb.append( EOL );
2050 }
2051 }
2052 }
2053
2054 if ( sb.toString().endsWith( EOL ) )
2055 {
2056 sb.delete( sb.toString().lastIndexOf( EOL ), sb.toString().length() );
2057 }
2058
2059 sb.append( EOL );
2060 }
2061 }
2062
2063 private void writeParamTag( final StringBuilder sb, final JavaExecutable javaExecutable,
2064 final JavaEntityTags javaEntityTags, String paramName, String paramValue )
2065 {
2066 if ( !fixTag( PARAM_TAG ) )
2067 {
2068
2069 String originalJavadocTag = javaEntityTags.getJavadocParamTag( paramValue );
2070 if ( originalJavadocTag != null )
2071 {
2072 sb.append( originalJavadocTag );
2073 }
2074 return;
2075 }
2076
2077 boolean found = false;
2078 JavaParameter javaParam = javaExecutable.getParameterByName( paramName );
2079 if ( javaParam == null )
2080 {
2081
2082 List<JavaTypeVariable<JavaGenericDeclaration>> typeParams = javaExecutable.getTypeParameters();
2083 for ( JavaTypeVariable<JavaGenericDeclaration> typeParam : typeParams )
2084 {
2085 if ( typeParam.getGenericValue().equals( paramName ) )
2086 {
2087 found = true;
2088 }
2089 }
2090 }
2091 else
2092 {
2093 found = true;
2094 }
2095
2096 if ( !found )
2097 {
2098 if ( getLog().isWarnEnabled() )
2099 {
2100 getLog().warn(
2101 "Fixed unknown param '" + paramName + "' defined in " + getJavaMethodAsString( javaExecutable ) );
2102 }
2103
2104 if ( sb.toString().endsWith( EOL ) )
2105 {
2106 sb.delete( sb.toString().lastIndexOf( EOL ), sb.toString().length() );
2107 }
2108 }
2109 else
2110 {
2111 String originalJavadocTag = javaEntityTags.getJavadocParamTag( paramValue );
2112 if ( originalJavadocTag != null )
2113 {
2114 sb.append( originalJavadocTag );
2115 String s = "@" + PARAM_TAG + " " + paramName;
2116 if ( StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim().endsWith( s ) )
2117 {
2118 sb.append( " " );
2119 sb.append( getDefaultJavadocForType( javaParam.getJavaClass() ) );
2120 }
2121 }
2122 }
2123 }
2124
2125 private void writeReturnTag( final StringBuilder sb, final JavaMethod javaMethod,
2126 final JavaEntityTags javaEntityTags )
2127 {
2128 String originalJavadocTag = javaEntityTags.getJavadocReturnTag();
2129 if ( originalJavadocTag == null )
2130 {
2131 return;
2132 }
2133
2134 if ( !fixTag( RETURN_TAG ) )
2135 {
2136
2137 sb.append( originalJavadocTag );
2138 return;
2139 }
2140
2141 if ( StringUtils.isNotEmpty( originalJavadocTag ) && javaMethod.getReturns() != null
2142 && !javaMethod.getReturns().isVoid() )
2143 {
2144 sb.append( originalJavadocTag );
2145 if ( originalJavadocTag.trim().endsWith( "@" + RETURN_TAG ) )
2146 {
2147 sb.append( " " );
2148 sb.append( getDefaultJavadocForType( javaMethod.getReturns() ) );
2149 }
2150 }
2151 }
2152
2153 void writeThrowsTag( final StringBuilder sb, final JavaExecutable javaExecutable,
2154 final JavaEntityTags javaEntityTags, final List<String> params )
2155 {
2156 String exceptionClassName = params.get( 0 );
2157
2158 String originalJavadocTag = javaEntityTags.getJavadocThrowsTag( exceptionClassName );
2159 if ( originalJavadocTag == null )
2160 {
2161 return;
2162 }
2163
2164 if ( !fixTag( THROWS_TAG ) )
2165 {
2166
2167 sb.append( originalJavadocTag );
2168 return;
2169 }
2170
2171 if ( javaExecutable.getExceptions() != null )
2172 {
2173 for ( JavaType exception : javaExecutable.getExceptions() )
2174 {
2175 if ( exception.getValue().endsWith( exceptionClassName ) )
2176 {
2177 originalJavadocTag = StringUtils.replace( originalJavadocTag, exceptionClassName,
2178 exception.getFullyQualifiedName() );
2179 if ( StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim().endsWith(
2180 "@" + THROWS_TAG + " " + exception.getValue() ) )
2181 {
2182 originalJavadocTag += " if any.";
2183 }
2184
2185 sb.append( originalJavadocTag );
2186
2187
2188 javaEntityTags.putJavadocThrowsTag( exception.getValue(), originalJavadocTag );
2189
2190 return;
2191 }
2192 }
2193 }
2194
2195 Class<?> clazz = getClass( javaExecutable.getDeclaringClass(), exceptionClassName );
2196
2197 if ( clazz != null )
2198 {
2199 if ( RuntimeException.class.isAssignableFrom( clazz ) )
2200 {
2201 sb.append( StringUtils.replace( originalJavadocTag, exceptionClassName, clazz.getName() ) );
2202
2203
2204 javaEntityTags.putJavadocThrowsTag( clazz.getName(), originalJavadocTag );
2205 }
2206 else if ( Throwable.class.isAssignableFrom( clazz ) )
2207 {
2208 getLog().debug( "Removing '" + originalJavadocTag + "'; Throwable not specified by "
2209 + getJavaMethodAsString( javaExecutable ) + " and it is not a RuntimeException." );
2210 }
2211 else
2212 {
2213 getLog().debug( "Removing '" + originalJavadocTag + "'; It is not a Throwable" );
2214 }
2215 }
2216 else if ( removeUnknownThrows )
2217 {
2218 getLog().warn( "Ignoring unknown throws '" + exceptionClassName + "' defined on "
2219 + getJavaMethodAsString( javaExecutable ) );
2220 }
2221 else
2222 {
2223 getLog().warn( "Found unknown throws '" + exceptionClassName + "' defined on "
2224 + getJavaMethodAsString( javaExecutable ) );
2225
2226 sb.append( originalJavadocTag );
2227
2228 if ( params.size() == 1 )
2229 {
2230 sb.append( " if any." );
2231 }
2232
2233 javaEntityTags.putJavadocThrowsTag( exceptionClassName, originalJavadocTag );
2234 }
2235 }
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247 private void addMissingJavadocTags( final StringBuilder sb, final JavaAnnotatedElement entity,
2248 final String indent, final boolean isJavaExecutable,
2249 final JavaEntityTags javaEntityTags )
2250 throws MojoExecutionException
2251 {
2252 if ( isJavaExecutable )
2253 {
2254 JavaExecutable javaExecutable = (JavaExecutable) entity;
2255
2256 if ( fixTag( PARAM_TAG ) )
2257 {
2258 if ( javaExecutable.getParameters() != null )
2259 {
2260 for ( JavaParameter javaParameter : javaExecutable.getParameters() )
2261 {
2262 if ( !javaEntityTags.hasJavadocParamTag( javaParameter.getName() ) )
2263 {
2264 appendDefaultParamTag( sb, indent, javaParameter );
2265 }
2266 }
2267 }
2268
2269 if ( javaExecutable.getTypeParameters() != null )
2270 {
2271 for ( JavaTypeVariable<JavaGenericDeclaration> typeParam : javaExecutable.getTypeParameters() )
2272 {
2273 if ( !javaEntityTags.hasJavadocParamTag( "<" + typeParam.getName() + ">" ) )
2274 {
2275 appendDefaultParamTag( sb, indent, typeParam );
2276 }
2277 }
2278 }
2279 }
2280
2281 if ( javaExecutable instanceof JavaMethod )
2282 {
2283 JavaMethod javaMethod = (JavaMethod) javaExecutable;
2284 if ( fixTag( RETURN_TAG ) && StringUtils.isEmpty( javaEntityTags.getJavadocReturnTag() )
2285 && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() )
2286 {
2287 appendDefaultReturnTag( sb, indent, javaMethod );
2288 }
2289 }
2290
2291 if ( fixTag( THROWS_TAG ) && javaExecutable.getExceptions() != null )
2292 {
2293 for ( JavaType exception : javaExecutable.getExceptions() )
2294 {
2295 if ( javaEntityTags.getJavadocThrowsTag( exception.getValue(), true ) == null )
2296 {
2297 appendDefaultThrowsTag( sb, indent, exception );
2298 }
2299 }
2300 }
2301 }
2302 else
2303 {
2304 if ( !javaEntityTags.getNamesTags().contains( AUTHOR_TAG ) )
2305 {
2306 appendDefaultAuthorTag( sb, indent );
2307 }
2308 if ( !javaEntityTags.getNamesTags().contains( VERSION_TAG ) )
2309 {
2310 appendDefaultVersionTag( sb, indent );
2311 }
2312 }
2313
2314 if ( fixTag( SINCE_TAG ) && !javaEntityTags.getNamesTags().contains( SINCE_TAG ) )
2315 {
2316 if ( !isJavaExecutable )
2317 {
2318 if ( !ignoreClirr )
2319 {
2320 if ( isNewClassFromLastVersion( (JavaClass) entity ) )
2321 {
2322 appendDefaultSinceTag( sb, indent );
2323 }
2324 }
2325 else
2326 {
2327 appendDefaultSinceTag( sb, indent );
2328 addSinceClasses( (JavaClass) entity );
2329 }
2330 }
2331 else
2332 {
2333 if ( !ignoreClirr )
2334 {
2335 if ( isNewMethodFromLastRevision( (JavaExecutable) entity ) )
2336 {
2337 appendDefaultSinceTag( sb, indent );
2338 }
2339 }
2340 else if ( sinceClasses != null )
2341 {
2342 if ( entity instanceof JavaMember
2343 && !sinceClassesContains( ( (JavaMember) entity ).getDeclaringClass() ) )
2344 {
2345 appendDefaultSinceTag( sb, indent );
2346 }
2347 else if ( entity instanceof JavaClass
2348 && !sinceClassesContains( ( (JavaClass) entity ).getDeclaringClass() ) )
2349 {
2350 appendDefaultSinceTag( sb, indent );
2351 }
2352 }
2353 }
2354 }
2355 }
2356
2357
2358
2359
2360
2361
2362
2363
2364 private void addDefaultJavadocTags( final StringBuilder sb, final JavaAnnotatedElement entity,
2365 final String indent, final boolean isJavaExecutable )
2366 throws MojoExecutionException
2367 {
2368 boolean separatorAdded = false;
2369 if ( isJavaExecutable )
2370 {
2371 JavaExecutable javaExecutable = (JavaExecutable) entity;
2372
2373 if ( fixTag( PARAM_TAG ) && javaExecutable.getParameters() != null )
2374 {
2375 for ( JavaParameter javaParameter : javaExecutable.getParameters() )
2376 {
2377 separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, javaParameter );
2378 }
2379 }
2380
2381 if ( javaExecutable instanceof JavaMethod && fixTag( RETURN_TAG ) )
2382 {
2383 JavaMethod javaMethod = (JavaMethod) javaExecutable;
2384 if ( javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() )
2385 {
2386 separatorAdded = appendDefaultReturnTag( sb, indent, separatorAdded, javaMethod );
2387 }
2388 }
2389
2390 if ( fixTag( THROWS_TAG ) && javaExecutable.getExceptions() != null )
2391 {
2392 for ( JavaType exception : javaExecutable.getExceptions() )
2393 {
2394 separatorAdded = appendDefaultThrowsTag( sb, indent, separatorAdded, exception );
2395 }
2396 }
2397 }
2398 else
2399 {
2400 separatorAdded = appendDefaultAuthorTag( sb, indent, separatorAdded );
2401
2402 separatorAdded = appendDefaultVersionTag( sb, indent, separatorAdded );
2403 }
2404
2405 if ( fixTag( SINCE_TAG ) )
2406 {
2407 if ( !isJavaExecutable )
2408 {
2409 JavaClass javaClass = (JavaClass) entity;
2410
2411 if ( !ignoreClirr )
2412 {
2413 if ( isNewClassFromLastVersion( javaClass ) )
2414 {
2415 separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
2416 }
2417 }
2418 else
2419 {
2420 separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
2421
2422 addSinceClasses( javaClass );
2423 }
2424 }
2425 else
2426 {
2427 JavaExecutable javaExecutable = (JavaExecutable) entity;
2428
2429 if ( !ignoreClirr )
2430 {
2431 if ( isNewMethodFromLastRevision( javaExecutable ) )
2432 {
2433 separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
2434 }
2435 }
2436 else
2437 {
2438 if ( sinceClasses != null && !sinceClassesContains( javaExecutable.getDeclaringClass() ) )
2439 {
2440 separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded );
2441 }
2442 }
2443 }
2444 }
2445 }
2446
2447
2448
2449
2450
2451
2452
2453 private boolean appendDefaultAuthorTag( final StringBuilder sb, final String indent, boolean separatorAdded )
2454 {
2455 if ( !fixTag( AUTHOR_TAG ) )
2456 {
2457 return separatorAdded;
2458 }
2459
2460 if ( !separatorAdded )
2461 {
2462 appendSeparator( sb, indent );
2463 separatorAdded = true;
2464 }
2465
2466 appendDefaultAuthorTag( sb, indent );
2467 return separatorAdded;
2468 }
2469
2470
2471
2472
2473
2474 private void appendDefaultAuthorTag( final StringBuilder sb, final String indent )
2475 {
2476 if ( !fixTag( AUTHOR_TAG ) )
2477 {
2478 return;
2479 }
2480
2481 sb.append( indent ).append( " * @" ).append( AUTHOR_TAG ).append( " " );
2482 sb.append( defaultAuthor );
2483 sb.append( EOL );
2484 }
2485
2486
2487
2488
2489
2490
2491
2492 private boolean appendDefaultSinceTag( final StringBuilder sb, final String indent, boolean separatorAdded )
2493 {
2494 if ( !fixTag( SINCE_TAG ) )
2495 {
2496 return separatorAdded;
2497 }
2498
2499 if ( !separatorAdded )
2500 {
2501 appendSeparator( sb, indent );
2502 separatorAdded = true;
2503 }
2504
2505 appendDefaultSinceTag( sb, indent );
2506 return separatorAdded;
2507 }
2508
2509
2510
2511
2512
2513 private void appendDefaultSinceTag( final StringBuilder sb, final String indent )
2514 {
2515 if ( !fixTag( SINCE_TAG ) )
2516 {
2517 return;
2518 }
2519
2520 sb.append( indent ).append( " * @" ).append( SINCE_TAG ).append( " " );
2521 sb.append( defaultSince );
2522 sb.append( EOL );
2523 }
2524
2525
2526
2527
2528
2529
2530
2531 private boolean appendDefaultVersionTag( final StringBuilder sb, final String indent, boolean separatorAdded )
2532 {
2533 if ( !fixTag( VERSION_TAG ) )
2534 {
2535 return separatorAdded;
2536 }
2537
2538 if ( !separatorAdded )
2539 {
2540 appendSeparator( sb, indent );
2541 separatorAdded = true;
2542 }
2543
2544 appendDefaultVersionTag( sb, indent );
2545 return separatorAdded;
2546 }
2547
2548
2549
2550
2551
2552 private void appendDefaultVersionTag( final StringBuilder sb, final String indent )
2553 {
2554 if ( !fixTag( VERSION_TAG ) )
2555 {
2556 return;
2557 }
2558
2559 sb.append( indent ).append( " * @" ).append( VERSION_TAG ).append( " " );
2560 sb.append( defaultVersion );
2561 sb.append( EOL );
2562 }
2563
2564
2565
2566
2567
2568
2569
2570
2571 private boolean appendDefaultParamTag( final StringBuilder sb, final String indent, boolean separatorAdded,
2572 final JavaParameter typeParam )
2573 {
2574 if ( !fixTag( PARAM_TAG ) )
2575 {
2576 return separatorAdded;
2577 }
2578
2579 if ( !separatorAdded )
2580 {
2581 appendSeparator( sb, indent );
2582 separatorAdded = true;
2583 }
2584
2585 appendDefaultParamTag( sb, indent, typeParam );
2586 return separatorAdded;
2587 }
2588
2589
2590
2591
2592
2593
2594
2595
2596 private boolean appendDefaultParamTag( final StringBuilder sb, final String indent, boolean separatorAdded,
2597 final JavaTypeVariable<JavaGenericDeclaration> typeParameter )
2598 {
2599 if ( !fixTag( PARAM_TAG ) )
2600 {
2601 return separatorAdded;
2602 }
2603
2604 if ( !separatorAdded )
2605 {
2606 appendSeparator( sb, indent );
2607 separatorAdded = true;
2608 }
2609
2610 appendDefaultParamTag( sb, indent, typeParameter );
2611 return separatorAdded;
2612 }
2613
2614
2615
2616
2617
2618
2619 private void appendDefaultParamTag( final StringBuilder sb, final String indent, final JavaParameter typeParam )
2620 {
2621 if ( !fixTag( PARAM_TAG ) )
2622 {
2623 return;
2624 }
2625
2626 sb.append( indent ).append( " * @" ).append( PARAM_TAG ).append( " " );
2627 sb.append( typeParam.getName() );
2628 sb.append( " " );
2629 sb.append( getDefaultJavadocForType( typeParam.getJavaClass() ) );
2630 sb.append( EOL );
2631 }
2632
2633
2634
2635
2636
2637
2638 private void appendDefaultParamTag( final StringBuilder sb, final String indent,
2639 final JavaTypeVariable<JavaGenericDeclaration> typeParameter )
2640 {
2641 if ( !fixTag( PARAM_TAG ) )
2642 {
2643 return;
2644 }
2645
2646 sb.append( indent ).append( " * @" ).append( PARAM_TAG ).append( " " );
2647 sb.append( "<" ).append( typeParameter.getName() ).append( ">" );
2648 sb.append( " " );
2649 sb.append( getDefaultJavadocForType( typeParameter ) );
2650 sb.append( EOL );
2651 }
2652
2653
2654
2655
2656
2657
2658
2659
2660 private boolean appendDefaultReturnTag( final StringBuilder sb, final String indent, boolean separatorAdded,
2661 final JavaMethod javaMethod )
2662 {
2663 if ( !fixTag( RETURN_TAG ) )
2664 {
2665 return separatorAdded;
2666 }
2667
2668 if ( !separatorAdded )
2669 {
2670 appendSeparator( sb, indent );
2671 separatorAdded = true;
2672 }
2673
2674 appendDefaultReturnTag( sb, indent, javaMethod );
2675 return separatorAdded;
2676 }
2677
2678
2679
2680
2681
2682
2683 private void appendDefaultReturnTag( final StringBuilder sb, final String indent, final JavaMethod javaMethod )
2684 {
2685 if ( !fixTag( RETURN_TAG ) )
2686 {
2687 return;
2688 }
2689
2690 sb.append( indent ).append( " * @" ).append( RETURN_TAG ).append( " " );
2691 sb.append( getDefaultJavadocForType( javaMethod.getReturns() ) );
2692 sb.append( EOL );
2693 }
2694
2695
2696
2697
2698
2699
2700
2701
2702 private boolean appendDefaultThrowsTag( final StringBuilder sb, final String indent, boolean separatorAdded,
2703 final JavaType exception )
2704 {
2705 if ( !fixTag( THROWS_TAG ) )
2706 {
2707 return separatorAdded;
2708 }
2709
2710 if ( !separatorAdded )
2711 {
2712 appendSeparator( sb, indent );
2713 separatorAdded = true;
2714 }
2715
2716 appendDefaultThrowsTag( sb, indent, exception );
2717 return separatorAdded;
2718 }
2719
2720
2721
2722
2723
2724
2725 private void appendDefaultThrowsTag( final StringBuilder sb, final String indent, final JavaType exception )
2726 {
2727 if ( !fixTag( THROWS_TAG ) )
2728 {
2729 return;
2730 }
2731
2732 sb.append( indent ).append( " * @" ).append( THROWS_TAG ).append( " " );
2733 sb.append( exception.getFullyQualifiedName() );
2734 sb.append( " if any." );
2735 sb.append( EOL );
2736 }
2737
2738
2739
2740
2741
2742 private void appendSeparator( final StringBuilder sb, final String indent )
2743 {
2744 sb.append( indent ).append( " *" );
2745 sb.append( EOL );
2746 }
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756 private boolean isInherited( JavaExecutable javaMethod )
2757 throws MojoExecutionException
2758 {
2759 if ( javaMethod.getAnnotations() != null )
2760 {
2761 for ( JavaAnnotation annotation : javaMethod.getAnnotations() )
2762 {
2763 if ( annotation.toString().equals( "@java.lang.Override()" ) )
2764 {
2765 return true;
2766 }
2767 }
2768 }
2769
2770 Class<?> clazz = getClass( javaMethod.getDeclaringClass().getFullyQualifiedName() );
2771
2772 List<Class<?>> interfaces = ClassUtils.getAllInterfaces( clazz );
2773 for ( Class<?> intface : interfaces )
2774 {
2775 if ( isInherited( intface, javaMethod ) )
2776 {
2777 return true;
2778 }
2779 }
2780
2781 List<Class<?>> classes = ClassUtils.getAllSuperclasses( clazz );
2782 for ( Class<?> superClass : classes )
2783 {
2784 if ( isInherited( superClass, javaMethod ) )
2785 {
2786 return true;
2787 }
2788 }
2789
2790 return false;
2791 }
2792
2793
2794
2795
2796
2797
2798
2799
2800 private boolean isInherited( Class<?> clazz, JavaExecutable javaMethod )
2801 {
2802 for ( Method method : clazz.getDeclaredMethods() )
2803 {
2804 if ( !method.getName().equals( javaMethod.getName() ) )
2805 {
2806 continue;
2807 }
2808
2809 if ( method.getParameterTypes().length != javaMethod.getParameters().size() )
2810 {
2811 continue;
2812 }
2813
2814 boolean found = false;
2815 int j = 0;
2816 for ( Class<?> paramType : method.getParameterTypes() )
2817 {
2818 String name1 = paramType.getName();
2819 String name2 = javaMethod.getParameters().get( j++ ).getType().getFullyQualifiedName();
2820 found = name1.equals( name2 );
2821 }
2822
2823 return found;
2824 }
2825
2826 return false;
2827 }
2828
2829
2830
2831
2832
2833 private String getDefaultJavadocForType( JavaClass clazz )
2834 {
2835 StringBuilder sb = new StringBuilder();
2836
2837 if ( !JavaTypeVariable.class.isAssignableFrom( clazz.getClass() ) && clazz.isPrimitive() )
2838 {
2839 if ( clazz.isArray() )
2840 {
2841 sb.append( "an array of " ).append( clazz.getComponentType().getCanonicalName() );
2842 }
2843 else
2844 {
2845 sb.append( "a " ).append( clazz.getCanonicalName() );
2846 }
2847 return sb.append( "." ).toString();
2848 }
2849
2850 StringBuilder javadocLink = new StringBuilder();
2851 try
2852 {
2853 getClass( clazz.getCanonicalName() );
2854
2855 javadocLink.append( "{@link " );
2856
2857 if ( clazz.isArray() )
2858 {
2859 javadocLink.append( clazz.getComponentType().getCanonicalName() );
2860 }
2861 else
2862 {
2863 javadocLink.append( clazz.getCanonicalName() );
2864 }
2865 javadocLink.append( "}" );
2866 }
2867 catch ( Exception e )
2868 {
2869 javadocLink.append( clazz.getValue() );
2870 }
2871
2872 if ( clazz.isArray() )
2873 {
2874 sb.append( "an array of " ).append( javadocLink ).append( " objects." );
2875 }
2876 else
2877 {
2878 sb.append( "a " ).append( javadocLink ).append( " object." );
2879 }
2880
2881 return sb.toString();
2882 }
2883
2884 private String getDefaultJavadocForType( JavaTypeVariable<JavaGenericDeclaration> typeParameter )
2885 {
2886 return "a " + typeParameter.getName() + " object.";
2887 }
2888
2889
2890
2891
2892
2893
2894
2895
2896 private boolean isNewClassFromLastVersion( JavaClass javaClass )
2897 {
2898 return ( clirrNewClasses != null ) && clirrNewClasses.contains( javaClass.getFullyQualifiedName() );
2899 }
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909 private boolean isNewMethodFromLastRevision( JavaExecutable javaExecutable )
2910 throws MojoExecutionException
2911 {
2912 if ( clirrNewMethods == null )
2913 {
2914 return false;
2915 }
2916
2917 List<String> clirrMethods = clirrNewMethods.get( javaExecutable.getDeclaringClass().getFullyQualifiedName() );
2918 if ( clirrMethods == null )
2919 {
2920 return false;
2921 }
2922
2923 for ( String clirrMethod : clirrMethods )
2924 {
2925
2926 String retrn = "";
2927 if ( javaExecutable instanceof JavaMethod && ( (JavaMethod) javaExecutable ).getReturns() != null )
2928 {
2929 retrn = ( (JavaMethod) javaExecutable ).getReturns().getFullyQualifiedName();
2930 }
2931 StringBuilder params = new StringBuilder();
2932 for ( JavaParameter parameter : javaExecutable.getParameters() )
2933 {
2934 if ( params.length() > 0 )
2935 {
2936 params.append( ", " );
2937 }
2938 params.append( parameter.getResolvedFullyQualifiedName() );
2939 }
2940 if ( clirrMethod.contains( retrn + " " ) && clirrMethod.contains( javaExecutable.getName() + "(" )
2941 && clirrMethod.contains( "(" + params.toString() + ")" ) )
2942 {
2943 return true;
2944 }
2945 }
2946
2947 return false;
2948 }
2949
2950
2951
2952
2953
2954
2955
2956
2957 private Class<?> getClass( String className )
2958 throws MojoExecutionException
2959 {
2960 try
2961 {
2962 return ClassUtils.getClass( getProjectClassLoader(), className, false );
2963 }
2964 catch ( ClassNotFoundException e )
2965 {
2966 throw new MojoExecutionException( "ClassNotFoundException: " + e.getMessage(), e );
2967 }
2968 }
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985 private Class<?> getClass( JavaClass currentClass, String exceptionClassName )
2986 {
2987 String[] potentialClassNames =
2988 new String[]{ exceptionClassName, currentClass.getPackage().getName() + "." + exceptionClassName,
2989 currentClass.getPackage().getName() + "." + currentClass.getName() + "$" + exceptionClassName,
2990 "java.lang." + exceptionClassName };
2991
2992 Class<?> clazz = null;
2993 for ( String potentialClassName : potentialClassNames )
2994 {
2995 try
2996 {
2997 clazz = getClass( potentialClassName );
2998 }
2999 catch ( MojoExecutionException e )
3000 {
3001
3002 }
3003 if ( clazz != null )
3004 {
3005 return clazz;
3006 }
3007 }
3008
3009 return null;
3010 }
3011
3012
3013
3014
3015 private void addSinceClasses( JavaClass javaClass )
3016 {
3017 if ( sinceClasses == null )
3018 {
3019 sinceClasses = new ArrayList<>();
3020 }
3021 sinceClasses.add( javaClass.getFullyQualifiedName() );
3022 }
3023
3024 private boolean sinceClassesContains( JavaClass javaClass )
3025 {
3026 return sinceClasses.contains( javaClass.getFullyQualifiedName() );
3027 }
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042 private static void writeFile( final File javaFile, final String encoding, final String content )
3043 throws IOException
3044 {
3045 String unified = StringUtils.unifyLineSeparators( content );
3046 FileUtils.fileWrite( javaFile, encoding, unified );
3047 }
3048
3049
3050
3051
3052
3053 private static String getFullClirrGoal()
3054 {
3055 StringBuilder sb = new StringBuilder();
3056
3057 sb.append( CLIRR_MAVEN_PLUGIN_GROUPID ).append( ":" ).append( CLIRR_MAVEN_PLUGIN_ARTIFACTID ).append( ":" );
3058
3059 String clirrVersion = CLIRR_MAVEN_PLUGIN_VERSION;
3060 InputStream resourceAsStream = null;
3061 try
3062 {
3063 String resource = "META-INF/maven/" + CLIRR_MAVEN_PLUGIN_GROUPID + "/" + CLIRR_MAVEN_PLUGIN_ARTIFACTID
3064 + "/pom.properties";
3065 resourceAsStream = AbstractFixJavadocMojo.class.getClassLoader().getResourceAsStream( resource );
3066
3067 if ( resourceAsStream != null )
3068 {
3069 Properties properties = new Properties();
3070 properties.load( resourceAsStream );
3071 resourceAsStream.close();
3072 resourceAsStream = null;
3073 if ( StringUtils.isNotEmpty( properties.getProperty( "version" ) ) )
3074 {
3075 clirrVersion = properties.getProperty( "version" );
3076 }
3077 }
3078 }
3079 catch ( IOException e )
3080 {
3081
3082 }
3083 finally
3084 {
3085 IOUtil.close( resourceAsStream );
3086 }
3087
3088 sb.append( clirrVersion ).append( ":" ).append( CLIRR_MAVEN_PLUGIN_GOAL );
3089
3090 return sb.toString();
3091 }
3092
3093
3094
3095
3096
3097
3098
3099 private static String getDefaultClassJavadocComment( final JavaClass javaClass )
3100 {
3101 StringBuilder sb = new StringBuilder();
3102
3103 sb.append( "<p>" );
3104 if ( javaClass.isAbstract() )
3105 {
3106 sb.append( "Abstract " );
3107 }
3108
3109 sb.append( javaClass.getName() );
3110
3111 if ( !javaClass.isInterface() )
3112 {
3113 sb.append( " class." );
3114 }
3115 else
3116 {
3117 sb.append( " interface." );
3118 }
3119
3120 sb.append( "</p>" );
3121
3122 return sb.toString();
3123 }
3124
3125
3126
3127
3128
3129
3130
3131 private static String getDefaultMethodJavadocComment( final JavaExecutable javaExecutable )
3132 {
3133 if ( javaExecutable instanceof JavaConstructor )
3134 {
3135 return "<p>Constructor for " + javaExecutable.getName() + ".</p>";
3136 }
3137
3138 if ( javaExecutable.getName().length() > 3 && ( javaExecutable.getName().startsWith( "get" )
3139 || javaExecutable.getName().startsWith( "set" ) ) )
3140 {
3141 String field = StringUtils.lowercaseFirstLetter( javaExecutable.getName().substring( 3 ) );
3142
3143 JavaClass clazz = javaExecutable.getDeclaringClass();
3144
3145 if ( clazz.getFieldByName( field ) == null )
3146 {
3147 return "<p>" + javaExecutable.getName() + ".</p>";
3148 }
3149
3150 StringBuilder sb = new StringBuilder();
3151
3152 sb.append( "<p>" );
3153 if ( javaExecutable.getName().startsWith( "get" ) )
3154 {
3155 sb.append( "Getter " );
3156 }
3157 else if ( javaExecutable.getName().startsWith( "set" ) )
3158 {
3159 sb.append( "Setter " );
3160 }
3161 sb.append( "for the field <code>" ).append( field ).append( "</code>.</p>" );
3162
3163 return sb.toString();
3164 }
3165
3166 return "<p>" + javaExecutable.getName() + ".</p>";
3167 }
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184 private static boolean hasInheritedTag( final String content )
3185 {
3186 final String inheritedTagPattern =
3187 "^\\s*(\\/\\*\\*)?(\\s*(\\*)?)*(\\{)@inheritDoc\\s*(\\})(\\s*(\\*)?)*(\\*\\/)?$";
3188 return Pattern.matches( inheritedTagPattern, StringUtils.removeDuplicateWhitespace( content ) );
3189 }
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229 static String getJavadocComment( final String javaClassContent, final JavaAnnotatedElement entity )
3230 throws IOException
3231 {
3232 if ( entity.getComment() == null )
3233 {
3234 return "";
3235 }
3236
3237 String originalJavadoc = extractOriginalJavadocContent( javaClassContent, entity );
3238
3239 StringBuilder sb = new StringBuilder();
3240 BufferedReader lr = new BufferedReader( new StringReader( originalJavadoc ) );
3241 String line;
3242 while ( ( line = lr.readLine() ) != null )
3243 {
3244 String l = StringUtils.removeDuplicateWhitespace( line.trim() );
3245 if ( l.startsWith( "* @" ) || l.startsWith( "*@" ) )
3246 {
3247 break;
3248 }
3249 sb.append( line ).append( EOL );
3250 }
3251
3252 return trimRight( sb.toString() );
3253 }
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295 String getJavadocComment( final String javaClassContent, final JavaAnnotatedElement entity,
3296 final DocletTag docletTag )
3297 throws IOException
3298 {
3299 if ( docletTag.getValue() == null || docletTag.getParameters().isEmpty() )
3300 {
3301 return "";
3302 }
3303
3304 String originalJavadoc = extractOriginalJavadocContent( javaClassContent, entity );
3305
3306 StringBuilder sb = new StringBuilder();
3307 BufferedReader lr = new BufferedReader( new StringReader( originalJavadoc ) );
3308 String line;
3309 boolean found = false;
3310
3311
3312 Pattern p = Pattern.compile( "(\\s*\\*\\s?@" + docletTag.getName() + ")\\s+"
3313 + "(\\Q" + docletTag.getValue().split( "\r\n|\r|\n" )[0] + "\\E)" );
3314
3315 while ( ( line = lr.readLine() ) != null )
3316 {
3317 Matcher m = p.matcher( line );
3318 if ( m.matches() )
3319 {
3320 if ( fixTag( LINK_TAG ) )
3321 {
3322 line = replaceLinkTags( line, entity );
3323 }
3324 sb.append( line ).append( EOL );
3325 found = true;
3326 }
3327 else
3328 {
3329 if ( line.trim().startsWith( "* @" ) || line.trim().startsWith( "*@" ) )
3330 {
3331 found = false;
3332 }
3333 if ( found )
3334 {
3335 if ( fixTag( LINK_TAG ) )
3336 {
3337 line = replaceLinkTags( line, entity );
3338 }
3339 sb.append( line ).append( EOL );
3340 }
3341 }
3342 }
3343
3344 return trimRight( sb.toString() );
3345 }
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392 static String extractOriginalJavadoc( final String javaClassContent, final JavaAnnotatedElement entity )
3393 throws IOException
3394 {
3395 if ( entity.getComment() == null )
3396 {
3397 return "";
3398 }
3399
3400 String[] javaClassContentLines = getLines( javaClassContent );
3401 List<String> list = new LinkedList<>();
3402 for ( int i = entity.getLineNumber() - 2; i >= 0; i-- )
3403 {
3404 String line = javaClassContentLines[i];
3405
3406 list.add( trimRight( line ) );
3407 if ( line.trim().startsWith( START_JAVADOC ) )
3408 {
3409 break;
3410 }
3411 }
3412
3413 Collections.reverse( list );
3414
3415 return StringUtils.join( list.iterator(), EOL );
3416 }
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459 static String extractOriginalJavadocContent( final String javaClassContent,
3460 final JavaAnnotatedElement entity )
3461 throws IOException
3462 {
3463 if ( entity.getComment() == null )
3464 {
3465 return "";
3466 }
3467
3468 String originalJavadoc = extractOriginalJavadoc( javaClassContent, entity );
3469 int index = originalJavadoc.indexOf( START_JAVADOC );
3470 if ( index != -1 )
3471 {
3472 originalJavadoc = originalJavadoc.substring( index + START_JAVADOC.length() );
3473 }
3474 index = originalJavadoc.indexOf( END_JAVADOC );
3475 if ( index != -1 )
3476 {
3477 originalJavadoc = originalJavadoc.substring( 0, index );
3478 }
3479 if ( originalJavadoc.startsWith( "\r\n" ) )
3480 {
3481 originalJavadoc = originalJavadoc.substring( 2 );
3482 }
3483 else if ( originalJavadoc.startsWith( "\n" ) || originalJavadoc.startsWith( "\r" ) )
3484 {
3485 originalJavadoc = originalJavadoc.substring( 1 );
3486 }
3487
3488 return trimRight( originalJavadoc );
3489 }
3490
3491
3492
3493
3494
3495
3496
3497 private static String removeLastEmptyJavadocLines( final String content )
3498 throws IOException
3499 {
3500 if ( !content.contains( EOL ) )
3501 {
3502 return content;
3503 }
3504
3505 String[] lines = getLines( content );
3506 if ( lines.length == 1 )
3507 {
3508 return content;
3509 }
3510
3511 List<String> linesList = new LinkedList<>( Arrays.asList( lines ) );
3512
3513 Collections.reverse( linesList );
3514
3515 for ( Iterator<String> it = linesList.iterator(); it.hasNext(); )
3516 {
3517 String line = it.next();
3518
3519 if ( line.trim().equals( "*" ) )
3520 {
3521 it.remove();
3522 }
3523 else
3524 {
3525 break;
3526 }
3527 }
3528
3529 Collections.reverse( linesList );
3530
3531 return StringUtils.join( linesList.iterator(), EOL );
3532 }
3533
3534
3535
3536
3537
3538
3539
3540 private static String alignIndentationJavadocLines( final String content, final String indent )
3541 throws IOException
3542 {
3543 StringBuilder sb = new StringBuilder();
3544 for ( String line : getLines( content ) )
3545 {
3546 if ( sb.length() > 0 )
3547 {
3548 sb.append( EOL );
3549 }
3550 if ( !line.trim().startsWith( "*" ) )
3551 {
3552 line = "*" + line;
3553 }
3554 sb.append( indent ).append( " " ).append( trimLeft( line ) );
3555 }
3556
3557 return sb.toString();
3558 }
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572 private static String autodetectIndentation( final String line )
3573 {
3574 if ( StringUtils.isEmpty( line ) )
3575 {
3576 return "";
3577 }
3578
3579 return line.substring( 0, line.indexOf( trimLeft( line ) ) );
3580 }
3581
3582
3583
3584
3585
3586
3587 private static String[] getLines( final String content )
3588 throws IOException
3589 {
3590 List<String> lines = new LinkedList<>();
3591
3592 BufferedReader reader = new BufferedReader( new StringReader( content ) );
3593 String line = reader.readLine();
3594 while ( line != null )
3595 {
3596 lines.add( line );
3597 line = reader.readLine();
3598 }
3599
3600 return lines.toArray( new String[lines.size()] );
3601 }
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617 private static String trimLeft( final String text )
3618 {
3619 if ( StringUtils.isEmpty( text ) || StringUtils.isEmpty( text.trim() ) )
3620 {
3621 return "";
3622 }
3623
3624 String textTrimmed = text.trim();
3625 return text.substring( text.indexOf( textTrimmed ) );
3626 }
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641 private static String trimRight( final String text )
3642 {
3643 if ( StringUtils.isEmpty( text ) || StringUtils.isEmpty( text.trim() ) )
3644 {
3645 return "";
3646 }
3647
3648 String textTrimmed = text.trim();
3649 return text.substring( 0, text.indexOf( textTrimmed ) + textTrimmed.length() );
3650 }
3651
3652
3653
3654
3655
3656 class JavaEntityTags
3657 {
3658 private final JavaAnnotatedElement entity;
3659
3660 private final boolean isJavaMethod;
3661
3662
3663
3664
3665 private List<String> namesTags;
3666
3667
3668
3669
3670 private Map<String, String> tagParams;
3671
3672 private Set<String> documentedParams = new HashSet<>();
3673
3674
3675
3676
3677 private String tagReturn;
3678
3679
3680
3681
3682 private Map<String, String> tagThrows;
3683
3684
3685
3686
3687 private List<String> unknownsTags;
3688
3689 JavaEntityTags( JavaAnnotatedElement entity, boolean isJavaMethod )
3690 {
3691 this.entity = entity;
3692 this.isJavaMethod = isJavaMethod;
3693 this.namesTags = new LinkedList<>();
3694 this.tagParams = new LinkedHashMap<>();
3695 this.tagThrows = new LinkedHashMap<>();
3696 this.unknownsTags = new LinkedList<>();
3697 }
3698
3699 public List<String> getNamesTags()
3700 {
3701 return namesTags;
3702 }
3703
3704 public String getJavadocReturnTag()
3705 {
3706 return tagReturn;
3707 }
3708
3709 public void setJavadocReturnTag( String s )
3710 {
3711 tagReturn = s;
3712 }
3713
3714 public List<String> getUnknownTags()
3715 {
3716 return unknownsTags;
3717 }
3718
3719 public void putJavadocParamTag( String paramName, String paramValue, String originalJavadocTag )
3720 {
3721 documentedParams.add( paramName );
3722 tagParams.put( paramValue, originalJavadocTag );
3723 }
3724
3725 public String getJavadocParamTag( String paramValue )
3726 {
3727 String originalJavadocTag = tagParams.get( paramValue );
3728 if ( originalJavadocTag == null && getLog().isWarnEnabled() )
3729 {
3730 getLog().warn( getMessage( paramValue, "javaEntityTags.tagParams" ) );
3731 }
3732 return originalJavadocTag;
3733 }
3734
3735 public boolean hasJavadocParamTag( String paramName )
3736 {
3737 return documentedParams.contains( paramName );
3738 }
3739
3740 public void putJavadocThrowsTag( String paramName, String originalJavadocTag )
3741 {
3742 tagThrows.put( paramName, originalJavadocTag );
3743 }
3744
3745 public String getJavadocThrowsTag( String paramName )
3746 {
3747 return getJavadocThrowsTag( paramName, false );
3748 }
3749
3750 public String getJavadocThrowsTag( String paramName, boolean nullable )
3751 {
3752 String originalJavadocTag = tagThrows.get( paramName );
3753 if ( !nullable && originalJavadocTag == null && getLog().isWarnEnabled() )
3754 {
3755 getLog().warn( getMessage( paramName, "javaEntityTags.tagThrows" ) );
3756 }
3757
3758 return originalJavadocTag;
3759 }
3760
3761 private String getMessage( String paramName, String mapName )
3762 {
3763 StringBuilder msg = new StringBuilder();
3764 msg.append( "No param '" ).append( paramName ).append( "' key found in " ).append( mapName )
3765 .append( " for the entity: " );
3766 if ( isJavaMethod )
3767 {
3768 JavaMethod javaMethod = (JavaMethod) entity;
3769 msg.append( getJavaMethodAsString( javaMethod ) );
3770 }
3771 else
3772 {
3773 JavaClass javaClass = (JavaClass) entity;
3774 msg.append( javaClass.getFullyQualifiedName() );
3775 }
3776
3777 return msg.toString();
3778 }
3779
3780
3781
3782
3783 @Override
3784 public String toString()
3785 {
3786 StringBuilder sb = new StringBuilder();
3787
3788 sb.append( "namesTags=" ).append( namesTags ).append( "\n" );
3789 sb.append( "tagParams=" ).append( tagParams ).append( "\n" );
3790 sb.append( "tagReturn=" ).append( tagReturn ).append( "\n" );
3791 sb.append( "tagThrows=" ).append( tagThrows ).append( "\n" );
3792 sb.append( "unknownsTags=" ).append( unknownsTags ).append( "\n" );
3793
3794 return sb.toString();
3795 }
3796 }
3797 }