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