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