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