View Javadoc

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