View Javadoc

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