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