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