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