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