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