View Javadoc

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