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