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