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 }