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