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.getProperty("line.separator");
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.<String>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.<String>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 attempt to interact 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("The Maven Team strongly recommends the use of a SCM software BEFORE executing this goal.");
583 getLog().warn("");
584
585 while (true) {
586 getLog().info("Are you sure to proceed? [Y]es [N]o");
587
588 try {
589 String userExpression = inputHandler.readLine();
590 if (userExpression == null || JavadocUtil.equalsIgnoreCase(userExpression, "Y", "Yes")) {
591 getLog().info("OK, let's proceed...");
592 break;
593 }
594 if (JavadocUtil.equalsIgnoreCase(userExpression, "N", "No")) {
595 getLog().info("No changes in your sources occur.");
596 return false;
597 }
598 } catch (IOException e) {
599 throw new MojoExecutionException("Unable to read from standard input.", e);
600 }
601 }
602
603 return true;
604 }
605
606
607
608
609 private File getProjectSourceDirectory() {
610 return new File(project.getBuild().getSourceDirectory());
611 }
612
613
614
615
616
617
618 private void executeClirr() throws MavenInvocationException {
619 if (ignoreClirr) {
620 getLog().info("Clirr is ignored.");
621 return;
622 }
623
624 String clirrGoal = getFullClirrGoal();
625
626
627 File clirrTextOutputFile = FileUtils.createTempFile(
628 "clirr", ".txt", new File(project.getBuild().getDirectory()));
629 Properties properties = new Properties();
630 properties.put("textOutputFile", clirrTextOutputFile.getAbsolutePath());
631 properties.put("comparisonVersion", comparisonVersion);
632 properties.put("failOnError", "false");
633 properties.put("minSeverity", "info");
634
635 File invokerDir = new File(project.getBuild().getDirectory(), "invoker");
636 invokerDir.mkdirs();
637 File invokerLogFile = FileUtils.createTempFile("clirr-maven-plugin", ".txt", invokerDir);
638
639 JavadocUtil.invokeMaven(
640 getLog(),
641 session.getRepositorySession().getLocalRepository().getBasedir(),
642 project.getFile(),
643 Collections.singletonList(clirrGoal),
644 properties,
645 invokerLogFile,
646 session.getRequest().getGlobalSettingsFile(),
647 session.getRequest().getUserSettingsFile(),
648 session.getRequest().getGlobalToolchainsFile(),
649 session.getRequest().getUserToolchainsFile());
650
651 try {
652 if (invokerLogFile.exists()) {
653 String invokerLogContent = StringUtils.unifyLineSeparators(FileUtils.fileRead(invokerLogFile, "UTF-8"));
654
655 final String artifactNotFoundMsg = "Unable to find a previous version of the project in the repository";
656 if (invokerLogContent.contains(artifactNotFoundMsg)) {
657 getLog().warn("No previous artifact has been deployed, Clirr is ignored.");
658 return;
659 }
660 }
661 } catch (IOException e) {
662 getLog().debug("IOException: " + e.getMessage());
663 }
664
665 try {
666 parseClirrTextOutputFile(clirrTextOutputFile);
667 } catch (IOException e) {
668 if (getLog().isDebugEnabled()) {
669 getLog().debug("IOException: " + e.getMessage(), e);
670 }
671 getLog().info("IOException when parsing Clirr output '" + clirrTextOutputFile.getAbsolutePath()
672 + "', Clirr is ignored.");
673 }
674 }
675
676
677
678
679
680 private void parseClirrTextOutputFile(File clirrTextOutputFile) throws IOException {
681 if (!clirrTextOutputFile.exists()) {
682 if (getLog().isInfoEnabled()) {
683 getLog().info("No Clirr output file '" + clirrTextOutputFile.getAbsolutePath()
684 + "' exists, Clirr is ignored.");
685 }
686 return;
687 }
688
689 if (getLog().isInfoEnabled()) {
690 getLog().info("Clirr output file was created: " + clirrTextOutputFile.getAbsolutePath());
691 }
692
693 clirrNewClasses = new LinkedList<>();
694 clirrNewMethods = new LinkedHashMap<>();
695
696 try (BufferedReader reader = Files.newBufferedReader(clirrTextOutputFile.toPath(), StandardCharsets.UTF_8)) {
697
698 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
699 String[] split = StringUtils.split(line, ":");
700 if (split.length != 4) {
701 if (getLog().isDebugEnabled()) {
702 getLog().debug("Unable to parse the Clirr line: " + line);
703 }
704 continue;
705 }
706
707 int code;
708 try {
709 code = Integer.parseInt(split[1].trim());
710 } catch (NumberFormatException e) {
711 if (getLog().isDebugEnabled()) {
712 getLog().debug("Unable to parse the clirr line: " + line);
713 }
714 continue;
715 }
716
717
718
719
720
721
722
723 switch (code) {
724 case 7011:
725 methodAdded(split);
726 break;
727 case 7012:
728 methodAdded(split);
729 break;
730 case 8000:
731 clirrNewClasses.add(split[2].trim());
732 break;
733 default:
734 break;
735 }
736
737 }
738 }
739 if (clirrNewClasses.isEmpty() && clirrNewMethods.isEmpty()) {
740 getLog().info("Clirr NOT found API differences.");
741 } else {
742 getLog().info("Clirr found API differences, i.e. new classes/interfaces or methods.");
743 }
744 }
745
746 private void methodAdded(String[] split) {
747 List<String> list = clirrNewMethods.get(split[2].trim());
748 if (list == null) {
749 list = new ArrayList<>();
750 }
751 String[] splits2 = StringUtils.split(split[3].trim(), "'");
752 if (splits2.length != 3) {
753 return;
754 }
755 list.add(splits2[1].trim());
756 clirrNewMethods.put(split[2].trim(), list);
757 }
758
759
760
761
762
763 private boolean fixTag(String tag) {
764 if (fixTagsSplitted.length == 1 && fixTagsSplitted[0].equals(FIX_TAGS_ALL)) {
765 return true;
766 }
767
768 for (String aFixTagsSplitted : fixTagsSplitted) {
769 if (aFixTagsSplitted.trim().equals(tag)) {
770 return true;
771 }
772 }
773
774 return false;
775 }
776
777
778
779
780
781
782
783
784
785 private Collection<JavaClass> getQdoxClasses() throws IOException, MojoExecutionException {
786 if ("pom".equalsIgnoreCase(project.getPackaging())) {
787 getLog().warn("This project has 'pom' packaging, no Java sources is available.");
788 return null;
789 }
790
791 List<File> javaFiles = new LinkedList<>();
792 for (String sourceRoot : getProjectSourceRoots(project)) {
793 File f = new File(sourceRoot);
794 if (f.isDirectory()) {
795 javaFiles.addAll(FileUtils.getFiles(f, includes, excludes, true));
796 } else {
797 if (getLog().isWarnEnabled()) {
798 getLog().warn(f + " doesn't exist. Ignored it.");
799 }
800 }
801 }
802
803 ClassLibraryBuilder classLibraryBuilder = new OrderedClassLibraryBuilder();
804 classLibraryBuilder.appendClassLoader(getProjectClassLoader());
805
806 JavaProjectBuilder builder = new JavaProjectBuilder(classLibraryBuilder);
807 builder.setEncoding(encoding);
808 for (File f : javaFiles) {
809 if (!f.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".java") && getLog().isWarnEnabled()) {
810 getLog().warn("'" + f + "' is not a Java file. Ignored it.");
811 continue;
812 }
813
814 try {
815 builder.addSource(f);
816 } catch (ParseException e) {
817 if (getLog().isWarnEnabled()) {
818 getLog().warn("QDOX ParseException: " + e.getMessage() + ". Can't fix it.");
819 }
820 }
821 }
822
823 return builder.getClasses();
824 }
825
826
827
828
829
830 private ClassLoader getProjectClassLoader() throws MojoExecutionException {
831 if (projectClassLoader == null) {
832 List<String> classPath;
833 try {
834 classPath = getCompileClasspathElements(project);
835 } catch (DependencyResolutionRequiredException e) {
836 throw new MojoExecutionException("DependencyResolutionRequiredException: " + e.getMessage(), e);
837 }
838
839 List<URL> urls = new ArrayList<>(classPath.size());
840 for (String filename : classPath) {
841 try {
842 urls.add(new File(filename).toURI().toURL());
843 } catch (MalformedURLException | IllegalArgumentException e) {
844 throw new MojoExecutionException("MalformedURLException: " + e.getMessage(), e);
845 }
846 }
847
848 ClassLoader parent = null;
849 if (JAVA_VERSION.isAtLeast("9")) {
850 try {
851 parent = (ClassLoader) MethodUtils.invokeStaticMethod(ClassLoader.class, "getPlatformClassLoader");
852 } catch (Exception e) {
853 throw new MojoExecutionException(
854 "Failed to invoke ClassLoader#getPlatformClassLoader() dynamically", e);
855 }
856 }
857
858 projectClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
859 }
860
861 return projectClassLoader;
862 }
863
864
865
866
867
868
869
870
871 private void processFix(JavaClass javaClass) throws IOException, MojoExecutionException {
872
873 if (javaClass.isInner()) {
874 return;
875 }
876
877 File javaFile;
878 try {
879 javaFile = Paths.get(javaClass.getSource().getURL().toURI()).toFile();
880 } catch (URISyntaxException e) {
881 throw new MojoExecutionException(e.getMessage());
882 }
883
884
885 final String originalContent = StringUtils.unifyLineSeparators(FileUtils.fileRead(javaFile, encoding));
886
887 if (getLog().isDebugEnabled()) {
888 getLog().debug("Analyzing " + javaClass.getFullyQualifiedName());
889 }
890
891 final StringWriter stringWriter = new StringWriter();
892 boolean changeDetected = false;
893 try (BufferedReader reader = new BufferedReader(new StringReader(originalContent))) {
894
895 int lineNumber = 0;
896 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
897 lineNumber++;
898 final String indent = autodetectIndentation(line);
899
900
901 if (javaClass.getComment() == null
902 && javaClass.getAnnotations() != null
903 && !javaClass.getAnnotations().isEmpty()) {
904 if (lineNumber == javaClass.getAnnotations().get(0).getLineNumber()) {
905 changeDetected |= fixClassComment(stringWriter, originalContent, javaClass, indent);
906
907 takeCareSingleComment(stringWriter, originalContent, javaClass);
908 }
909 } else if (lineNumber == javaClass.getLineNumber()) {
910 changeDetected |= fixClassComment(stringWriter, originalContent, javaClass, indent);
911
912 takeCareSingleComment(stringWriter, originalContent, javaClass);
913 }
914
915
916 if (javaClass.getFields() != null) {
917 for (JavaField field : javaClass.getFields()) {
918 if (lineNumber == field.getLineNumber()) {
919 changeDetected |= fixFieldComment(stringWriter, javaClass, field, indent);
920 }
921 }
922 }
923
924
925 if (javaClass.getConstructors() != null) {
926 for (JavaConstructor method : javaClass.getConstructors()) {
927 if (lineNumber == method.getLineNumber()) {
928 final boolean commentUpdated =
929 fixMethodComment(stringWriter, originalContent, method, indent);
930 if (commentUpdated) {
931 takeCareSingleComment(stringWriter, originalContent, method);
932 }
933 changeDetected |= commentUpdated;
934 }
935 }
936 }
937
938
939 for (JavaMethod method : javaClass.getMethods()) {
940 int methodLineNumber;
941 if (method.getComment() == null && !method.getAnnotations().isEmpty()) {
942 methodLineNumber = method.getAnnotations().get(0).getLineNumber();
943 } else {
944 methodLineNumber = method.getLineNumber();
945 }
946
947 if (lineNumber == methodLineNumber) {
948 final boolean commentUpdated = fixMethodComment(stringWriter, originalContent, method, indent);
949 if (commentUpdated) {
950 takeCareSingleComment(stringWriter, originalContent, method);
951 }
952 changeDetected |= commentUpdated;
953 }
954 }
955
956 stringWriter.write(line);
957 stringWriter.write(EOL);
958 }
959 }
960
961 if (changeDetected) {
962 if (getLog().isInfoEnabled()) {
963 getLog().info("Saving changes to " + javaClass.getFullyQualifiedName());
964 }
965
966 if (outputDirectory != null
967 && !outputDirectory
968 .getAbsolutePath()
969 .equals(getProjectSourceDirectory().getAbsolutePath())) {
970 String path = StringUtils.replace(
971 javaFile.getAbsolutePath().replaceAll("\\\\", "/"),
972 project.getBuild().getSourceDirectory().replaceAll("\\\\", "/"),
973 "");
974 javaFile = new File(outputDirectory, path);
975 javaFile.getParentFile().mkdirs();
976 }
977 writeFile(javaFile, encoding, stringWriter.toString());
978 } else {
979 if (getLog().isDebugEnabled()) {
980 getLog().debug("No changes made to " + javaClass.getFullyQualifiedName());
981 }
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 private void takeCareSingleComment(
1016 final StringWriter stringWriter, final String originalContent, final JavaAnnotatedElement entity)
1017 throws IOException {
1018 if (entity.getComment() == null) {
1019 return;
1020 }
1021
1022 String javadocComment = trimRight(extractOriginalJavadoc(originalContent, entity));
1023 String extraComment = javadocComment.substring(javadocComment.indexOf(END_JAVADOC) + END_JAVADOC.length());
1024 if (extraComment != null && !extraComment.isEmpty()) {
1025 if (extraComment.contains(EOL)) {
1026 stringWriter.write(extraComment.substring(extraComment.indexOf(EOL) + EOL.length()));
1027 } else {
1028 stringWriter.write(extraComment);
1029 }
1030 stringWriter.write(EOL);
1031 }
1032 }
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045 private boolean fixClassComment(
1046 final StringWriter stringWriter,
1047 final String originalContent,
1048 final JavaClass javaClass,
1049 final String indent)
1050 throws MojoExecutionException, IOException {
1051 if (!fixClassComment) {
1052 return false;
1053 }
1054
1055 if (!isInLevel(javaClass.getModifiers())) {
1056 return false;
1057 }
1058
1059
1060 if (javaClass.getComment() == null) {
1061 addDefaultClassComment(stringWriter, javaClass, indent);
1062 return true;
1063 }
1064
1065
1066 return updateEntityComment(stringWriter, originalContent, javaClass, indent);
1067 }
1068
1069
1070
1071
1072
1073 private boolean isInLevel(List<String> modifiers) {
1074 if (LEVEL_PUBLIC.equalsIgnoreCase(level.trim())) {
1075 return modifiers.contains(LEVEL_PUBLIC);
1076 }
1077
1078 if (LEVEL_PROTECTED.equalsIgnoreCase(level.trim())) {
1079 return modifiers.contains(LEVEL_PUBLIC) || modifiers.contains(LEVEL_PROTECTED);
1080 }
1081
1082 if (LEVEL_PACKAGE.equalsIgnoreCase(level.trim())) {
1083 return !modifiers.contains(LEVEL_PRIVATE);
1084 }
1085
1086
1087 return true;
1088 }
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 private void addDefaultClassComment(
1125 final StringWriter stringWriter, final JavaClass javaClass, final String indent) {
1126 StringBuilder sb = new StringBuilder();
1127
1128 sb.append(indent).append(START_JAVADOC);
1129 sb.append(EOL);
1130 sb.append(indent).append(SEPARATOR_JAVADOC);
1131 sb.append(getDefaultClassJavadocComment(javaClass));
1132 sb.append(EOL);
1133
1134 appendSeparator(sb, indent);
1135
1136 appendDefaultAuthorTag(sb, indent);
1137
1138 appendDefaultVersionTag(sb, indent);
1139
1140 if (fixTag(SINCE_TAG)) {
1141 if (!ignoreClirr) {
1142 if (isNewClassFromLastVersion(javaClass)) {
1143 appendDefaultSinceTag(sb, indent);
1144 }
1145 } else {
1146 appendDefaultSinceTag(sb, indent);
1147 addSinceClasses(javaClass);
1148 }
1149 }
1150
1151 sb.append(indent).append(" ").append(END_JAVADOC);
1152 sb.append(EOL);
1153
1154 stringWriter.write(sb.toString());
1155 }
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167 private boolean fixFieldComment(
1168 final StringWriter stringWriter, final JavaClass javaClass, final JavaField field, final String indent)
1169 throws IOException {
1170 if (!fixFieldComment) {
1171 return false;
1172 }
1173
1174 if (!javaClass.isInterface() && (!isInLevel(field.getModifiers()) || !field.isStatic())) {
1175 return false;
1176 }
1177
1178
1179 if (field.getComment() == null) {
1180 addDefaultFieldComment(stringWriter, field, indent);
1181 return true;
1182 }
1183
1184
1185 return false;
1186 }
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207 private void addDefaultFieldComment(final StringWriter stringWriter, final JavaField field, final String indent)
1208 throws IOException {
1209 StringBuilder sb = new StringBuilder();
1210
1211 sb.append(indent).append(START_JAVADOC).append(" ");
1212 sb.append("Constant <code>").append(field.getName());
1213
1214 if (StringUtils.isNotEmpty(field.getInitializationExpression())) {
1215 String qualifiedName = field.getType().getFullyQualifiedName();
1216
1217 if (qualifiedName.equals(Byte.TYPE.toString())
1218 || qualifiedName.equals(Short.TYPE.toString())
1219 || qualifiedName.equals(Integer.TYPE.toString())
1220 || qualifiedName.equals(Long.TYPE.toString())
1221 || qualifiedName.equals(Float.TYPE.toString())
1222 || qualifiedName.equals(Double.TYPE.toString())
1223 || qualifiedName.equals(Boolean.TYPE.toString())
1224 || qualifiedName.equals(Character.TYPE.toString())) {
1225 sb.append("=");
1226 sb.append(StringEscapeUtils.escapeHtml4(
1227 field.getInitializationExpression().trim()));
1228 }
1229
1230 if (qualifiedName.equals(String.class.getName())) {
1231 StringBuilder value = new StringBuilder();
1232 String[] lines = getLines(field.getInitializationExpression());
1233 for (String line : lines) {
1234 StringTokenizer token = new StringTokenizer(line.trim(), "\"\n\r");
1235 while (token.hasMoreTokens()) {
1236 String s = token.nextToken();
1237
1238 if (s.trim().equals("+")) {
1239 continue;
1240 }
1241 if (s.trim().endsWith("\\")) {
1242 s += "\"";
1243 }
1244 value.append(s);
1245 }
1246 }
1247
1248 sb.append("=\"");
1249 String escapedValue = StringEscapeUtils.escapeHtml4(value.toString());
1250
1251
1252 if (escapedValue.length() < 40) {
1253 sb.append(escapedValue).append("\"");
1254 } else {
1255 sb.append(escapedValue, 0, 39).append("\"{trunked}");
1256 }
1257
1258 }
1259 }
1260
1261 sb.append("</code> ").append(END_JAVADOC);
1262 sb.append(EOL);
1263
1264 stringWriter.write(sb.toString());
1265 }
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278 private boolean fixMethodComment(
1279 final StringWriter stringWriter,
1280 final String originalContent,
1281 final JavaExecutable javaExecutable,
1282 final String indent)
1283 throws MojoExecutionException, IOException {
1284 if (!fixMethodComment) {
1285 return false;
1286 }
1287
1288 if (!javaExecutable.getDeclaringClass().isInterface() && !isInLevel(javaExecutable.getModifiers())) {
1289 return false;
1290 }
1291
1292
1293 if (javaExecutable.getComment() == null) {
1294 addDefaultMethodComment(stringWriter, javaExecutable, indent);
1295 return true;
1296 }
1297
1298
1299 return updateEntityComment(stringWriter, originalContent, javaExecutable, indent);
1300 }
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 private void addDefaultMethodComment(
1341 final StringWriter stringWriter, final JavaExecutable javaExecutable, final String indent)
1342 throws MojoExecutionException {
1343 StringBuilder sb = new StringBuilder();
1344
1345
1346 if (isInherited(javaExecutable)) {
1347 sb.append(indent).append(INHERITED_JAVADOC);
1348 sb.append(EOL);
1349
1350 stringWriter.write(sb.toString());
1351 return;
1352 }
1353
1354 sb.append(indent).append(START_JAVADOC);
1355 sb.append(EOL);
1356 sb.append(indent).append(SEPARATOR_JAVADOC);
1357 sb.append(getDefaultMethodJavadocComment(javaExecutable));
1358 sb.append(EOL);
1359
1360 boolean separatorAdded = false;
1361 if (fixTag(PARAM_TAG)) {
1362 if (javaExecutable.getParameters() != null) {
1363 for (JavaParameter javaParameter : javaExecutable.getParameters()) {
1364 separatorAdded = appendDefaultParamTag(sb, indent, separatorAdded, javaParameter);
1365 }
1366 }
1367
1368 if (javaExecutable.getTypeParameters() != null) {
1369 for (JavaTypeVariable<JavaGenericDeclaration> typeParam : javaExecutable.getTypeParameters()) {
1370 separatorAdded = appendDefaultParamTag(sb, indent, separatorAdded, typeParam);
1371 }
1372 }
1373 }
1374 if (javaExecutable instanceof JavaMethod) {
1375 JavaMethod javaMethod = (JavaMethod) javaExecutable;
1376 if (fixTag(RETURN_TAG)
1377 && javaMethod.getReturns() != null
1378 && !javaMethod.getReturns().isVoid()) {
1379 separatorAdded = appendDefaultReturnTag(sb, indent, separatorAdded, javaMethod);
1380 }
1381 }
1382 if (fixTag(THROWS_TAG) && javaExecutable.getExceptions() != null) {
1383 for (JavaType exception : javaExecutable.getExceptions()) {
1384 separatorAdded = appendDefaultThrowsTag(sb, indent, separatorAdded, exception);
1385 }
1386 }
1387 if (fixTag(SINCE_TAG) && isNewMethodFromLastRevision(javaExecutable)) {
1388 separatorAdded = appendDefaultSinceTag(sb, indent, separatorAdded);
1389 }
1390
1391 sb.append(indent).append(" ").append(END_JAVADOC);
1392 sb.append(EOL);
1393
1394 stringWriter.write(sb.toString());
1395 }
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406 private boolean updateEntityComment(
1407 final StringWriter stringWriter,
1408 final String originalContent,
1409 final JavaAnnotatedElement entity,
1410 final String indent)
1411 throws MojoExecutionException, IOException {
1412 boolean changeDetected = false;
1413
1414 String old = null;
1415 String s = stringWriter.toString();
1416 int i = s.lastIndexOf(START_JAVADOC);
1417 if (i != -1) {
1418 String tmp = s.substring(0, i);
1419 if (tmp.lastIndexOf(EOL) != -1) {
1420 tmp = tmp.substring(0, tmp.lastIndexOf(EOL));
1421 }
1422
1423 old = stringWriter.getBuffer().substring(i);
1424
1425 stringWriter.getBuffer().delete(0, stringWriter.getBuffer().length());
1426 stringWriter.write(tmp);
1427 stringWriter.write(EOL);
1428 } else {
1429 changeDetected = true;
1430 }
1431
1432 updateJavadocComment(stringWriter, originalContent, entity, indent);
1433
1434 if (changeDetected) {
1435 return true;
1436 }
1437
1438 return !stringWriter.getBuffer().substring(i).equals(old);
1439 }
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449 private void updateJavadocComment(
1450 final StringWriter stringWriter,
1451 final String originalContent,
1452 final JavaAnnotatedElement entity,
1453 final String indent)
1454 throws MojoExecutionException, IOException {
1455 if (entity.getComment() == null
1456 && (entity.getTags() == null || entity.getTags().isEmpty())) {
1457 return;
1458 }
1459
1460 boolean isJavaExecutable = entity instanceof JavaExecutable;
1461
1462 StringBuilder sb = new StringBuilder();
1463
1464
1465 if (isJavaExecutable) {
1466 JavaExecutable javaMethod = (JavaExecutable) entity;
1467
1468 if (isInherited(javaMethod)) {
1469
1470 if (StringUtils.isEmpty(javaMethod.getComment())) {
1471 sb.append(indent).append(INHERITED_JAVADOC);
1472 sb.append(EOL);
1473 stringWriter.write(sb.toString());
1474 return;
1475 }
1476
1477 String javadoc = getJavadocComment(originalContent, javaMethod);
1478
1479
1480 if (hasInheritedTag(javadoc)
1481 && (javaMethod.getTags() == null || javaMethod.getTags().isEmpty())) {
1482 sb.append(indent).append(INHERITED_JAVADOC);
1483 sb.append(EOL);
1484 stringWriter.write(sb.toString());
1485 return;
1486 }
1487
1488 if (javadoc.contains(START_JAVADOC)) {
1489 javadoc = javadoc.substring(javadoc.indexOf(START_JAVADOC) + START_JAVADOC.length());
1490 }
1491 if (javadoc.contains(END_JAVADOC)) {
1492 javadoc = javadoc.substring(0, javadoc.indexOf(END_JAVADOC));
1493 }
1494
1495 sb.append(indent).append(START_JAVADOC);
1496 sb.append(EOL);
1497 if (!javadoc.contains(INHERITED_TAG)) {
1498 sb.append(indent).append(SEPARATOR_JAVADOC).append(INHERITED_TAG);
1499 sb.append(EOL);
1500 appendSeparator(sb, indent);
1501 }
1502 javadoc = removeLastEmptyJavadocLines(javadoc);
1503 javadoc = alignIndentationJavadocLines(javadoc, indent);
1504 sb.append(javadoc);
1505 sb.append(EOL);
1506 if (javaMethod.getTags() != null) {
1507 for (DocletTag docletTag : javaMethod.getTags()) {
1508
1509 if (JavadocUtil.equals(docletTag.getName(), PARAM_TAG, RETURN_TAG, THROWS_TAG)) {
1510 continue;
1511 }
1512
1513 String s = getJavadocComment(originalContent, entity, docletTag);
1514 s = removeLastEmptyJavadocLines(s);
1515 s = alignIndentationJavadocLines(s, indent);
1516 sb.append(s);
1517 sb.append(EOL);
1518 }
1519 }
1520 sb.append(indent).append(" ").append(END_JAVADOC);
1521 sb.append(EOL);
1522
1523 if (hasInheritedTag(sb.toString().trim())) {
1524 sb = new StringBuilder();
1525 sb.append(indent).append(INHERITED_JAVADOC);
1526 sb.append(EOL);
1527 stringWriter.write(sb.toString());
1528 return;
1529 }
1530
1531 stringWriter.write(sb.toString());
1532 return;
1533 }
1534 }
1535
1536 sb.append(indent).append(START_JAVADOC);
1537 sb.append(EOL);
1538
1539
1540 if (StringUtils.isNotEmpty(entity.getComment())) {
1541 updateJavadocComment(sb, originalContent, entity, indent);
1542 } else {
1543 addDefaultJavadocComment(sb, entity, indent, isJavaExecutable);
1544 }
1545
1546
1547 updateJavadocTags(sb, originalContent, entity, indent, isJavaExecutable);
1548
1549 sb = new StringBuilder(removeLastEmptyJavadocLines(sb.toString())).append(EOL);
1550
1551 sb.append(indent).append(" ").append(END_JAVADOC);
1552 sb.append(EOL);
1553
1554 stringWriter.write(sb.toString());
1555 }
1556
1557
1558
1559
1560
1561
1562
1563
1564 private void updateJavadocComment(
1565 final StringBuilder sb,
1566 final String originalContent,
1567 final JavaAnnotatedElement entity,
1568 final String indent)
1569 throws IOException {
1570 String comment = getJavadocComment(originalContent, entity);
1571 comment = removeLastEmptyJavadocLines(comment);
1572 comment = alignIndentationJavadocLines(comment, indent);
1573
1574 if (comment.contains(START_JAVADOC)) {
1575 comment = comment.substring(comment.indexOf(START_JAVADOC) + START_JAVADOC.length());
1576 comment = indent + SEPARATOR_JAVADOC + comment.trim();
1577 }
1578 if (comment.contains(END_JAVADOC)) {
1579 comment = comment.substring(0, comment.indexOf(END_JAVADOC));
1580 }
1581
1582 if (fixTag(LINK_TAG)) {
1583 comment = replaceLinkTags(comment, entity);
1584 }
1585
1586 String[] lines = getLines(comment);
1587 for (String line : lines) {
1588 sb.append(indent).append(" ").append(line.trim());
1589 sb.append(EOL);
1590 }
1591 }
1592
1593 private static final Pattern REPLACE_LINK_TAGS_PATTERN = Pattern.compile("\\{@link\\s");
1594
1595 static String replaceLinkTags(String comment, JavaAnnotatedElement entity) {
1596 StringBuilder resolvedComment = new StringBuilder();
1597
1598 Matcher linktagMatcher = REPLACE_LINK_TAGS_PATTERN.matcher(comment);
1599 int startIndex = 0;
1600 while (linktagMatcher.find()) {
1601 int startName = linktagMatcher.end();
1602 resolvedComment.append(comment, startIndex, startName);
1603 int endName = comment.indexOf("}", startName);
1604 if (endName >= 0) {
1605 String name;
1606 String link = comment.substring(startName, endName);
1607 int hashIndex = link.indexOf('#');
1608 if (hashIndex >= 0) {
1609 name = link.substring(0, hashIndex);
1610 } else {
1611 name = link;
1612 }
1613 if (StringUtils.isNotBlank(name)) {
1614 String typeName;
1615 if (entity instanceof JavaClass) {
1616 JavaClass clazz = (JavaClass) entity;
1617 typeName = TypeResolver.byClassName(
1618 clazz.getBinaryName(),
1619 clazz.getJavaClassLibrary(),
1620 clazz.getSource().getImports())
1621 .resolveType(name.trim());
1622 } else if (entity instanceof JavaMember) {
1623 JavaClass clazz = ((JavaMember) entity).getDeclaringClass();
1624 typeName = TypeResolver.byClassName(
1625 clazz.getBinaryName(),
1626 clazz.getJavaClassLibrary(),
1627 clazz.getSource().getImports())
1628 .resolveType(name.trim());
1629 } else {
1630 typeName = null;
1631 }
1632
1633 if (typeName == null) {
1634 typeName = name.trim();
1635 } else {
1636 typeName = typeName.replaceAll("\\$", ".");
1637 }
1638
1639 resolvedComment.append(typeName);
1640 }
1641 if (hashIndex >= 0) {
1642 resolvedComment.append(link.substring(hashIndex).trim());
1643 }
1644 startIndex = endName;
1645 } else {
1646 startIndex = startName;
1647 }
1648 }
1649 resolvedComment.append(comment.substring(startIndex));
1650 return resolvedComment.toString();
1651 }
1652
1653
1654
1655
1656
1657
1658
1659 private void addDefaultJavadocComment(
1660 final StringBuilder sb,
1661 final JavaAnnotatedElement entity,
1662 final String indent,
1663 final boolean isJavaExecutable) {
1664 sb.append(indent).append(SEPARATOR_JAVADOC);
1665 if (isJavaExecutable) {
1666 sb.append(getDefaultMethodJavadocComment((JavaExecutable) entity));
1667 } else {
1668 sb.append(getDefaultClassJavadocComment((JavaClass) entity));
1669 }
1670 sb.append(EOL);
1671 }
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682 private void updateJavadocTags(
1683 final StringBuilder sb,
1684 final String originalContent,
1685 final JavaAnnotatedElement entity,
1686 final String indent,
1687 final boolean isJavaExecutable)
1688 throws IOException, MojoExecutionException {
1689 appendSeparator(sb, indent);
1690
1691
1692 JavaEntityTags javaEntityTags = parseJavadocTags(originalContent, entity, indent, isJavaExecutable);
1693
1694
1695 updateJavadocTags(sb, entity, isJavaExecutable, javaEntityTags);
1696
1697
1698 addMissingJavadocTags(sb, entity, indent, isJavaExecutable, javaEntityTags);
1699 }
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711 JavaEntityTags parseJavadocTags(
1712 final String originalContent,
1713 final JavaAnnotatedElement entity,
1714 final String indent,
1715 final boolean isJavaMethod)
1716 throws IOException {
1717 JavaEntityTags javaEntityTags = new JavaEntityTags(entity, isJavaMethod);
1718 for (DocletTag docletTag : entity.getTags()) {
1719 String originalJavadocTag = getJavadocComment(originalContent, entity, docletTag);
1720 originalJavadocTag = removeLastEmptyJavadocLines(originalJavadocTag);
1721 originalJavadocTag = alignIndentationJavadocLines(originalJavadocTag, indent);
1722
1723 javaEntityTags.getNamesTags().add(docletTag.getName());
1724
1725 if (isJavaMethod) {
1726 List<String> params = docletTag.getParameters();
1727 if (params.size() < 1) {
1728 continue;
1729 }
1730
1731 String paramName = params.get(0);
1732 switch (docletTag.getName()) {
1733 case PARAM_TAG:
1734 javaEntityTags.putJavadocParamTag(paramName, docletTag.getValue(), originalJavadocTag);
1735 break;
1736 case RETURN_TAG:
1737 javaEntityTags.setJavadocReturnTag(originalJavadocTag);
1738 break;
1739 case THROWS_TAG:
1740 javaEntityTags.putJavadocThrowsTag(paramName, originalJavadocTag);
1741 break;
1742 default:
1743 javaEntityTags.getUnknownTags().add(originalJavadocTag);
1744 break;
1745 }
1746 } else {
1747 javaEntityTags.getUnknownTags().add(originalJavadocTag);
1748 }
1749 }
1750
1751 return javaEntityTags;
1752 }
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762 private void updateJavadocTags(
1763 final StringBuilder sb,
1764 final JavaAnnotatedElement entity,
1765 final boolean isJavaExecutable,
1766 final JavaEntityTags javaEntityTags) {
1767 for (DocletTag docletTag : entity.getTags()) {
1768 if (isJavaExecutable) {
1769 JavaExecutable javaExecutable = (JavaExecutable) entity;
1770
1771 List<String> params = docletTag.getParameters();
1772 if (params.size() < 1) {
1773 continue;
1774 }
1775
1776 if (docletTag.getName().equals(PARAM_TAG)) {
1777 writeParamTag(sb, javaExecutable, javaEntityTags, params.get(0), docletTag.getValue());
1778 } else if (docletTag.getName().equals(RETURN_TAG) && javaExecutable instanceof JavaMethod) {
1779 writeReturnTag(sb, (JavaMethod) javaExecutable, javaEntityTags);
1780 } else if (docletTag.getName().equals(THROWS_TAG)) {
1781 writeThrowsTag(sb, javaExecutable, javaEntityTags, params);
1782 } else {
1783
1784 for (Iterator<String> it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); ) {
1785 String originalJavadocTag = it.next();
1786 String simplified = StringUtils.removeDuplicateWhitespace(originalJavadocTag)
1787 .trim();
1788
1789 if (simplified.contains("@" + docletTag.getName())) {
1790 it.remove();
1791 sb.append(originalJavadocTag);
1792 sb.append(EOL);
1793 }
1794 }
1795 }
1796 } else {
1797 for (Iterator<String> it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); ) {
1798 String originalJavadocTag = it.next();
1799 String simplified = StringUtils.removeDuplicateWhitespace(originalJavadocTag)
1800 .trim();
1801
1802 if (simplified.contains("@" + docletTag.getName())) {
1803 it.remove();
1804 sb.append(originalJavadocTag);
1805 sb.append(EOL);
1806 }
1807 }
1808 }
1809
1810 if (sb.toString().endsWith(EOL)) {
1811 sb.delete(sb.toString().lastIndexOf(EOL), sb.toString().length());
1812 }
1813
1814 sb.append(EOL);
1815 }
1816 }
1817
1818 private void writeParamTag(
1819 final StringBuilder sb,
1820 final JavaExecutable javaExecutable,
1821 final JavaEntityTags javaEntityTags,
1822 String paramName,
1823 String paramValue) {
1824 if (!fixTag(PARAM_TAG)) {
1825
1826 String originalJavadocTag = javaEntityTags.getJavadocParamTag(paramValue);
1827 if (originalJavadocTag != null) {
1828 sb.append(originalJavadocTag);
1829 }
1830 return;
1831 }
1832
1833 boolean found = false;
1834 JavaParameter javaParam = javaExecutable.getParameterByName(paramName);
1835 if (javaParam == null) {
1836
1837 List<JavaTypeVariable<JavaGenericDeclaration>> typeParams = javaExecutable.getTypeParameters();
1838 for (JavaTypeVariable<JavaGenericDeclaration> typeParam : typeParams) {
1839 if (("<" + typeParam.getName() + ">").equals(paramName)) {
1840 found = true;
1841 }
1842 }
1843 } else {
1844 found = true;
1845 }
1846
1847 if (!found) {
1848 if (getLog().isWarnEnabled()) {
1849 getLog().warn("Fixed unknown param '" + paramName + "' defined in "
1850 + getJavaMethodAsString(javaExecutable));
1851 }
1852
1853 if (sb.toString().endsWith(EOL)) {
1854 sb.delete(sb.toString().lastIndexOf(EOL), sb.toString().length());
1855 }
1856 } else {
1857 String originalJavadocTag = javaEntityTags.getJavadocParamTag(paramValue);
1858 if (originalJavadocTag != null) {
1859 sb.append(originalJavadocTag);
1860 String s = "@" + PARAM_TAG + " " + paramName;
1861 if (StringUtils.removeDuplicateWhitespace(originalJavadocTag)
1862 .trim()
1863 .endsWith(s)) {
1864 sb.append(" ");
1865 sb.append(getDefaultJavadocForType(javaParam.getJavaClass()));
1866 }
1867 }
1868 }
1869 }
1870
1871 private void writeReturnTag(
1872 final StringBuilder sb, final JavaMethod javaMethod, final JavaEntityTags javaEntityTags) {
1873 String originalJavadocTag = javaEntityTags.getJavadocReturnTag();
1874 if (originalJavadocTag == null) {
1875 return;
1876 }
1877
1878 if (!fixTag(RETURN_TAG)) {
1879
1880 sb.append(originalJavadocTag);
1881 return;
1882 }
1883
1884 if ((originalJavadocTag != null && !originalJavadocTag.isEmpty())
1885 && javaMethod.getReturns() != null
1886 && !javaMethod.getReturns().isVoid()) {
1887 sb.append(originalJavadocTag);
1888 if (originalJavadocTag.trim().endsWith("@" + RETURN_TAG)) {
1889 sb.append(" ");
1890 sb.append(getDefaultJavadocForType(javaMethod.getReturns()));
1891 }
1892 }
1893 }
1894
1895 void writeThrowsTag(
1896 final StringBuilder sb,
1897 final JavaExecutable javaExecutable,
1898 final JavaEntityTags javaEntityTags,
1899 final List<String> params) {
1900 String exceptionClassName = params.get(0);
1901
1902 String originalJavadocTag = javaEntityTags.getJavadocThrowsTag(exceptionClassName);
1903 if (originalJavadocTag == null) {
1904 return;
1905 }
1906
1907 if (!fixTag(THROWS_TAG)) {
1908
1909 sb.append(originalJavadocTag);
1910 return;
1911 }
1912
1913 if (javaExecutable.getExceptions() != null) {
1914 for (JavaType exception : javaExecutable.getExceptions()) {
1915 if (exception.getFullyQualifiedName().endsWith(exceptionClassName)) {
1916 originalJavadocTag = StringUtils.replace(
1917 originalJavadocTag, exceptionClassName, exception.getFullyQualifiedName());
1918 if (StringUtils.removeDuplicateWhitespace(originalJavadocTag)
1919 .trim()
1920 .endsWith("@" + THROWS_TAG + " " + exception.getValue())) {
1921 originalJavadocTag += " if any.";
1922 }
1923
1924 sb.append(originalJavadocTag);
1925
1926
1927 javaEntityTags.putJavadocThrowsTag(exception.getValue(), originalJavadocTag);
1928
1929 return;
1930 }
1931 }
1932 }
1933
1934 Class<?> clazz = getClass(javaExecutable.getDeclaringClass(), exceptionClassName);
1935
1936 if (clazz != null) {
1937 if (RuntimeException.class.isAssignableFrom(clazz)) {
1938 sb.append(StringUtils.replace(originalJavadocTag, exceptionClassName, clazz.getName()));
1939
1940
1941 javaEntityTags.putJavadocThrowsTag(clazz.getName(), originalJavadocTag);
1942 } else if (Throwable.class.isAssignableFrom(clazz)) {
1943 getLog().debug("Removing '" + originalJavadocTag + "'; Throwable not specified by "
1944 + getJavaMethodAsString(javaExecutable) + " and it is not a RuntimeException.");
1945 } else {
1946 getLog().debug("Removing '" + originalJavadocTag + "'; It is not a Throwable");
1947 }
1948 } else if (removeUnknownThrows) {
1949 getLog().warn("Ignoring unknown throws '" + exceptionClassName + "' defined on "
1950 + getJavaMethodAsString(javaExecutable));
1951 } else {
1952 getLog().warn("Found unknown throws '" + exceptionClassName + "' defined on "
1953 + getJavaMethodAsString(javaExecutable));
1954
1955 sb.append(originalJavadocTag);
1956
1957 if (params.size() == 1) {
1958 sb.append(" if any.");
1959 }
1960
1961 javaEntityTags.putJavadocThrowsTag(exceptionClassName, originalJavadocTag);
1962 }
1963 }
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975 private void addMissingJavadocTags(
1976 final StringBuilder sb,
1977 final JavaAnnotatedElement entity,
1978 final String indent,
1979 final boolean isJavaExecutable,
1980 final JavaEntityTags javaEntityTags)
1981 throws MojoExecutionException {
1982 if (isJavaExecutable) {
1983 JavaExecutable javaExecutable = (JavaExecutable) entity;
1984
1985 if (fixTag(PARAM_TAG)) {
1986 if (javaExecutable.getParameters() != null) {
1987 for (JavaParameter javaParameter : javaExecutable.getParameters()) {
1988 if (!javaEntityTags.hasJavadocParamTag(javaParameter.getName())) {
1989 appendDefaultParamTag(sb, indent, javaParameter);
1990 }
1991 }
1992 }
1993
1994 if (javaExecutable.getTypeParameters() != null) {
1995 for (JavaTypeVariable<JavaGenericDeclaration> typeParam : javaExecutable.getTypeParameters()) {
1996 if (!javaEntityTags.hasJavadocParamTag("<" + typeParam.getName() + ">")) {
1997 appendDefaultParamTag(sb, indent, typeParam);
1998 }
1999 }
2000 }
2001 }
2002
2003 if (javaExecutable instanceof JavaMethod) {
2004 JavaMethod javaMethod = (JavaMethod) javaExecutable;
2005 if (fixTag(RETURN_TAG)
2006 && StringUtils.isEmpty(javaEntityTags.getJavadocReturnTag())
2007 && javaMethod.getReturns() != null
2008 && !javaMethod.getReturns().isVoid()) {
2009 appendDefaultReturnTag(sb, indent, javaMethod);
2010 }
2011 }
2012
2013 if (fixTag(THROWS_TAG) && javaExecutable.getExceptions() != null) {
2014 for (JavaType exception : javaExecutable.getExceptions()) {
2015 if (javaEntityTags.getJavadocThrowsTag(exception.getValue(), true) == null) {
2016 appendDefaultThrowsTag(sb, indent, exception);
2017 }
2018 }
2019 }
2020 } else {
2021 if (!javaEntityTags.getNamesTags().contains(AUTHOR_TAG)) {
2022 appendDefaultAuthorTag(sb, indent);
2023 }
2024 if (!javaEntityTags.getNamesTags().contains(VERSION_TAG)) {
2025 appendDefaultVersionTag(sb, indent);
2026 }
2027 }
2028
2029 if (fixTag(SINCE_TAG) && !javaEntityTags.getNamesTags().contains(SINCE_TAG)) {
2030 if (!isJavaExecutable) {
2031 if (!ignoreClirr) {
2032 if (isNewClassFromLastVersion((JavaClass) entity)) {
2033 appendDefaultSinceTag(sb, indent);
2034 }
2035 } else {
2036 appendDefaultSinceTag(sb, indent);
2037 addSinceClasses((JavaClass) entity);
2038 }
2039 } else {
2040 if (!ignoreClirr) {
2041 if (isNewMethodFromLastRevision((JavaExecutable) entity)) {
2042 appendDefaultSinceTag(sb, indent);
2043 }
2044 } else if (sinceClasses != null) {
2045 if (entity instanceof JavaMember
2046 && !sinceClassesContains(((JavaMember) entity).getDeclaringClass())) {
2047 appendDefaultSinceTag(sb, indent);
2048 } else if (entity instanceof JavaClass
2049 && !sinceClassesContains(((JavaClass) entity).getDeclaringClass())) {
2050 appendDefaultSinceTag(sb, indent);
2051 }
2052 }
2053 }
2054 }
2055 }
2056
2057
2058
2059
2060
2061 private void appendDefaultAuthorTag(final StringBuilder sb, final String indent) {
2062 if (!fixTag(AUTHOR_TAG)) {
2063 return;
2064 }
2065
2066 sb.append(indent).append(" * @").append(AUTHOR_TAG).append(" ");
2067 sb.append(defaultAuthor);
2068 sb.append(EOL);
2069 }
2070
2071
2072
2073
2074
2075
2076
2077 private boolean appendDefaultSinceTag(final StringBuilder sb, final String indent, boolean separatorAdded) {
2078 if (!fixTag(SINCE_TAG)) {
2079 return separatorAdded;
2080 }
2081
2082 if (!separatorAdded) {
2083 appendSeparator(sb, indent);
2084 separatorAdded = true;
2085 }
2086
2087 appendDefaultSinceTag(sb, indent);
2088 return separatorAdded;
2089 }
2090
2091
2092
2093
2094
2095 private void appendDefaultSinceTag(final StringBuilder sb, final String indent) {
2096 if (!fixTag(SINCE_TAG)) {
2097 return;
2098 }
2099
2100 sb.append(indent).append(" * @").append(SINCE_TAG).append(" ");
2101 sb.append(defaultSince);
2102 sb.append(EOL);
2103 }
2104
2105
2106
2107
2108
2109 private void appendDefaultVersionTag(final StringBuilder sb, final String indent) {
2110 if (!fixTag(VERSION_TAG) || StringUtils.isEmpty(defaultVersion)) {
2111 return;
2112 }
2113
2114 sb.append(indent).append(" * @").append(VERSION_TAG).append(" ");
2115 sb.append(defaultVersion);
2116 sb.append(EOL);
2117 }
2118
2119
2120
2121
2122
2123
2124
2125
2126 private boolean appendDefaultParamTag(
2127 final StringBuilder sb, final String indent, boolean separatorAdded, final JavaParameter typeParam) {
2128 if (!fixTag(PARAM_TAG)) {
2129 return separatorAdded;
2130 }
2131
2132 if (!separatorAdded) {
2133 appendSeparator(sb, indent);
2134 separatorAdded = true;
2135 }
2136
2137 appendDefaultParamTag(sb, indent, typeParam);
2138 return separatorAdded;
2139 }
2140
2141
2142
2143
2144
2145
2146
2147
2148 private boolean appendDefaultParamTag(
2149 final StringBuilder sb,
2150 final String indent,
2151 boolean separatorAdded,
2152 final JavaTypeVariable<JavaGenericDeclaration> typeParameter) {
2153 if (!fixTag(PARAM_TAG)) {
2154 return separatorAdded;
2155 }
2156
2157 if (!separatorAdded) {
2158 appendSeparator(sb, indent);
2159 separatorAdded = true;
2160 }
2161
2162 appendDefaultParamTag(sb, indent, typeParameter);
2163 return separatorAdded;
2164 }
2165
2166
2167
2168
2169
2170
2171 private void appendDefaultParamTag(final StringBuilder sb, final String indent, final JavaParameter typeParam) {
2172 if (!fixTag(PARAM_TAG)) {
2173 return;
2174 }
2175
2176 sb.append(indent).append(" * @").append(PARAM_TAG).append(" ");
2177 sb.append(typeParam.getName());
2178 sb.append(" ");
2179 sb.append(getDefaultJavadocForType(typeParam.getJavaClass()));
2180 sb.append(EOL);
2181 }
2182
2183
2184
2185
2186
2187
2188 private void appendDefaultParamTag(
2189 final StringBuilder sb, final String indent, final JavaTypeVariable<JavaGenericDeclaration> typeParameter) {
2190 if (!fixTag(PARAM_TAG)) {
2191 return;
2192 }
2193
2194 sb.append(indent).append(" * @").append(PARAM_TAG).append(" ");
2195 sb.append("<").append(typeParameter.getName()).append(">");
2196 sb.append(" ");
2197 sb.append(getDefaultJavadocForType(typeParameter));
2198 sb.append(EOL);
2199 }
2200
2201
2202
2203
2204
2205
2206
2207
2208 private boolean appendDefaultReturnTag(
2209 final StringBuilder sb, final String indent, boolean separatorAdded, final JavaMethod javaMethod) {
2210 if (!fixTag(RETURN_TAG)) {
2211 return separatorAdded;
2212 }
2213
2214 if (!separatorAdded) {
2215 appendSeparator(sb, indent);
2216 separatorAdded = true;
2217 }
2218
2219 appendDefaultReturnTag(sb, indent, javaMethod);
2220 return separatorAdded;
2221 }
2222
2223
2224
2225
2226
2227
2228 private void appendDefaultReturnTag(final StringBuilder sb, final String indent, final JavaMethod javaMethod) {
2229 if (!fixTag(RETURN_TAG)) {
2230 return;
2231 }
2232
2233 sb.append(indent).append(" * @").append(RETURN_TAG).append(" ");
2234 sb.append(getDefaultJavadocForType(javaMethod.getReturns()));
2235 sb.append(EOL);
2236 }
2237
2238
2239
2240
2241
2242
2243
2244
2245 private boolean appendDefaultThrowsTag(
2246 final StringBuilder sb, final String indent, boolean separatorAdded, final JavaType exception) {
2247 if (!fixTag(THROWS_TAG)) {
2248 return separatorAdded;
2249 }
2250
2251 if (!separatorAdded) {
2252 appendSeparator(sb, indent);
2253 separatorAdded = true;
2254 }
2255
2256 appendDefaultThrowsTag(sb, indent, exception);
2257 return separatorAdded;
2258 }
2259
2260
2261
2262
2263
2264
2265 private void appendDefaultThrowsTag(final StringBuilder sb, final String indent, final JavaType exception) {
2266 if (!fixTag(THROWS_TAG)) {
2267 return;
2268 }
2269
2270 sb.append(indent).append(" * @").append(THROWS_TAG).append(" ");
2271 sb.append(exception.getFullyQualifiedName());
2272 sb.append(" if any.");
2273 sb.append(EOL);
2274 }
2275
2276
2277
2278
2279
2280 private void appendSeparator(final StringBuilder sb, final String indent) {
2281 sb.append(indent).append(" *");
2282 sb.append(EOL);
2283 }
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293 private boolean isInherited(JavaExecutable javaMethod) throws MojoExecutionException {
2294 if (javaMethod.getAnnotations() != null) {
2295 for (JavaAnnotation annotation : javaMethod.getAnnotations()) {
2296 if (annotation.toString().equals("@java.lang.Override()")) {
2297 return true;
2298 }
2299 }
2300 }
2301
2302 Class<?> clazz = getClass(javaMethod.getDeclaringClass().getFullyQualifiedName());
2303
2304 List<Class<?>> interfaces = ClassUtils.getAllInterfaces(clazz);
2305 for (Class<?> intface : interfaces) {
2306 if (isInherited(intface, javaMethod)) {
2307 return true;
2308 }
2309 }
2310
2311 List<Class<?>> classes = ClassUtils.getAllSuperclasses(clazz);
2312 for (Class<?> superClass : classes) {
2313 if (isInherited(superClass, javaMethod)) {
2314 return true;
2315 }
2316 }
2317
2318 return false;
2319 }
2320
2321
2322
2323
2324
2325
2326
2327
2328 private boolean isInherited(Class<?> clazz, JavaExecutable javaMethod) {
2329 for (Method method : clazz.getDeclaredMethods()) {
2330 if (!method.getName().equals(javaMethod.getName())) {
2331 continue;
2332 }
2333
2334 if (method.getParameterTypes().length != javaMethod.getParameters().size()) {
2335 continue;
2336 }
2337
2338 boolean found = false;
2339 int j = 0;
2340 for (Class<?> paramType : method.getParameterTypes()) {
2341 String name1 = paramType.getName();
2342 String name2 = javaMethod.getParameters().get(j++).getType().getFullyQualifiedName();
2343 found = name1.equals(name2);
2344 }
2345
2346 return found;
2347 }
2348
2349 return false;
2350 }
2351
2352
2353
2354
2355
2356 private String getDefaultJavadocForType(JavaClass clazz) {
2357 StringBuilder sb = new StringBuilder();
2358
2359 if (!JavaTypeVariable.class.isAssignableFrom(clazz.getClass()) && clazz.isPrimitive()) {
2360 if (clazz.isArray()) {
2361 sb.append("an array of ").append(clazz.getComponentType().getCanonicalName());
2362 } else {
2363 sb.append("a ").append(clazz.getCanonicalName());
2364 }
2365 return sb.toString();
2366 }
2367
2368 StringBuilder javadocLink = new StringBuilder();
2369 try {
2370 getClass(clazz.getCanonicalName());
2371
2372 javadocLink.append("{@link ");
2373
2374 if (clazz.isArray()) {
2375 javadocLink.append(clazz.getComponentType().getCanonicalName());
2376 } else {
2377 javadocLink.append(clazz.getCanonicalName());
2378 }
2379 javadocLink.append("}");
2380 } catch (Exception e) {
2381 javadocLink.append(clazz.getValue());
2382 }
2383
2384 if (clazz.isArray()) {
2385 sb.append("an array of ").append(javadocLink).append(" objects");
2386 } else {
2387 sb.append("a ").append(javadocLink).append(" object");
2388 }
2389
2390 return sb.toString();
2391 }
2392
2393 private String getDefaultJavadocForType(JavaTypeVariable<JavaGenericDeclaration> typeParameter) {
2394 return "a " + typeParameter.getName() + " class";
2395 }
2396
2397
2398
2399
2400
2401
2402
2403
2404 private boolean isNewClassFromLastVersion(JavaClass javaClass) {
2405 return (clirrNewClasses != null) && clirrNewClasses.contains(javaClass.getFullyQualifiedName());
2406 }
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416 private boolean isNewMethodFromLastRevision(JavaExecutable javaExecutable) throws MojoExecutionException {
2417 if (clirrNewMethods == null) {
2418 return false;
2419 }
2420
2421 List<String> clirrMethods =
2422 clirrNewMethods.get(javaExecutable.getDeclaringClass().getFullyQualifiedName());
2423 if (clirrMethods == null) {
2424 return false;
2425 }
2426
2427 for (String clirrMethod : clirrMethods) {
2428
2429 String retrn = "";
2430 if (javaExecutable instanceof JavaMethod && ((JavaMethod) javaExecutable).getReturns() != null) {
2431 retrn = ((JavaMethod) javaExecutable).getReturns().getFullyQualifiedName();
2432 }
2433 StringBuilder params = new StringBuilder();
2434 for (JavaParameter parameter : javaExecutable.getParameters()) {
2435 if (params.length() > 0) {
2436 params.append(", ");
2437 }
2438 params.append(parameter.getResolvedFullyQualifiedName());
2439 }
2440 if (clirrMethod.contains(retrn + " ")
2441 && clirrMethod.contains(javaExecutable.getName() + "(")
2442 && clirrMethod.contains("(" + params.toString() + ")")) {
2443 return true;
2444 }
2445 }
2446
2447 return false;
2448 }
2449
2450
2451
2452
2453
2454
2455
2456
2457 private Class<?> getClass(String className) throws MojoExecutionException {
2458 try {
2459 return ClassUtils.getClass(getProjectClassLoader(), className, false);
2460 } catch (ClassNotFoundException e) {
2461 throw new MojoExecutionException("ClassNotFoundException: " + e.getMessage(), e);
2462 }
2463 }
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480 private Class<?> getClass(JavaClass currentClass, String exceptionClassName) {
2481 String[] potentialClassNames = new String[] {
2482 exceptionClassName,
2483 currentClass.getPackage().getName() + "." + exceptionClassName,
2484 currentClass.getPackage().getName() + "." + currentClass.getName() + "$" + exceptionClassName,
2485 "java.lang." + exceptionClassName
2486 };
2487
2488 Class<?> clazz = null;
2489 for (String potentialClassName : potentialClassNames) {
2490 try {
2491 clazz = getClass(potentialClassName);
2492 } catch (MojoExecutionException e) {
2493
2494 }
2495 if (clazz != null) {
2496 return clazz;
2497 }
2498 }
2499
2500 return null;
2501 }
2502
2503
2504
2505
2506 private void addSinceClasses(JavaClass javaClass) {
2507 if (sinceClasses == null) {
2508 sinceClasses = new ArrayList<>();
2509 }
2510 sinceClasses.add(javaClass.getFullyQualifiedName());
2511 }
2512
2513 private boolean sinceClassesContains(JavaClass javaClass) {
2514 return sinceClasses.contains(javaClass.getFullyQualifiedName());
2515 }
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530 private static void writeFile(final File javaFile, final String encoding, final String content) throws IOException {
2531 String unified = StringUtils.unifyLineSeparators(content);
2532 FileUtils.fileWrite(javaFile, encoding, unified);
2533 }
2534
2535
2536
2537
2538
2539 private static String getFullClirrGoal() {
2540 StringBuilder sb = new StringBuilder();
2541
2542 sb.append(CLIRR_MAVEN_PLUGIN_GROUPID)
2543 .append(":")
2544 .append(CLIRR_MAVEN_PLUGIN_ARTIFACTID)
2545 .append(":");
2546
2547 String clirrVersion = CLIRR_MAVEN_PLUGIN_VERSION;
2548
2549 String resource = "META-INF/maven/" + CLIRR_MAVEN_PLUGIN_GROUPID + "/" + CLIRR_MAVEN_PLUGIN_ARTIFACTID
2550 + "/pom.properties";
2551
2552 try (InputStream resourceAsStream =
2553 AbstractFixJavadocMojo.class.getClassLoader().getResourceAsStream(resource)) {
2554
2555 if (resourceAsStream != null) {
2556 Properties properties = new Properties();
2557 properties.load(resourceAsStream);
2558 if (StringUtils.isNotEmpty(properties.getProperty("version"))) {
2559 clirrVersion = properties.getProperty("version");
2560 }
2561 }
2562 } catch (IOException e) {
2563
2564 }
2565
2566 sb.append(clirrVersion).append(":").append(CLIRR_MAVEN_PLUGIN_GOAL);
2567
2568 return sb.toString();
2569 }
2570
2571
2572
2573
2574
2575
2576
2577 private static String getDefaultClassJavadocComment(final JavaClass javaClass) {
2578 StringBuilder sb = new StringBuilder();
2579
2580 sb.append("<p>");
2581 if (javaClass.isAbstract()) {
2582 sb.append("Abstract ");
2583 }
2584
2585 sb.append(javaClass.getName());
2586
2587 if (!javaClass.isInterface()) {
2588 sb.append(" class.");
2589 } else {
2590 sb.append(" interface.");
2591 }
2592
2593 sb.append("</p>");
2594
2595 return sb.toString();
2596 }
2597
2598
2599
2600
2601
2602
2603
2604 private static String getDefaultMethodJavadocComment(final JavaExecutable javaExecutable) {
2605 if (javaExecutable instanceof JavaConstructor) {
2606 return "<p>Constructor for " + javaExecutable.getName() + ".</p>";
2607 }
2608
2609 if (javaExecutable.getName().length() > 3
2610 && (javaExecutable.getName().startsWith("get")
2611 || javaExecutable.getName().startsWith("set"))) {
2612 String field =
2613 StringUtils.lowercaseFirstLetter(javaExecutable.getName().substring(3));
2614
2615 JavaClass clazz = javaExecutable.getDeclaringClass();
2616
2617 if (clazz.getFieldByName(field) == null) {
2618 return "<p>" + javaExecutable.getName() + ".</p>";
2619 }
2620
2621 StringBuilder sb = new StringBuilder();
2622
2623 sb.append("<p>");
2624 if (javaExecutable.getName().startsWith("get")) {
2625 sb.append("Getter ");
2626 } else if (javaExecutable.getName().startsWith("set")) {
2627 sb.append("Setter ");
2628 }
2629 sb.append("for the field <code>").append(field).append("</code>.</p>");
2630
2631 return sb.toString();
2632 }
2633
2634 return "<p>" + javaExecutable.getName() + ".</p>";
2635 }
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652 private static boolean hasInheritedTag(final String content) {
2653 final String inheritedTagPattern =
2654 "^\\s*(\\/\\*\\*)?(\\s*(\\*)?)*(\\{)@inheritDoc\\s*(\\})(\\s*(\\*)?)*(\\*\\/)?$";
2655 return Pattern.matches(inheritedTagPattern, StringUtils.removeDuplicateWhitespace(content));
2656 }
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 static String getJavadocComment(final String javaClassContent, final JavaAnnotatedElement entity)
2697 throws IOException {
2698 if (entity.getComment() == null) {
2699 return "";
2700 }
2701
2702 String originalJavadoc = extractOriginalJavadocContent(javaClassContent, entity);
2703
2704 StringBuilder sb = new StringBuilder();
2705 BufferedReader lr = new BufferedReader(new StringReader(originalJavadoc));
2706 String line;
2707 while ((line = lr.readLine()) != null) {
2708 String l = StringUtils.removeDuplicateWhitespace(line.trim());
2709 if (l.startsWith("* @") || l.startsWith("*@")) {
2710 break;
2711 }
2712 sb.append(line).append(EOL);
2713 }
2714
2715 return trimRight(sb.toString());
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
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758 String getJavadocComment(
2759 final String javaClassContent, final JavaAnnotatedElement entity, final DocletTag docletTag)
2760 throws IOException {
2761 if (docletTag.getValue() == null || docletTag.getParameters().isEmpty()) {
2762 return "";
2763 }
2764
2765 String originalJavadoc = extractOriginalJavadocContent(javaClassContent, entity);
2766
2767 StringBuilder sb = new StringBuilder();
2768 BufferedReader lr = new BufferedReader(new StringReader(originalJavadoc));
2769 String line;
2770 boolean found = false;
2771
2772
2773 Pattern p = Pattern.compile("(\\s*\\*\\s?@" + docletTag.getName() + ")\\s+" + "(\\Q"
2774 + docletTag.getValue().split("\r\n|\r|\n")[0] + "\\E)");
2775
2776 while ((line = lr.readLine()) != null) {
2777 Matcher m = p.matcher(line);
2778 if (m.matches()) {
2779 if (fixTag(LINK_TAG)) {
2780 line = replaceLinkTags(line, entity);
2781 }
2782 sb.append(line).append(EOL);
2783 found = true;
2784 } else {
2785 if (line.trim().startsWith("* @") || line.trim().startsWith("*@")) {
2786 found = false;
2787 }
2788 if (found) {
2789 if (fixTag(LINK_TAG)) {
2790 line = replaceLinkTags(line, entity);
2791 }
2792 sb.append(line).append(EOL);
2793 }
2794 }
2795 }
2796
2797 return trimRight(sb.toString());
2798 }
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 static String extractOriginalJavadoc(final String javaClassContent, final JavaAnnotatedElement entity)
2846 throws IOException {
2847 if (entity.getComment() == null) {
2848 return "";
2849 }
2850
2851 String[] javaClassContentLines = getLines(javaClassContent);
2852 List<String> list = new LinkedList<>();
2853 for (int i = entity.getLineNumber() - 2; i >= 0; i--) {
2854 String line = javaClassContentLines[i];
2855
2856 list.add(trimRight(line));
2857 if (line.trim().startsWith(START_JAVADOC)) {
2858 break;
2859 }
2860 }
2861
2862 Collections.reverse(list);
2863
2864 return StringUtils.join(list.iterator(), EOL);
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
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908 static String extractOriginalJavadocContent(final String javaClassContent, final JavaAnnotatedElement entity)
2909 throws IOException {
2910 if (entity.getComment() == null) {
2911 return "";
2912 }
2913
2914 String originalJavadoc = extractOriginalJavadoc(javaClassContent, entity);
2915 int index = originalJavadoc.indexOf(START_JAVADOC);
2916 if (index != -1) {
2917 originalJavadoc = originalJavadoc.substring(index + START_JAVADOC.length());
2918 }
2919 index = originalJavadoc.indexOf(END_JAVADOC);
2920 if (index != -1) {
2921 originalJavadoc = originalJavadoc.substring(0, index);
2922 }
2923 if (originalJavadoc.startsWith("\r\n")) {
2924 originalJavadoc = originalJavadoc.substring(2);
2925 } else if (originalJavadoc.startsWith("\n") || originalJavadoc.startsWith("\r")) {
2926 originalJavadoc = originalJavadoc.substring(1);
2927 }
2928
2929 return trimRight(originalJavadoc);
2930 }
2931
2932
2933
2934
2935
2936
2937
2938 private static String removeLastEmptyJavadocLines(final String content) throws IOException {
2939 if (!content.contains(EOL)) {
2940 return content;
2941 }
2942
2943 String[] lines = getLines(content);
2944 if (lines.length == 1) {
2945 return content;
2946 }
2947
2948 List<String> linesList = new LinkedList<>(Arrays.asList(lines));
2949
2950 Collections.reverse(linesList);
2951
2952 for (Iterator<String> it = linesList.iterator(); it.hasNext(); ) {
2953 String line = it.next();
2954
2955 if (line.trim().equals("*")) {
2956 it.remove();
2957 } else {
2958 break;
2959 }
2960 }
2961
2962 Collections.reverse(linesList);
2963
2964 return StringUtils.join(linesList.iterator(), EOL);
2965 }
2966
2967
2968
2969
2970
2971
2972
2973 private static String alignIndentationJavadocLines(final String content, final String indent) throws IOException {
2974 StringBuilder sb = new StringBuilder();
2975 for (String line : getLines(content)) {
2976 if (sb.length() > 0) {
2977 sb.append(EOL);
2978 }
2979 if (!line.trim().startsWith("*")) {
2980 line = "*" + line;
2981 }
2982 sb.append(indent).append(" ").append(trimLeft(line));
2983 }
2984
2985 return sb.toString();
2986 }
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000 private static String autodetectIndentation(final String line) {
3001 if (line == null || line.isEmpty()) {
3002 return "";
3003 }
3004
3005 return line.substring(0, line.indexOf(trimLeft(line)));
3006 }
3007
3008
3009
3010
3011
3012
3013 private static String[] getLines(final String content) throws IOException {
3014 List<String> lines = new LinkedList<>();
3015
3016 BufferedReader reader = new BufferedReader(new StringReader(content));
3017 String line = reader.readLine();
3018 while (line != null) {
3019 lines.add(line);
3020 line = reader.readLine();
3021 }
3022
3023 return lines.toArray(new String[lines.size()]);
3024 }
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040 private static String trimLeft(final String text) {
3041 if ((text == null || text.isEmpty()) || StringUtils.isEmpty(text.trim())) {
3042 return "";
3043 }
3044
3045 String textTrimmed = text.trim();
3046 return text.substring(text.indexOf(textTrimmed));
3047 }
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062 private static String trimRight(final String text) {
3063 if ((text == null || text.isEmpty()) || StringUtils.isEmpty(text.trim())) {
3064 return "";
3065 }
3066
3067 String textTrimmed = text.trim();
3068 return text.substring(0, text.indexOf(textTrimmed) + textTrimmed.length());
3069 }
3070
3071
3072
3073
3074 class JavaEntityTags {
3075 private final JavaAnnotatedElement entity;
3076
3077 private final boolean isJavaMethod;
3078
3079
3080
3081
3082 private List<String> namesTags;
3083
3084
3085
3086
3087 private Map<String, String> tagParams;
3088
3089 private Set<String> documentedParams = new HashSet<>();
3090
3091
3092
3093
3094 private String tagReturn;
3095
3096
3097
3098
3099 private Map<String, String> tagThrows;
3100
3101
3102
3103
3104 private List<String> unknownsTags;
3105
3106 JavaEntityTags(JavaAnnotatedElement entity, boolean isJavaMethod) {
3107 this.entity = entity;
3108 this.isJavaMethod = isJavaMethod;
3109 this.namesTags = new LinkedList<>();
3110 this.tagParams = new LinkedHashMap<>();
3111 this.tagThrows = new LinkedHashMap<>();
3112 this.unknownsTags = new LinkedList<>();
3113 }
3114
3115 public List<String> getNamesTags() {
3116 return namesTags;
3117 }
3118
3119 public String getJavadocReturnTag() {
3120 return tagReturn;
3121 }
3122
3123 public void setJavadocReturnTag(String s) {
3124 tagReturn = s;
3125 }
3126
3127 public List<String> getUnknownTags() {
3128 return unknownsTags;
3129 }
3130
3131 public void putJavadocParamTag(String paramName, String paramValue, String originalJavadocTag) {
3132 documentedParams.add(paramName);
3133 tagParams.put(paramValue, originalJavadocTag);
3134 }
3135
3136 public String getJavadocParamTag(String paramValue) {
3137 String originalJavadocTag = tagParams.get(paramValue);
3138 if (originalJavadocTag == null && getLog().isWarnEnabled()) {
3139 getLog().warn(getMessage(paramValue, "javaEntityTags.tagParams"));
3140 }
3141 return originalJavadocTag;
3142 }
3143
3144 public boolean hasJavadocParamTag(String paramName) {
3145 return documentedParams.contains(paramName);
3146 }
3147
3148 public void putJavadocThrowsTag(String paramName, String originalJavadocTag) {
3149 tagThrows.put(paramName, originalJavadocTag);
3150 }
3151
3152 public String getJavadocThrowsTag(String paramName) {
3153 return getJavadocThrowsTag(paramName, false);
3154 }
3155
3156 public String getJavadocThrowsTag(String paramName, boolean nullable) {
3157 String originalJavadocTag = tagThrows.get(paramName);
3158 if (!nullable && originalJavadocTag == null && getLog().isWarnEnabled()) {
3159 getLog().warn(getMessage(paramName, "javaEntityTags.tagThrows"));
3160 }
3161
3162 return originalJavadocTag;
3163 }
3164
3165 private String getMessage(String paramName, String mapName) {
3166 StringBuilder msg = new StringBuilder();
3167 msg.append("No param '")
3168 .append(paramName)
3169 .append("' key found in ")
3170 .append(mapName)
3171 .append(" for the entity: ");
3172 if (isJavaMethod) {
3173 JavaMethod javaMethod = (JavaMethod) entity;
3174 msg.append(getJavaMethodAsString(javaMethod));
3175 } else {
3176 JavaClass javaClass = (JavaClass) entity;
3177 msg.append(javaClass.getFullyQualifiedName());
3178 }
3179
3180 return msg.toString();
3181 }
3182
3183
3184
3185
3186 @Override
3187 public String toString() {
3188 StringBuilder sb = new StringBuilder();
3189
3190 sb.append("namesTags=").append(namesTags).append("\n");
3191 sb.append("tagParams=").append(tagParams).append("\n");
3192 sb.append("tagReturn=").append(tagReturn).append("\n");
3193 sb.append("tagThrows=").append(tagThrows).append("\n");
3194 sb.append("unknownsTags=").append(unknownsTags).append("\n");
3195
3196 return sb.toString();
3197 }
3198 }
3199 }