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