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