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