org.apache.maven.plugins.javadoc.AbstractFixJavadocMojo Maven / Gradle / Ivy
Show all versions of maven-javadoc-plugin Show documentation
package org.apache.maven.plugins.javadoc; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import com.thoughtworks.qdox.JavaProjectBuilder; import com.thoughtworks.qdox.library.ClassLibraryBuilder; import com.thoughtworks.qdox.library.OrderedClassLibraryBuilder; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaAnnotatedElement; import com.thoughtworks.qdox.model.JavaAnnotation; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaConstructor; import com.thoughtworks.qdox.model.JavaExecutable; import com.thoughtworks.qdox.model.JavaField; import com.thoughtworks.qdox.model.JavaGenericDeclaration; import com.thoughtworks.qdox.model.JavaMember; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaParameter; import com.thoughtworks.qdox.model.JavaType; import com.thoughtworks.qdox.model.JavaTypeVariable; import com.thoughtworks.qdox.parser.ParseException; import com.thoughtworks.qdox.type.TypeResolver; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; import org.apache.maven.shared.invoker.MavenInvocationException; import org.codehaus.plexus.components.interactivity.InputHandler; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.ReaderFactory; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.WriterFactory; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Abstract class to fix Javadoc documentation and tags in source files. *
* * @param buffer not null * @param javaClass not null * @param indent not null * @see #getDefaultClassJavadocComment(JavaClass) * @see #appendDefaultAuthorTag(StringBuilder, String) * @see #appendDefaultSinceTag(StringBuilder, String) * @see #appendDefaultVersionTag(StringBuilder, String) */ private void addDefaultClassComment( final StringWriter stringWriter, final JavaClass javaClass, final String indent ) { StringBuilder sb = new StringBuilder(); sb.append( indent ).append( START_JAVADOC ); sb.append( EOL ); sb.append( indent ).append( SEPARATOR_JAVADOC ); sb.append( getDefaultClassJavadocComment( javaClass ) ); sb.append( EOL ); appendSeparator( sb, indent ); appendDefaultAuthorTag( sb, indent ); appendDefaultVersionTag( sb, indent ); if ( fixTag( SINCE_TAG ) ) { if ( !ignoreClirr ) { if ( isNewClassFromLastVersion( javaClass ) ) { appendDefaultSinceTag( sb, indent ); } } else { appendDefaultSinceTag( sb, indent ); addSinceClasses( javaClass ); } } sb.append( indent ).append( " " ).append( END_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); } /** * Add Javadoc field comment, only for static fields or interface fields. * * @param stringWriter not null * @param javaClass not null * @param field not null * @param indent not null * @return {@code true} if comment was updated, otherwise {@code false} * @throws IOException if any */ private boolean fixFieldComment( final StringWriter stringWriter, final JavaClass javaClass, final JavaField field, final String indent ) throws IOException { if ( !fixFieldComment ) { return false; } if ( !javaClass.isInterface() && ( !isInLevel( field.getModifiers() ) || !field.isStatic() ) ) { return false; } // add if ( field.getComment() == null ) { addDefaultFieldComment( stringWriter, field, indent ); return true; } // no update return false; } /** * Add a default Javadoc for the given field, i.e.: *
* See Where Tags * Can Be Used. * * @author Vincent Siveton * @version $Id: AbstractFixJavadocMojo.java 1801354 2017-07-09 08:49:46Z rfscholte $ * @since 2.6 */ public abstract class AbstractFixJavadocMojo extends AbstractMojo { /** * The vm line separator */ private static final String EOL = System.getProperty( "line.separator" ); /** * Tag name for @author * */ private static final String AUTHOR_TAG = "author"; /** * Tag name for @version * */ private static final String VERSION_TAG = "version"; /** * Tag name for @since * */ private static final String SINCE_TAG = "since"; /** * Tag name for @param * */ private static final String PARAM_TAG = "param"; /** * Tag name for @return * */ private static final String RETURN_TAG = "return"; /** * Tag name for @throws * */ private static final String THROWS_TAG = "throws"; /** * Tag name for @link * */ private static final String LINK_TAG = "link"; /** * Tag name for {@inheritDoc} * */ private static final String INHERITED_TAG = "{@inheritDoc}"; /** * Start Javadoc String i.e./**
* */ private static final String START_JAVADOC = "/**"; /** * End Javadoc String i.e.*/
* */ private static final String END_JAVADOC = "*/"; /** * Javadoc Separator i.e.*
* */ private static final String SEPARATOR_JAVADOC = " * "; /** * Inherited Javadoc i.e./** {@inheritDoc} */
* */ private static final String INHERITED_JAVADOC = START_JAVADOC + " " + INHERITED_TAG + " " + END_JAVADOC; /** *all
parameter used by {@link #fixTags} * */ private static final String FIX_TAGS_ALL = "all"; /** *public
parameter used by {@link #level} * */ private static final String LEVEL_PUBLIC = "public"; /** *protected
parameter used by {@link #level} * */ private static final String LEVEL_PROTECTED = "protected"; /** *package
parameter used by {@link #level} * */ private static final String LEVEL_PACKAGE = "package"; /** *private
parameter used by {@link #level} * */ private static final String LEVEL_PRIVATE = "private"; /** * The Clirr Maven plugin groupIdorg.codehaus.mojo
* */ private static final String CLIRR_MAVEN_PLUGIN_GROUPID = "org.codehaus.mojo"; /** * The Clirr Maven plugin artifactIdclirr-maven-plugin
* */ private static final String CLIRR_MAVEN_PLUGIN_ARTIFACTID = "clirr-maven-plugin"; /** * The latest Clirr Maven plugin version2.2.2
* */ private static final String CLIRR_MAVEN_PLUGIN_VERSION = "2.2.2"; /** * The Clirr Maven plugin goalcheck
* */ private static final String CLIRR_MAVEN_PLUGIN_GOAL = "check"; /** * Java Files Pattern. */ public static final String JAVA_FILES = "**\\/*.java"; /** * Default version value. */ public static final String DEFAULT_VERSION_VALUE = "\u0024Id: \u0024Id"; // ---------------------------------------------------------------------- // Mojo components // ---------------------------------------------------------------------- /** * Input handler, needed for command line handling. */ @Component private InputHandler inputHandler; // ---------------------------------------------------------------------- // Mojo parameters // ---------------------------------------------------------------------- /** * Version to compare the current code against using the * Clirr Maven Plugin. *
* See defaultSince. */ @Parameter ( property = "comparisonVersion", defaultValue = "(,${project.version})" ) private String comparisonVersion; /** * Default value for the Javadoc tag@author
. *
* If not specified, theuser.name
defined in the System properties will be used. */ @Parameter ( property = "defaultAuthor" ) private String defaultAuthor; /** * Default value for the Javadoc tag@since
. */ @Parameter ( property = "defaultSince", defaultValue = "${project.version}" ) private String defaultSince; /** * Default value for the Javadoc tag@version
. *
* By default, it is$Id:$
, corresponding to a * SVN keyword. * Refer to your SCM to use an other SCM keyword. */ @Parameter ( property = "defaultVersion", defaultValue = DEFAULT_VERSION_VALUE ) private String defaultVersion = "\u0024Id: \u0024"; // can't use default-value="\u0024Id: \u0024" /** * The file encoding to use when reading the source files. If the property *project.build.sourceEncoding
is not set, the platform default encoding is used. */ @Parameter ( property = "encoding", defaultValue = "${project.build.sourceEncoding}" ) private String encoding; /** * Comma separated excludes Java files, i.e.**/*Test.java
. */ @Parameter ( property = "excludes" ) private String excludes; /** * Comma separated tags to fix in classes, interfaces or methods Javadoc comments. * Possible values are: **
*/ @Parameter ( property = "fixTags", defaultValue = "all" ) private String fixTags; /** * Flag to fix the classes or interfaces Javadoc comments according the- all (fix all Javadoc tags)
*- author (fix only @author tag)
*- version (fix only @version tag)
*- since (fix only @since tag)
*- param (fix only @param tag)
*- return (fix only @return tag)
*- throws (fix only @throws tag)
*- link (fix only @link tag)
*level
. */ @Parameter ( property = "fixClassComment", defaultValue = "true" ) private boolean fixClassComment; /** * Flag to fix the fields Javadoc comments according thelevel
. */ @Parameter ( property = "fixFieldComment", defaultValue = "true" ) private boolean fixFieldComment; /** * Flag to fix the methods Javadoc comments according thelevel
. */ @Parameter ( property = "fixMethodComment", defaultValue = "true" ) private boolean fixMethodComment; /** * Flag to remove throws tags from unknown classes. */ @Parameter ( property = "removeUnknownThrows", defaultValue = "false" ) private boolean removeUnknownThrows; /** * Forcing the goal execution i.e. skip warranty messages (not recommended). */ @Parameter ( property = "force" ) private boolean force; /** * Flag to ignore or not Clirr. */ @Parameter ( property = "ignoreClirr", defaultValue = "false" ) protected boolean ignoreClirr; /** * Comma separated includes Java files, i.e.**/*Test.java
. * * Note: default value is {@code **\/*.java}. */ @Parameter ( property = "includes", defaultValue = JAVA_FILES ) private String includes; /** * Specifies the access level for classes and members to show in the Javadocs. * Possible values are: **
*/ @Parameter ( property = "level", defaultValue = "protected" ) private String level; /** * The local repository where the artifacts are located, used by the tests. */ @Parameter ( property = "localRepository" ) private ArtifactRepository localRepository; /** * Output directory where Java classes will be rewritten. */ @Parameter ( property = "outputDirectory", defaultValue = "${project.build.sourceDirectory}" ) private File outputDirectory; /** * The Maven Project Object. */ @Parameter( defaultValue = "${project}", readonly = true, required = true ) private MavenProject project; /** * The current user system settings for use in Maven. */ @Parameter( defaultValue = "${settings}", readonly = true, required = true ) private Settings settings; // ---------------------------------------------------------------------- // Internal fields // ---------------------------------------------------------------------- /** * The current project class loader. */ private ClassLoader projectClassLoader; /** * Split {@link #fixTags} by comma. * * @see {@link #init()} */ private String[] fixTagsSplitted; /** * New classes found by Clirr. */ private List- public * (shows only public classes and members)
*- protected * (shows only public and protected classes and members)
*- package * (shows all classes and members not marked private)
*- private * (shows all classes and members)
*clirrNewClasses; /** * New Methods in a Class (the key) found by Clirr. */ private Map > clirrNewMethods; /** * List of classes where *since
is added. Will be used to add or not this tag in the methods. */ private ListsinceClasses; /** * {@inheritDoc} */ @Override public void execute() throws MojoExecutionException, MojoFailureException { if ( !fixClassComment && !fixFieldComment && !fixMethodComment ) { getLog().info( "Specified to NOT fix classes, fields and methods. Nothing to do." ); return; } // verify goal params init(); if ( fixTagsSplitted.length == 0 ) { getLog().info( "No fix tag specified. Nothing to do." ); return; } // add warranty msg if ( !preCheck() ) { return; } // run clirr try { executeClirr(); } catch ( MavenInvocationException e ) { if ( getLog().isDebugEnabled() ) { getLog().error( "MavenInvocationException: " + e.getMessage(), e ); } else { getLog().error( "MavenInvocationException: " + e.getMessage() ); } getLog().info( "Clirr is ignored." ); } // run qdox and process try { Collection javaClasses = getQdoxClasses(); if ( javaClasses != null ) { for ( JavaClass javaClass : javaClasses ) { processFix( javaClass ); } } } catch ( IOException e ) { throw new MojoExecutionException( "IOException: " + e.getMessage(), e ); } } // ---------------------------------------------------------------------- // protected methods // ---------------------------------------------------------------------- /** * @param p not null maven project. * @return the artifact type. */ protected String getArtifactType( MavenProject p ) { return p.getArtifact().getType(); } /** * @param p not null maven project. * @return the list of source paths for the given project. */ protected List getProjectSourceRoots( MavenProject p ) { return ( p.getCompileSourceRoots() == null ? Collections. emptyList() : new LinkedList<>( p.getCompileSourceRoots() ) ); } /** * @param p not null * @return the compile classpath elements * @throws DependencyResolutionRequiredException * if any */ protected List getCompileClasspathElements( MavenProject p ) throws DependencyResolutionRequiredException { return ( p.getCompileClasspathElements() == null ? Collections. emptyList() : new LinkedList<>( p.getCompileClasspathElements() ) ); } /** * @param javaExecutable not null * @return the fully qualify name of javaMethod with signature */ protected static String getJavaMethodAsString( JavaExecutable javaExecutable ) { return javaExecutable.getDeclaringClass().getFullyQualifiedName() + "#" + javaExecutable.getCallSignature(); } // ---------------------------------------------------------------------- // private methods // ---------------------------------------------------------------------- /** * Init goal parameters. */ private void init() { // defaultAuthor if ( StringUtils.isEmpty( defaultAuthor ) ) { defaultAuthor = System.getProperty( "user.name" ); } // defaultSince int i = defaultSince.indexOf( "-" + Artifact.SNAPSHOT_VERSION ); if ( i != -1 ) { defaultSince = defaultSince.substring( 0, i ); } // fixTags if ( !FIX_TAGS_ALL.equalsIgnoreCase( fixTags.trim() ) ) { String[] split = StringUtils.split( fixTags, "," ); List filtered = new LinkedList<>(); for ( String aSplit : split ) { String s = aSplit.trim(); if ( JavadocUtil.equalsIgnoreCase( s, FIX_TAGS_ALL, AUTHOR_TAG, VERSION_TAG, SINCE_TAG, PARAM_TAG, THROWS_TAG, LINK_TAG, RETURN_TAG ) ) { filtered.add( s ); } else { if ( getLog().isWarnEnabled() ) { getLog().warn( "Unrecognized '" + s + "' for fixTags parameter. Ignored it!" ); } } } fixTags = StringUtils.join( filtered.iterator(), "," ); } fixTagsSplitted = StringUtils.split( fixTags, "," ); // encoding if ( StringUtils.isEmpty( encoding ) ) { if ( getLog().isWarnEnabled() ) { getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING + ", i.e. build is platform dependent!" ); } encoding = ReaderFactory.FILE_ENCODING; } // level level = level.trim(); if ( !JavadocUtil.equalsIgnoreCase( level, LEVEL_PUBLIC, LEVEL_PROTECTED, LEVEL_PACKAGE, LEVEL_PRIVATE ) ) { if ( getLog().isWarnEnabled() ) { getLog().warn( "Unrecognized '" + level + "' for level parameter, using 'protected' level." ); } level = "protected"; } } /** * @return true
if the user wants to proceed,false
otherwise. * @throws MojoExecutionException if any */ private boolean preCheck() throws MojoExecutionException { if ( force ) { return true; } if ( outputDirectory != null && !outputDirectory.getAbsolutePath().equals( getProjectSourceDirectory().getAbsolutePath() ) ) { return true; } if ( !settings.isInteractiveMode() ) { getLog().error( "Maven is not attempt to interact with the user for input. " + "Verify theconfiguration in your settings." ); return false; } getLog().warn( "" ); getLog().warn( " WARRANTY DISCLAIMER" ); getLog().warn( "" ); getLog().warn( "All warranties with regard to this Maven goal are disclaimed!" ); getLog().warn( "The changes will be done directly in the source code." ); getLog().warn( "The Maven Team strongly recommends the use of a SCM software BEFORE executing this goal." ); getLog().warn( "" ); while ( true ) { getLog().info( "Are you sure to proceed? [Y]es [N]o" ); try { String userExpression = inputHandler.readLine(); if ( userExpression == null || JavadocUtil.equalsIgnoreCase( userExpression, "Y", "Yes" ) ) { getLog().info( "OK, let's proceed..." ); break; } if ( JavadocUtil.equalsIgnoreCase( userExpression, "N", "No" ) ) { getLog().info( "No changes in your sources occur." ); return false; } } catch ( IOException e ) { throw new MojoExecutionException( "Unable to read from standard input.", e ); } } return true; } /** * @return the source dir as File for the given project */ private File getProjectSourceDirectory() { return new File( project.getBuild().getSourceDirectory() ); } /** * Invoke Maven to run clirr-maven-plugin to find API differences. * * @throws MavenInvocationException if any */ private void executeClirr() throws MavenInvocationException { if ( ignoreClirr ) { getLog().info( "Clirr is ignored." ); return; } String clirrGoal = getFullClirrGoal(); // http://mojo.codehaus.org/clirr-maven-plugin/check-mojo.html File clirrTextOutputFile = FileUtils.createTempFile( "clirr", ".txt", new File( project.getBuild().getDirectory() ) ); Properties properties = new Properties(); properties.put( "textOutputFile", clirrTextOutputFile.getAbsolutePath() ); properties.put( "comparisonVersion", comparisonVersion ); properties.put( "failOnError", "false" ); File invokerDir = new File( project.getBuild().getDirectory(), "invoker" ); invokerDir.mkdirs(); File invokerLogFile = FileUtils.createTempFile( "clirr-maven-plugin", ".txt", invokerDir ); new File( project.getBuild().getDirectory(), "invoker-clirr-maven-plugin.txt" ); JavadocUtil.invokeMaven( getLog(), new File( localRepository.getBasedir() ), project.getFile(), Collections.singletonList( clirrGoal ), properties, invokerLogFile ); try { if ( invokerLogFile.exists() ) { String invokerLogContent = StringUtils.unifyLineSeparators( FileUtils.fileRead( invokerLogFile, "UTF-8" ) ); // see org.codehaus.mojo.clirr.AbstractClirrMojo#getComparisonArtifact() final String artifactNotFoundMsg = "Unable to find a previous version of the project in the repository"; if ( invokerLogContent.contains( artifactNotFoundMsg ) ) { getLog().warn( "No previous artifact has been deployed, Clirr is ignored." ); return; } } } catch ( IOException e ) { getLog().debug( "IOException: " + e.getMessage() ); } try { parseClirrTextOutputFile( clirrTextOutputFile ); } catch ( IOException e ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "IOException: " + e.getMessage(), e ); } getLog().info( "IOException when parsing Clirr output '" + clirrTextOutputFile.getAbsolutePath() + "', Clirr is ignored." ); } } /** * @param clirrTextOutputFile not null * @throws IOException if any */ private void parseClirrTextOutputFile( File clirrTextOutputFile ) throws IOException { if ( !clirrTextOutputFile.exists() ) { if ( getLog().isInfoEnabled() ) { getLog().info( "No Clirr output file '" + clirrTextOutputFile.getAbsolutePath() + "' exists, Clirr is ignored." ); } return; } if ( getLog().isInfoEnabled() ) { getLog().info( "Clirr output file was created: " + clirrTextOutputFile.getAbsolutePath() ); } clirrNewClasses = new LinkedList<>(); clirrNewMethods = new LinkedHashMap<>(); BufferedReader reader = null; try { reader = new BufferedReader( ReaderFactory.newReader( clirrTextOutputFile, "UTF-8" ) ); for ( String line = reader.readLine(); line != null; line = reader.readLine() ) { String[] split = StringUtils.split( line, ":" ); if ( split.length != 4 ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Unable to parse the clirr line: " + line ); } continue; } int code; try { code = Integer.parseInt( split[1].trim() ); } catch ( NumberFormatException e ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Unable to parse the clirr line: " + line ); } continue; } // http://clirr.sourceforge.net/clirr-core/exegesis.html // 7011 - Method Added // 7012 - Method Added to Interface // 8000 - Class Added List list; String[] splits2; // CHECKSTYLE_OFF: MagicNumber switch ( code ) { case 7011: list = clirrNewMethods.get( split[2].trim() ); if ( list == null ) { list = new ArrayList<>(); } splits2 = StringUtils.split( split[3].trim(), "'" ); if ( splits2.length != 3 ) { continue; } list.add( splits2[1].trim() ); clirrNewMethods.put( split[2].trim(), list ); break; case 7012: list = clirrNewMethods.get( split[2].trim() ); if ( list == null ) { list = new ArrayList<>(); } splits2 = StringUtils.split( split[3].trim(), "'" ); if ( splits2.length != 3 ) { continue; } list.add( splits2[1].trim() ); clirrNewMethods.put( split[2].trim(), list ); break; case 8000: clirrNewClasses.add( split[2].trim() ); break; default: break; } // CHECKSTYLE_ON: MagicNumber } reader.close(); reader = null; } finally { IOUtils.closeQuietly( reader ); } if ( clirrNewClasses.isEmpty() && clirrNewMethods.isEmpty() ) { getLog().info( "Clirr NOT found API differences." ); } else { getLog().info( "Clirr found API differences, i.e. new classes/interfaces or methods." ); } } /** * @param tag not null * @return true
iftag
is defined in {@link #fixTags}. */ private boolean fixTag( String tag ) { if ( fixTagsSplitted.length == 1 && fixTagsSplitted[0].equals( FIX_TAGS_ALL ) ) { return true; } for ( String aFixTagsSplitted : fixTagsSplitted ) { if ( aFixTagsSplitted.trim().equals( tag ) ) { return true; } } return false; } /** * Calling Qdox to find {@link JavaClass} objects from the Maven project sources. * Ignore java class if Qdox has parsing errors. * * @return an array of {@link JavaClass} found by QDox * @throws IOException if any * @throws MojoExecutionException if any */ private CollectiongetQdoxClasses() throws IOException, MojoExecutionException { if ( "pom".equalsIgnoreCase( project.getPackaging() ) ) { getLog().warn( "This project has 'pom' packaging, no Java sources is available." ); return null; } List javaFiles = new LinkedList<>(); for ( String sourceRoot : getProjectSourceRoots( project ) ) { File f = new File( sourceRoot ); if ( f.isDirectory() ) { javaFiles.addAll( FileUtils.getFiles( f, includes, excludes, true ) ); } else { if ( getLog().isWarnEnabled() ) { getLog().warn( f + " doesn't exist. Ignored it." ); } } } ClassLibraryBuilder classLibraryBuilder = new OrderedClassLibraryBuilder(); classLibraryBuilder.appendClassLoader( getProjectClassLoader() ); JavaProjectBuilder builder = new JavaProjectBuilder( classLibraryBuilder ); builder.setEncoding( encoding ); for ( File f : javaFiles ) { if ( !f.getAbsolutePath().toLowerCase( Locale.ENGLISH ).endsWith( ".java" ) && getLog().isWarnEnabled() ) { getLog().warn( "'" + f + "' is not a Java file. Ignored it." ); continue; } try { builder.addSource( f ); } catch ( ParseException e ) { if ( getLog().isWarnEnabled() ) { getLog().warn( "QDOX ParseException: " + e.getMessage() + ". Can't fix it." ); } } } return builder.getClasses(); } /** * @return the classLoader for the given project using lazy instantiation. * @throws MojoExecutionException if any */ private ClassLoader getProjectClassLoader() throws MojoExecutionException { if ( projectClassLoader == null ) { List classPath; try { classPath = getCompileClasspathElements( project ); } catch ( DependencyResolutionRequiredException e ) { throw new MojoExecutionException( "DependencyResolutionRequiredException: " + e.getMessage(), e ); } List urls = new ArrayList<>( classPath.size() ); for ( String filename : classPath ) { try { urls.add( new File( filename ).toURL() ); } catch ( MalformedURLException e ) { throw new MojoExecutionException( "MalformedURLException: " + e.getMessage(), e ); } } projectClassLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null ); } return projectClassLoader; } /** * Process the given {@link JavaClass}, ie add missing javadoc tags depending user parameters. * * @param javaClass not null * @throws IOException if any * @throws MojoExecutionException if any */ private void processFix( JavaClass javaClass ) throws IOException, MojoExecutionException { // Skipping inner classes if ( javaClass.isInner() ) { return; } File javaFile = new File( javaClass.getSource().getURL().getFile() ); // the original java content in memory final String originalContent = StringUtils.unifyLineSeparators( FileUtils.fileRead( javaFile, encoding ) ); if ( getLog().isDebugEnabled() ) { getLog().debug( "Analyzing " + javaClass.getFullyQualifiedName() ); } final StringWriter stringWriter = new StringWriter(); BufferedReader reader = null; boolean changeDetected = false; try { reader = new BufferedReader( new StringReader( originalContent ) ); int lineNumber = 0; for ( String line = reader.readLine(); line != null; line = reader.readLine() ) { lineNumber++; final String indent = autodetectIndentation( line ); // fixing classes if ( javaClass.getComment() == null && javaClass.getAnnotations() != null && !javaClass.getAnnotations().isEmpty() ) { if ( lineNumber == javaClass.getAnnotations().get( 0 ).getLineNumber() ) { changeDetected |= fixClassComment( stringWriter, originalContent, javaClass, indent ); takeCareSingleComment( stringWriter, originalContent, javaClass ); } } else if ( lineNumber == javaClass.getLineNumber() ) { changeDetected |= fixClassComment( stringWriter, originalContent, javaClass, indent ); takeCareSingleComment( stringWriter, originalContent, javaClass ); } // fixing fields if ( javaClass.getFields() != null ) { for ( JavaField field : javaClass.getFields() ) { if ( lineNumber == field.getLineNumber() ) { changeDetected |= fixFieldComment( stringWriter, javaClass, field, indent ); } } } // fixing methods if ( javaClass.getConstructors() != null ) { for ( JavaConstructor method : javaClass.getConstructors() ) { if ( lineNumber == method.getLineNumber() ) { changeDetected |= fixMethodComment( stringWriter, originalContent, method, indent ); takeCareSingleComment( stringWriter, originalContent, method ); } } } // fixing methods for ( JavaMethod method : javaClass.getMethods() ) { int methodLineNumber; if ( method.getComment() == null && !method.getAnnotations().isEmpty() ) { methodLineNumber = method.getAnnotations().get( 0 ).getLineNumber(); } else { methodLineNumber = method.getLineNumber(); } if ( lineNumber == methodLineNumber ) { changeDetected |= fixMethodComment( stringWriter, originalContent, method, indent ); takeCareSingleComment( stringWriter, originalContent, method ); } } stringWriter.write( line ); stringWriter.write( EOL ); } reader.close(); reader = null; } finally { IOUtil.close( reader ); } if ( changeDetected ) { if ( getLog().isInfoEnabled() ) { getLog().info( "Saving changes to " + javaClass.getFullyQualifiedName() ); } if ( outputDirectory != null && !outputDirectory.getAbsolutePath().equals( getProjectSourceDirectory().getAbsolutePath() ) ) { String path = StringUtils.replace( javaFile.getAbsolutePath().replaceAll( "\\\\", "/" ), project.getBuild().getSourceDirectory().replaceAll( "\\\\", "/" ), "" ); javaFile = new File( outputDirectory, path ); javaFile.getParentFile().mkdirs(); } writeFile( javaFile, encoding, stringWriter.toString() ); } else { if ( getLog().isDebugEnabled() ) { getLog().debug( "No changes made to " + javaClass.getFullyQualifiedName() ); } } } /** * Take care of block or single comments between Javadoc comment and entity declaration ie: *
** 1 * /**
* * @param stringWriter not null * @param originalContent not null * @param entity not null * @param changeDetected * @return the updated changeDetected flag * @throws IOException if any * @see #extractOriginalJavadoc(String, AbstractJavaEntity) */ private void takeCareSingleComment( final StringWriter stringWriter, final String originalContent, final JavaAnnotatedElement entity ) throws IOException { if ( entity.getComment() == null ) { return; } String javadocComment = trimRight( extractOriginalJavadoc( originalContent, entity ) ); String extraComment = javadocComment.substring( javadocComment.indexOf( END_JAVADOC ) + END_JAVADOC.length() ); if ( StringUtils.isNotEmpty( extraComment ) ) { if ( extraComment.contains( EOL ) ) { stringWriter.write( extraComment.substring( extraComment.indexOf( EOL ) + EOL.length() ) ); } else { stringWriter.write( extraComment ); } stringWriter.write( EOL ); } } /** * Add/update Javadoc class comment. * * @param stringWriter * @param originalContent * @param javaClass * @param indent * @return {@code true} if the comment is updated, otherwise {@code false} * @throws MojoExecutionException * @throws IOException */ private boolean fixClassComment( final StringWriter stringWriter, final String originalContent, final JavaClass javaClass, final String indent ) throws MojoExecutionException, IOException { if ( !fixClassComment ) { return false; } if ( !isInLevel( javaClass.getModifiers() ) ) { return false; } // add if ( javaClass.getComment() == null ) { addDefaultClassComment( stringWriter, javaClass, indent ); return true; } // update return updateEntityComment( stringWriter, originalContent, javaClass, indent ); } /** * @param modifiers list of modifiers (public, private, protected, package) * @return
* 2 * * {Javadoc Comment}
* 3 * */
* 4 * /*
* 5 * * {Block Comment}
* 6 * */
* 7 * // {Single comment}
* 8 * public void * dummyMethod( * String s ){} *true
if modifier is align withlevel
. */ private boolean isInLevel( Listmodifiers ) { if ( LEVEL_PUBLIC.equalsIgnoreCase( level.trim() ) ) { return modifiers.contains( LEVEL_PUBLIC ); } if ( LEVEL_PROTECTED.equalsIgnoreCase( level.trim() ) ) { return ( modifiers.contains( LEVEL_PUBLIC ) || modifiers.contains( LEVEL_PROTECTED ) ); } if ( LEVEL_PACKAGE.equalsIgnoreCase( level.trim() ) ) { return !modifiers.contains( LEVEL_PRIVATE ); } // should be private (shows all classes and members) return true; } /** * Add a default Javadoc for the given class, i.e.: *
** 1 * /**
*
* 2 * * {Comment based on the class name}
* 3 * *
* 4 * * @author * X {added if addMissingAuthor}
* 5 * * @version * X {added if addMissingVersion}
* 6 * * @since * X {added if addMissingSince and new classes * from previous version}
* 7 * */
* 8 public class * DummyClass {}
** 1 * /** Constant <code> * MY_STRING_CONSTANT="value" * </code> */
* * @param stringWriter not null * @param field not null * @param indent not null * @throws IOException if any */ private void addDefaultFieldComment( final StringWriter stringWriter, final JavaField field, final String indent ) throws IOException { StringBuilder sb = new StringBuilder(); sb.append( indent ).append( START_JAVADOC ).append( " " ); sb.append( "Constant
* 2 * public static final * String MY_STRING_CONSTANT = * "value"; *" ).append( field.getName() ); if ( StringUtils.isNotEmpty( field.getInitializationExpression() ) ) { String qualifiedName = field.getType().getFullyQualifiedName(); if ( qualifiedName.equals( Byte.TYPE.toString() ) || qualifiedName.equals( Short.TYPE.toString() ) || qualifiedName.equals( Integer.TYPE.toString() ) || qualifiedName.equals( Long.TYPE.toString() ) || qualifiedName.equals( Float.TYPE.toString() ) || qualifiedName.equals( Double.TYPE.toString() ) || qualifiedName.equals( Boolean.TYPE.toString() ) || qualifiedName.equals( Character.TYPE.toString() ) ) { sb.append( "=" ); sb.append( field.getInitializationExpression().trim() ); } if ( qualifiedName.equals( String.class.getName() ) ) { StringBuilder value = new StringBuilder(); String[] lines = getLines( field.getInitializationExpression() ); for ( String line : lines ) { StringTokenizer token = new StringTokenizer( line.trim(), "\"\n\r" ); while ( token.hasMoreTokens() ) { String s = token.nextToken(); if ( s.trim().equals( "+" ) ) { continue; } if ( s.trim().endsWith( "\\" ) ) { s += "\""; } value.append( s ); } } sb.append( "=\"" ); // reduce the size // CHECKSTYLE_OFF: MagicNumber if ( value.length() < 40 ) { sb.append( value.toString() ).append( "\"" ); } else { sb.append( value.toString().substring( 0, 39 ) ).append( "\"{trunked}" ); } // CHECKSTYLE_ON: MagicNumber } } sb.append( "
" ).append( END_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); } /** * Add/update Javadoc method comment. * * @param stringWriter not null * @param originalContent not null * @param javaExecutable not null * @param indent not null * @return {@code true} if comment was updated, otherwise {@code false} * @throws MojoExecutionException if any * @throws IOException if any */ private boolean fixMethodComment( final StringWriter stringWriter, final String originalContent, final JavaExecutable javaExecutable, final String indent ) throws MojoExecutionException, IOException { if ( !fixMethodComment ) { return false; } if ( !javaExecutable.getDeclaringClass().isInterface() && !isInLevel( javaExecutable.getModifiers() ) ) { return false; } // add if ( javaExecutable.getComment() == null ) { addDefaultMethodComment( stringWriter, javaExecutable, indent ); return true; } // update return updateEntityComment( stringWriter, originalContent, javaExecutable, indent ); } /** * Add in the buffer a default Javadoc for the given class: *
** 1 * /**
* * @param buffer not null * @param javaExecutable not null * @param indent not null * @throws MojoExecutionException if any * @see #getDefaultMethodJavadocComment(JavaMethod) * @see #appendDefaultSinceTag(StringBuilder, String) */ private void addDefaultMethodComment( final StringWriter stringWriter, final JavaExecutable javaExecutable, final String indent ) throws MojoExecutionException { StringBuilder sb = new StringBuilder(); // special case if ( isInherited( javaExecutable ) ) { sb.append( indent ).append( INHERITED_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); return; } sb.append( indent ).append( START_JAVADOC ); sb.append( EOL ); sb.append( indent ).append( SEPARATOR_JAVADOC ); sb.append( getDefaultMethodJavadocComment( javaExecutable ) ); sb.append( EOL ); boolean separatorAdded = false; if ( fixTag( PARAM_TAG ) ) { if ( javaExecutable.getParameters() != null ) { for ( JavaParameter javaParameter : javaExecutable.getParameters() ) { separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, javaParameter ); } } // is generic? if ( javaExecutable.getTypeParameters() != null ) { for ( JavaTypeVariable
* 2 * * {Comment based on the method name}
* 3 * *
* 4 * * @param * X {added if addMissingParam}
* 5 * * @return * X {added if addMissingReturn}
* 6 * * @throws * X {added if addMissingThrows}
* 7 * * @since * X {added if addMissingSince and new classes * from previous version}
* 8 * */
* 9 public * void dummyMethod * ( String s * ){} *typeParam : javaExecutable.getTypeParameters() ) { separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, typeParam ); } } } if ( javaExecutable instanceof JavaMethod ) { JavaMethod javaMethod = (JavaMethod) javaExecutable; if ( fixTag( RETURN_TAG ) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() ) { separatorAdded = appendDefaultReturnTag( sb, indent, separatorAdded, javaMethod ); } } if ( fixTag( THROWS_TAG ) && javaExecutable.getExceptions() != null ) { for ( JavaType exception : javaExecutable.getExceptions() ) { separatorAdded = appendDefaultThrowsTag( sb, indent, separatorAdded, exception ); } } if ( fixTag( SINCE_TAG ) && isNewMethodFromLastRevision( javaExecutable ) ) { separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded ); } sb.append( indent ).append( " " ).append( END_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); } /** * @param stringWriter not null * @param originalContent not null * @param entity not null * @param indent not null * @param changeDetected * @return the updated changeDetected flag * @throws MojoExecutionException if any * @throws IOException if any */ private boolean updateEntityComment( final StringWriter stringWriter, final String originalContent, final JavaAnnotatedElement entity, final String indent ) throws MojoExecutionException, IOException { boolean changeDetected = false; String old = null; String s = stringWriter.toString(); int i = s.lastIndexOf( START_JAVADOC ); if ( i != -1 ) { String tmp = s.substring( 0, i ); if ( tmp.lastIndexOf( EOL ) != -1 ) { tmp = tmp.substring( 0, tmp.lastIndexOf( EOL ) ); } old = stringWriter.getBuffer().substring( i ); stringWriter.getBuffer().delete( 0, stringWriter.getBuffer().length() ); stringWriter.write( tmp ); stringWriter.write( EOL ); } else { changeDetected = true; } updateJavadocComment( stringWriter, originalContent, entity, indent ); if ( changeDetected ) { return true; // return now if we already know there's a change } return !stringWriter.getBuffer().substring( i ).equals( old ); } /** * @param stringWriter not null * @param originalContent not null * @param entity not null * @param indent not null * @throws MojoExecutionException if any * @throws IOException if any */ private void updateJavadocComment( final StringWriter stringWriter, final String originalContent, final JavaAnnotatedElement entity, final String indent ) throws MojoExecutionException, IOException { if ( entity.getComment() == null && ( entity.getTags() == null || entity.getTags().isEmpty() ) ) { return; } boolean isJavaExecutable = false; if ( entity instanceof JavaExecutable ) { isJavaExecutable = true; } StringBuilder sb = new StringBuilder(); // special case for inherited method if ( isJavaExecutable ) { JavaExecutable javaMethod = (JavaExecutable) entity; if ( isInherited( javaMethod ) ) { // QDOX-154 could be empty if ( StringUtils.isEmpty( javaMethod.getComment() ) ) { sb.append( indent ).append( INHERITED_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); return; } String javadoc = getJavadocComment( originalContent, javaMethod ); // case: /** {@inheritDoc} */ or no tags if ( hasInheritedTag( javadoc ) && ( javaMethod.getTags() == null || javaMethod.getTags().isEmpty() ) ) { sb.append( indent ).append( INHERITED_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); return; } if ( javadoc.contains( START_JAVADOC ) ) { javadoc = javadoc.substring( javadoc.indexOf( START_JAVADOC ) + START_JAVADOC.length() ); } if ( javadoc.contains( END_JAVADOC ) ) { javadoc = javadoc.substring( 0, javadoc.indexOf( END_JAVADOC ) ); } sb.append( indent ).append( START_JAVADOC ); sb.append( EOL ); if ( !javadoc.contains( INHERITED_TAG ) ) { sb.append( indent ).append( SEPARATOR_JAVADOC ).append( INHERITED_TAG ); sb.append( EOL ); appendSeparator( sb, indent ); } javadoc = removeLastEmptyJavadocLines( javadoc ); javadoc = alignIndentationJavadocLines( javadoc, indent ); sb.append( javadoc ); sb.append( EOL ); if ( javaMethod.getTags() != null ) { for ( DocletTag docletTag : javaMethod.getTags() ) { // Voluntary ignore these tags if ( JavadocUtil.equals( docletTag.getName(), PARAM_TAG, RETURN_TAG, THROWS_TAG ) ) { continue; } String s = getJavadocComment( originalContent, entity, docletTag ); s = removeLastEmptyJavadocLines( s ); s = alignIndentationJavadocLines( s, indent ); sb.append( s ); sb.append( EOL ); } } sb.append( indent ).append( " " ).append( END_JAVADOC ); sb.append( EOL ); if ( hasInheritedTag( sb.toString().trim() ) ) { sb = new StringBuilder(); sb.append( indent ).append( INHERITED_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); return; } stringWriter.write( sb.toString() ); return; } } sb.append( indent ).append( START_JAVADOC ); sb.append( EOL ); // comment if ( StringUtils.isNotEmpty( entity.getComment() ) ) { updateJavadocComment( sb, originalContent, entity, indent ); } else { addDefaultJavadocComment( sb, entity, indent, isJavaExecutable ); } // tags if ( entity.getTags() != null && !entity.getTags().isEmpty() ) { updateJavadocTags( sb, originalContent, entity, indent, isJavaExecutable ); } else { addDefaultJavadocTags( sb, entity, indent, isJavaExecutable ); } sb = new StringBuilder( removeLastEmptyJavadocLines( sb.toString() ) ).append( EOL ); sb.append( indent ).append( " " ).append( END_JAVADOC ); sb.append( EOL ); stringWriter.write( sb.toString() ); } /** * @param sb not null * @param originalContent not null * @param entity not null * @param indent not null * @throws IOException if any */ private void updateJavadocComment( final StringBuilder sb, final String originalContent, final JavaAnnotatedElement entity, final String indent ) throws IOException { String comment = getJavadocComment( originalContent, entity ); comment = removeLastEmptyJavadocLines( comment ); comment = alignIndentationJavadocLines( comment, indent ); if ( comment.contains( START_JAVADOC ) ) { comment = comment.substring( comment.indexOf( START_JAVADOC ) + START_JAVADOC.length() ); comment = indent + SEPARATOR_JAVADOC + comment.trim(); } if ( comment.contains( END_JAVADOC ) ) { comment = comment.substring( 0, comment.indexOf( END_JAVADOC ) ); } if ( fixTag( LINK_TAG ) ) { comment = replaceLinkTags( comment, entity ); } String[] lines = getLines( comment ); for ( String line : lines ) { sb.append( indent ).append( " " ).append( line.trim() ); sb.append( EOL ); } } static String replaceLinkTags( String comment, JavaAnnotatedElement entity ) { StringBuilder resolvedComment = new StringBuilder(); // scan comment for {@link someClassName} and try to resolve this Matcher linktagMatcher = Pattern.compile( "\\{@link\\s" ).matcher( comment ); int startIndex = 0; while ( linktagMatcher.find() ) { int startName = linktagMatcher.end(); resolvedComment.append( comment.substring( startIndex, startName ) ); int endName = comment.indexOf( "}", startName ); if ( endName >= 0 ) { String name; String link = comment.substring( startName, endName ); int hashIndex = link.indexOf( '#' ); if ( hashIndex >= 0 ) { name = link.substring( 0, hashIndex ); } else { name = link; } if ( StringUtils.isNotBlank( name ) ) { String typeName; if ( entity instanceof JavaClass ) { JavaClass clazz = (JavaClass) entity; typeName = TypeResolver.byClassName( clazz.getBinaryName(), clazz.getJavaClassLibrary(), clazz.getSource().getImports() ).resolveType( name.trim() ); } else if ( entity instanceof JavaMember ) { JavaClass clazz = ( (JavaMember) entity ).getDeclaringClass(); typeName = TypeResolver.byClassName( clazz.getBinaryName(), clazz.getJavaClassLibrary(), clazz.getSource().getImports() ).resolveType( name.trim() ); } else { typeName = null; } if ( typeName == null ) { typeName = name.trim(); } else { typeName = typeName.replaceAll( "\\$", "." ); } //adjust name for inner classes resolvedComment.append( typeName ); } if ( hashIndex >= 0 ) { resolvedComment.append( link.substring( hashIndex ).trim() ); } startIndex = endName; } else { startIndex = startName; } } resolvedComment.append( comment.substring( startIndex ) ); return resolvedComment.toString(); } /** * @param sb not null * @param entity not null * @param indent not null * @param isJavaExecutable */ private void addDefaultJavadocComment( final StringBuilder sb, final JavaAnnotatedElement entity, final String indent, final boolean isJavaExecutable ) { sb.append( indent ).append( SEPARATOR_JAVADOC ); if ( isJavaExecutable ) { sb.append( getDefaultMethodJavadocComment( (JavaExecutable) entity ) ); } else { sb.append( getDefaultClassJavadocComment( (JavaClass) entity ) ); } sb.append( EOL ); } /** * @param sb not null * @param originalContent not null * @param entity not null * @param indent not null * @param isJavaExecutable * @throws IOException if any * @throws MojoExecutionException if any */ private void updateJavadocTags( final StringBuilder sb, final String originalContent, final JavaAnnotatedElement entity, final String indent, final boolean isJavaExecutable ) throws IOException, MojoExecutionException { appendSeparator( sb, indent ); // parse tags JavaEntityTags javaEntityTags = parseJavadocTags( originalContent, entity, indent, isJavaExecutable ); // update and write tags updateJavadocTags( sb, entity, isJavaExecutable, javaEntityTags ); // add missing tags... addMissingJavadocTags( sb, entity, indent, isJavaExecutable, javaEntityTags ); } /** * Parse entity tags * * @param originalContent not null * @param entity not null * @param indent not null * @param isJavaMethod * @return an instance of {@link JavaEntityTags} * @throws IOException if any */ JavaEntityTags parseJavadocTags( final String originalContent, final JavaAnnotatedElement entity, final String indent, final boolean isJavaMethod ) throws IOException { JavaEntityTags javaEntityTags = new JavaEntityTags( entity, isJavaMethod ); for ( DocletTag docletTag : entity.getTags() ) { String originalJavadocTag = getJavadocComment( originalContent, entity, docletTag ); originalJavadocTag = removeLastEmptyJavadocLines( originalJavadocTag ); originalJavadocTag = alignIndentationJavadocLines( originalJavadocTag, indent ); javaEntityTags.getNamesTags().add( docletTag.getName() ); if ( isJavaMethod ) { List params = docletTag.getParameters(); if ( params.size() < 1 ) { continue; } String paramName = params.get( 0 ); if ( docletTag.getName().equals( PARAM_TAG ) ) { javaEntityTags.putJavadocParamTag( paramName, originalJavadocTag ); } else if ( docletTag.getName().equals( RETURN_TAG ) ) { javaEntityTags.setJavadocReturnTag( originalJavadocTag ); } else if ( docletTag.getName().equals( THROWS_TAG ) ) { javaEntityTags.putJavadocThrowsTag( paramName, originalJavadocTag ); } else { javaEntityTags.getUnknownTags().add( originalJavadocTag ); } } else { javaEntityTags.getUnknownTags().add( originalJavadocTag ); } } return javaEntityTags; } /** * Write tags according javaEntityTags. * * @param sb not null * @param entity not null * @param isJavaExecutable * @param javaEntityTags not null */ private void updateJavadocTags( final StringBuilder sb, final JavaAnnotatedElement entity, final boolean isJavaExecutable, final JavaEntityTags javaEntityTags ) { for ( DocletTag docletTag : entity.getTags() ) { if ( isJavaExecutable ) { JavaExecutable javaExecutable = (JavaExecutable) entity; List params = docletTag.getParameters(); if ( params.size() < 1 ) { continue; } if ( docletTag.getName().equals( PARAM_TAG ) ) { writeParamTag( sb, javaExecutable, javaEntityTags, params ); } else if ( docletTag.getName().equals( RETURN_TAG ) && javaExecutable instanceof JavaMethod ) { writeReturnTag( sb, (JavaMethod) javaExecutable, javaEntityTags ); } else if ( docletTag.getName().equals( THROWS_TAG ) ) { writeThrowsTag( sb, javaExecutable, javaEntityTags, params ); } else { // write unknown tags for ( Iterator it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); ) { String originalJavadocTag = it.next(); String simplified = StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim(); if ( simplified.contains( "@" + docletTag.getName() ) ) { it.remove(); sb.append( originalJavadocTag ); sb.append( EOL ); } } } } else { for ( Iterator it = javaEntityTags.getUnknownTags().iterator(); it.hasNext(); ) { String originalJavadocTag = it.next(); String simplified = StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim(); if ( simplified.contains( "@" + docletTag.getName() ) ) { it.remove(); sb.append( originalJavadocTag ); sb.append( EOL ); } } } if ( sb.toString().endsWith( EOL ) ) { sb.delete( sb.toString().lastIndexOf( EOL ), sb.toString().length() ); } sb.append( EOL ); } } private void writeParamTag( final StringBuilder sb, final JavaExecutable javaExecutable, final JavaEntityTags javaEntityTags, List params ) { String paramName = params.get( 0 ); if ( !fixTag( PARAM_TAG ) ) { // write original param tag if found String originalJavadocTag = javaEntityTags.getJavadocParamTag( paramName ); if ( originalJavadocTag != null ) { sb.append( originalJavadocTag ); } return; } boolean found = false; JavaParameter javaParam = javaExecutable.getParameterByName( paramName ); if ( javaParam == null ) { // is generic? List > typeParams = javaExecutable.getTypeParameters(); for ( JavaTypeVariable typeParam : typeParams ) { if ( typeParam.getGenericValue().equals( paramName ) ) { found = true; } } } else { found = true; } if ( !found ) { if ( getLog().isWarnEnabled() ) { getLog().warn( "Fixed unknown param '" + paramName + "' defined in " + getJavaMethodAsString( javaExecutable ) ); } if ( sb.toString().endsWith( EOL ) ) { sb.delete( sb.toString().lastIndexOf( EOL ), sb.toString().length() ); } } else { String originalJavadocTag = javaEntityTags.getJavadocParamTag( paramName ); if ( originalJavadocTag != null ) { sb.append( originalJavadocTag ); String s = "@" + PARAM_TAG + " " + paramName; if ( StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim().endsWith( s ) ) { sb.append( " " ); sb.append( getDefaultJavadocForType( javaParam.getJavaClass() ) ); } } } } private void writeReturnTag( final StringBuilder sb, final JavaMethod javaMethod, final JavaEntityTags javaEntityTags ) { String originalJavadocTag = javaEntityTags.getJavadocReturnTag(); if ( originalJavadocTag == null ) { return; } if ( !fixTag( RETURN_TAG ) ) { // write original param tag if found sb.append( originalJavadocTag ); return; } if ( StringUtils.isNotEmpty( originalJavadocTag ) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() ) { sb.append( originalJavadocTag ); if ( originalJavadocTag.trim().endsWith( "@" + RETURN_TAG ) ) { sb.append( " " ); sb.append( getDefaultJavadocForType( javaMethod.getReturns() ) ); } } } void writeThrowsTag( final StringBuilder sb, final JavaExecutable javaExecutable, final JavaEntityTags javaEntityTags, final List params ) { String exceptionClassName = params.get( 0 ); String originalJavadocTag = javaEntityTags.getJavadocThrowsTag( exceptionClassName ); if ( originalJavadocTag == null ) { return; } if ( !fixTag( THROWS_TAG ) ) { // write original param tag if found sb.append( originalJavadocTag ); return; } if ( javaExecutable.getExceptions() != null ) { for ( JavaType exception : javaExecutable.getExceptions() ) { if ( exception.getValue().endsWith( exceptionClassName ) ) { originalJavadocTag = StringUtils.replace( originalJavadocTag, exceptionClassName, exception.getFullyQualifiedName() ); if ( StringUtils.removeDuplicateWhitespace( originalJavadocTag ).trim().endsWith( "@" + THROWS_TAG + " " + exception.getValue() ) ) { originalJavadocTag += " if any."; } sb.append( originalJavadocTag ); // added qualified name javaEntityTags.putJavadocThrowsTag( exception.getValue(), originalJavadocTag ); return; } } } Class> clazz = getClass( javaExecutable.getDeclaringClass(), exceptionClassName ); if ( clazz != null ) { if ( ClassUtils.isAssignable( clazz, RuntimeException.class ) ) { sb.append( StringUtils.replace( originalJavadocTag, exceptionClassName, clazz.getName() ) ); // added qualified name javaEntityTags.putJavadocThrowsTag( clazz.getName(), originalJavadocTag ); } else if ( ClassUtils.isAssignable( clazz, Throwable.class ) ) { getLog().debug( "Removing '" + originalJavadocTag + "'; Throwable not specified by " + getJavaMethodAsString( javaExecutable ) + " and it is not a RuntimeException." ); } else { getLog().debug( "Removing '" + originalJavadocTag + "'; It is not a Throwable" ); } } else if ( removeUnknownThrows ) { getLog().warn( "Ignoring unknown throws '" + exceptionClassName + "' defined on " + getJavaMethodAsString( javaExecutable ) ); } else { getLog().warn( "Found unknown throws '" + exceptionClassName + "' defined on " + getJavaMethodAsString( javaExecutable ) ); sb.append( originalJavadocTag ); if ( params.size() == 1 ) { sb.append( " if any." ); } javaEntityTags.putJavadocThrowsTag( exceptionClassName, originalJavadocTag ); } } /** * Add missing tags not already written. * * @param sb not null * @param entity not null * @param indent not null * @param isJavaExecutable * @param javaEntityTags not null * @throws MojoExecutionException if any */ private void addMissingJavadocTags( final StringBuilder sb, final JavaAnnotatedElement entity, final String indent, final boolean isJavaExecutable, final JavaEntityTags javaEntityTags ) throws MojoExecutionException { if ( isJavaExecutable ) { JavaExecutable javaExecutable = (JavaExecutable) entity; if ( fixTag( PARAM_TAG ) ) { if ( javaExecutable.getParameters() != null ) { for ( JavaParameter javaParameter : javaExecutable.getParameters() ) { if ( javaEntityTags.getJavadocParamTag( javaParameter.getName(), true ) == null ) { appendDefaultParamTag( sb, indent, javaParameter ); } } } // is generic? if ( javaExecutable.getTypeParameters() != null ) { for ( JavaTypeVariable typeParam : javaExecutable.getTypeParameters() ) { if ( javaEntityTags.getJavadocParamTag( "<" + typeParam.getName() + ">", true ) == null ) { appendDefaultParamTag( sb, indent, typeParam ); } } } } if ( javaExecutable instanceof JavaMethod ) { JavaMethod javaMethod = (JavaMethod) javaExecutable; if ( fixTag( RETURN_TAG ) && StringUtils.isEmpty( javaEntityTags.getJavadocReturnTag() ) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() ) { appendDefaultReturnTag( sb, indent, javaMethod ); } } if ( fixTag( THROWS_TAG ) && javaExecutable.getExceptions() != null ) { for ( JavaType exception : javaExecutable.getExceptions() ) { if ( javaEntityTags.getJavadocThrowsTag( exception.getValue(), true ) == null ) { appendDefaultThrowsTag( sb, indent, exception ); } } } } else { if ( !javaEntityTags.getNamesTags().contains( AUTHOR_TAG ) ) { appendDefaultAuthorTag( sb, indent ); } if ( !javaEntityTags.getNamesTags().contains( VERSION_TAG ) ) { appendDefaultVersionTag( sb, indent ); } } if ( fixTag( SINCE_TAG ) && !javaEntityTags.getNamesTags().contains( SINCE_TAG ) ) { if ( !isJavaExecutable ) { if ( !ignoreClirr ) { if ( isNewClassFromLastVersion( (JavaClass) entity ) ) { appendDefaultSinceTag( sb, indent ); } } else { appendDefaultSinceTag( sb, indent ); addSinceClasses( (JavaClass) entity ); } } else { if ( !ignoreClirr ) { if ( isNewMethodFromLastRevision( (JavaExecutable) entity ) ) { appendDefaultSinceTag( sb, indent ); } } else if ( sinceClasses != null ) { if ( entity instanceof JavaMember && !sinceClassesContains( ( (JavaMember) entity ).getDeclaringClass() ) ) { appendDefaultSinceTag( sb, indent ); } else if ( entity instanceof JavaClass && !sinceClassesContains( ( (JavaClass) entity ).getDeclaringClass() ) ) { appendDefaultSinceTag( sb, indent ); } } } } } /** * @param sb not null * @param entity not null * @param indent not null * @param isJavaExecutable * @throws MojoExecutionException if any */ private void addDefaultJavadocTags( final StringBuilder sb, final JavaAnnotatedElement entity, final String indent, final boolean isJavaExecutable ) throws MojoExecutionException { boolean separatorAdded = false; if ( isJavaExecutable ) { JavaExecutable javaExecutable = (JavaExecutable) entity; if ( fixTag( PARAM_TAG ) && javaExecutable.getParameters() != null ) { for ( JavaParameter javaParameter : javaExecutable.getParameters() ) { separatorAdded = appendDefaultParamTag( sb, indent, separatorAdded, javaParameter ); } } if ( javaExecutable instanceof JavaMethod && fixTag( RETURN_TAG ) ) { JavaMethod javaMethod = (JavaMethod) javaExecutable; if ( javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid() ) { separatorAdded = appendDefaultReturnTag( sb, indent, separatorAdded, javaMethod ); } } if ( fixTag( THROWS_TAG ) && javaExecutable.getExceptions() != null ) { for ( JavaType exception : javaExecutable.getExceptions() ) { separatorAdded = appendDefaultThrowsTag( sb, indent, separatorAdded, exception ); } } } else { separatorAdded = appendDefaultAuthorTag( sb, indent, separatorAdded ); separatorAdded = appendDefaultVersionTag( sb, indent, separatorAdded ); } if ( fixTag( SINCE_TAG ) ) { if ( !isJavaExecutable ) { JavaClass javaClass = (JavaClass) entity; if ( !ignoreClirr ) { if ( isNewClassFromLastVersion( javaClass ) ) { separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded ); } } else { separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded ); addSinceClasses( javaClass ); } } else { JavaExecutable javaExecutable = (JavaExecutable) entity; if ( !ignoreClirr ) { if ( isNewMethodFromLastRevision( javaExecutable ) ) { separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded ); } } else { if ( sinceClasses != null && !sinceClassesContains( javaExecutable.getDeclaringClass() ) ) { separatorAdded = appendDefaultSinceTag( sb, indent, separatorAdded ); } } } } } /** * @param sb not null * @param indent not null * @param separatorAdded * @return true if separator has been added. */ private boolean appendDefaultAuthorTag( final StringBuilder sb, final String indent, boolean separatorAdded ) { if ( !fixTag( AUTHOR_TAG ) ) { return separatorAdded; } if ( !separatorAdded ) { appendSeparator( sb, indent ); separatorAdded = true; } appendDefaultAuthorTag( sb, indent ); return separatorAdded; } /** * @param sb not null * @param indent not null */ private void appendDefaultAuthorTag( final StringBuilder sb, final String indent ) { if ( !fixTag( AUTHOR_TAG ) ) { return; } sb.append( indent ).append( " * @" ).append( AUTHOR_TAG ).append( " " ); sb.append( defaultAuthor ); sb.append( EOL ); } /** * @param sb not null * @param indent not null * @param separatorAdded * @return true if separator has been added. */ private boolean appendDefaultSinceTag( final StringBuilder sb, final String indent, boolean separatorAdded ) { if ( !fixTag( SINCE_TAG ) ) { return separatorAdded; } if ( !separatorAdded ) { appendSeparator( sb, indent ); separatorAdded = true; } appendDefaultSinceTag( sb, indent ); return separatorAdded; } /** * @param sb not null * @param indent not null */ private void appendDefaultSinceTag( final StringBuilder sb, final String indent ) { if ( !fixTag( SINCE_TAG ) ) { return; } sb.append( indent ).append( " * @" ).append( SINCE_TAG ).append( " " ); sb.append( defaultSince ); sb.append( EOL ); } /** * @param sb not null * @param indent not null * @param separatorAdded * @return true if separator has been added. */ private boolean appendDefaultVersionTag( final StringBuilder sb, final String indent, boolean separatorAdded ) { if ( !fixTag( VERSION_TAG ) ) { return separatorAdded; } if ( !separatorAdded ) { appendSeparator( sb, indent ); separatorAdded = true; } appendDefaultVersionTag( sb, indent ); return separatorAdded; } /** * @param sb not null * @param indent not null */ private void appendDefaultVersionTag( final StringBuilder sb, final String indent ) { if ( !fixTag( VERSION_TAG ) ) { return; } sb.append( indent ).append( " * @" ).append( VERSION_TAG ).append( " " ); sb.append( defaultVersion ); sb.append( EOL ); } /** * @param sb not null * @param indent not null * @param separatorAdded * @param typeParam not null * @return true if separator has been added. */ private boolean appendDefaultParamTag( final StringBuilder sb, final String indent, boolean separatorAdded, final JavaParameter typeParam ) { if ( !fixTag( PARAM_TAG ) ) { return separatorAdded; } if ( !separatorAdded ) { appendSeparator( sb, indent ); separatorAdded = true; } appendDefaultParamTag( sb, indent, typeParam ); return separatorAdded; } /** * @param sb not null * @param indent not null * @param separatorAdded * @param typeParameter not null * @return true if separator has been added. */ private boolean appendDefaultParamTag( final StringBuilder sb, final String indent, boolean separatorAdded, final JavaTypeVariable typeParameter ) { if ( !fixTag( PARAM_TAG ) ) { return separatorAdded; } if ( !separatorAdded ) { appendSeparator( sb, indent ); separatorAdded = true; } appendDefaultParamTag( sb, indent, typeParameter ); return separatorAdded; } /** * @param sb not null * @param indent not null * @param typeParam not null */ private void appendDefaultParamTag( final StringBuilder sb, final String indent, final JavaParameter typeParam ) { if ( !fixTag( PARAM_TAG ) ) { return; } sb.append( indent ).append( " * @" ).append( PARAM_TAG ).append( " " ); sb.append( typeParam.getName() ); sb.append( " " ); sb.append( getDefaultJavadocForType( typeParam.getJavaClass() ) ); sb.append( EOL ); } /** * @param sb not null * @param indent not null * @param typeParameter not null */ private void appendDefaultParamTag( final StringBuilder sb, final String indent, final JavaTypeVariable typeParameter ) { if ( !fixTag( PARAM_TAG ) ) { return; } sb.append( indent ).append( " * @" ).append( PARAM_TAG ).append( " " ); sb.append( "<" ).append( typeParameter.getName() ).append( ">" ); sb.append( " " ); sb.append( getDefaultJavadocForType( typeParameter ) ); sb.append( EOL ); } /** * @param sb not null * @param indent not null * @param separatorAdded * @param javaMethod not null * @return true if separator has been added. */ private boolean appendDefaultReturnTag( final StringBuilder sb, final String indent, boolean separatorAdded, final JavaMethod javaMethod ) { if ( !fixTag( RETURN_TAG ) ) { return separatorAdded; } if ( !separatorAdded ) { appendSeparator( sb, indent ); separatorAdded = true; } appendDefaultReturnTag( sb, indent, javaMethod ); return separatorAdded; } /** * @param sb not null * @param indent not null * @param javaMethod not null */ private void appendDefaultReturnTag( final StringBuilder sb, final String indent, final JavaMethod javaMethod ) { if ( !fixTag( RETURN_TAG ) ) { return; } sb.append( indent ).append( " * @" ).append( RETURN_TAG ).append( " " ); sb.append( getDefaultJavadocForType( javaMethod.getReturns() ) ); sb.append( EOL ); } /** * @param sb not null * @param indent not null * @param separatorAdded * @param exception not null * @return true if separator has been added. */ private boolean appendDefaultThrowsTag( final StringBuilder sb, final String indent, boolean separatorAdded, final JavaType exception ) { if ( !fixTag( THROWS_TAG ) ) { return separatorAdded; } if ( !separatorAdded ) { appendSeparator( sb, indent ); separatorAdded = true; } appendDefaultThrowsTag( sb, indent, exception ); return separatorAdded; } /** * @param sb not null * @param indent not null * @param exception not null */ private void appendDefaultThrowsTag( final StringBuilder sb, final String indent, final JavaType exception ) { if ( !fixTag( THROWS_TAG ) ) { return; } sb.append( indent ).append( " * @" ).append( THROWS_TAG ).append( " " ); sb.append( exception.getFullyQualifiedName() ); sb.append( " if any." ); sb.append( EOL ); } /** * @param sb not null * @param indent not null */ private void appendSeparator( final StringBuilder sb, final String indent ) { sb.append( indent ).append( " *" ); sb.append( EOL ); } /** * Verify if a method has @java.lang.Override()
annotation or if it is an inherited method * from an interface or a super class. The goal is to handle{@inheritDoc}
tag. * * @param javaMethod not null * @returntrue
if the method is inherited,false
otherwise. * @throws MojoExecutionException if any */ private boolean isInherited( JavaExecutable javaMethod ) throws MojoExecutionException { if ( javaMethod.getAnnotations() != null ) { for ( JavaAnnotation annotation : javaMethod.getAnnotations() ) { if ( annotation.toString().equals( "@java.lang.Override()" ) ) { return true; } } } Class> clazz = getClass( javaMethod.getDeclaringClass().getFullyQualifiedName() ); List> interfaces = ClassUtils.getAllInterfaces( clazz ); for ( Class> intface : interfaces ) { if ( isInherited( intface, javaMethod ) ) { return true; } } List > classes = ClassUtils.getAllSuperclasses( clazz ); for ( Class> superClass : classes ) { if ( isInherited( superClass, javaMethod ) ) { return true; } } return false; } /** * @param clazz the Java class object, not null * @param javaMethod the QDox JavaMethod object not null * @return true
ifjavaMethod
exists in the givenclazz
, *false
otherwise. * @see #isInherited(JavaMethod) */ private boolean isInherited( Class> clazz, JavaExecutable javaMethod ) { for ( Method method : clazz.getDeclaredMethods() ) { if ( !method.getName().equals( javaMethod.getName() ) ) { continue; } if ( method.getParameterTypes().length != javaMethod.getParameters().size() ) { continue; } boolean found = false; int j = 0; for ( Class> paramType : method.getParameterTypes() ) { String name1 = paramType.getName(); String name2 = javaMethod.getParameters().get( j++ ).getType().getFullyQualifiedName(); found = name1.equals( name2 ); // TODO check algo, seems broken (only takes in account the last param) } return found; } return false; } /** * @param clazz * @return */ private String getDefaultJavadocForType( JavaClass clazz ) { StringBuilder sb = new StringBuilder(); if ( !JavaTypeVariable.class.isAssignableFrom( clazz.getClass() ) && clazz.isPrimitive() ) { if ( clazz.isArray() ) { sb.append( "an array of " ).append( clazz.getComponentType().getCanonicalName() ); } else { sb.append( "a " ).append( clazz.getCanonicalName() ); } return sb.append( "." ).toString(); } StringBuilder javadocLink = new StringBuilder(); try { getClass( clazz.getCanonicalName() ); javadocLink.append( "{@link " ); if ( clazz.isArray() ) { javadocLink.append( clazz.getComponentType().getCanonicalName() ); } else { javadocLink.append( clazz.getCanonicalName() ); } javadocLink.append( "}" ); } catch ( Exception e ) { javadocLink.append( clazz.getValue() ); } if ( clazz.isArray() ) { sb.append( "an array of " ).append( javadocLink ).append( " objects." ); } else { sb.append( "a " ).append( javadocLink ).append( " object." ); } return sb.toString(); } private String getDefaultJavadocForType( JavaTypeVariabletypeParameter ) { return "a " + typeParameter.getName() + " object."; } /** * Check under Clirr if this given class is newer from the last version. * * @param javaClass a given class not null * @return true
if Clirr said that this class is added from the last version, *false
otherwise or if {@link #clirrNewClasses} is null. */ private boolean isNewClassFromLastVersion( JavaClass javaClass ) { return ( clirrNewClasses != null ) && clirrNewClasses.contains( javaClass.getFullyQualifiedName() ); } /** * Check under Clirr if this given method is newer from the last version. * * @param javaExecutable a given method not null * @returntrue
if Clirr said that this method is added from the last version, *false
otherwise or if {@link #clirrNewMethods} is null. * @throws MojoExecutionException if any */ private boolean isNewMethodFromLastRevision( JavaExecutable javaExecutable ) throws MojoExecutionException { if ( clirrNewMethods == null ) { return false; } ListclirrMethods = clirrNewMethods.get( javaExecutable.getDeclaringClass().getFullyQualifiedName() ); if ( clirrMethods == null ) { return false; } for ( String clirrMethod : clirrMethods ) { // see net.sf.clirr.core.internal.checks.MethodSetCheck#getMethodId(JavaType clazz, Method method) String retrn = ""; if ( javaExecutable instanceof JavaMethod && ( (JavaMethod) javaExecutable ).getReturns() != null ) { retrn = ( (JavaMethod) javaExecutable ).getReturns().getFullyQualifiedName(); } StringBuilder params = new StringBuilder(); for ( JavaParameter parameter : javaExecutable.getParameters() ) { if ( params.length() > 0 ) { params.append( ", " ); } params.append( parameter.getResolvedFullyQualifiedName() ); } if ( clirrMethod.contains( retrn + " " ) && clirrMethod.contains( javaExecutable.getName() + "(" ) && clirrMethod.contains( "(" + params.toString() + ")" ) ) { return true; } } return false; } /** * @param className not null * @return the Class corresponding to the given class name using the project classloader. * @throws MojoExecutionException if class not found * @see {@link ClassUtils#getClass(ClassLoader, String, boolean)} * @see {@link #getProjectClassLoader()} */ private Class> getClass( String className ) throws MojoExecutionException { try { return ClassUtils.getClass( getProjectClassLoader(), className, false ); } catch ( ClassNotFoundException e ) { throw new MojoExecutionException( "ClassNotFoundException: " + e.getMessage(), e ); } } /** * Returns the Class object assignable for {@link RuntimeException} class and associated with the given * exception class name. * * @param currentClass not null * @param exceptionClassName not null, an exception class name defined as: * *
* @return the class if found, otherwise {@code null}. * @see #getClass(String) */ private Class> getClass( JavaClass currentClass, String exceptionClassName ) { String[] potentialClassNames = new String[]{ exceptionClassName, currentClass.getPackage().getName() + "." + exceptionClassName, currentClass.getPackage().getName() + "." + currentClass.getName() + "$" + exceptionClassName, "java.lang." + exceptionClassName }; Class> clazz = null; for ( String potentialClassName : potentialClassNames ) { try { clazz = getClass( potentialClassName ); } catch ( MojoExecutionException e ) { // nop } if ( clazz != null ) { return clazz; } } return null; } /** * @param javaClass not null */ private void addSinceClasses( JavaClass javaClass ) { if ( sinceClasses == null ) { sinceClasses = new ArrayList<>(); } sinceClasses.add( javaClass.getFullyQualifiedName() ); } private boolean sinceClassesContains( JavaClass javaClass ) { return sinceClasses.contains( javaClass.getFullyQualifiedName() ); } // ---------------------------------------------------------------------- // Static methods // ---------------------------------------------------------------------- /** * Write content into the given javaFile and using the given encoding. * All line separators will be unified. * * @param javaFile not null * @param encoding not null * @param content not null * @throws IOException if any */ private static void writeFile( final File javaFile, final String encoding, final String content ) throws IOException { Writer writer = null; try { writer = WriterFactory.newWriter( javaFile, encoding ); writer.write( StringUtils.unifyLineSeparators( content ) ); writer.close(); writer = null; } finally { IOUtil.close( writer ); } } /** * @return the full clirr goal, i.e.- exception class fully qualified
*- exception class in the same package
*- exception inner class
*- exception class in java.lang package
*groupId:artifactId:version:goal
. The clirr-plugin version * could be load from the pom.properties in the clirr-maven-plugin dependency. */ private static String getFullClirrGoal() { StringBuilder sb = new StringBuilder(); sb.append( CLIRR_MAVEN_PLUGIN_GROUPID ).append( ":" ).append( CLIRR_MAVEN_PLUGIN_ARTIFACTID ).append( ":" ); String clirrVersion = CLIRR_MAVEN_PLUGIN_VERSION; InputStream resourceAsStream = null; try { String resource = "META-INF/maven/" + CLIRR_MAVEN_PLUGIN_GROUPID + "/" + CLIRR_MAVEN_PLUGIN_ARTIFACTID + "/pom.properties"; resourceAsStream = AbstractFixJavadocMojo.class.getClassLoader().getResourceAsStream( resource ); if ( resourceAsStream != null ) { Properties properties = new Properties(); properties.load( resourceAsStream ); resourceAsStream.close(); resourceAsStream = null; if ( StringUtils.isNotEmpty( properties.getProperty( "version" ) ) ) { clirrVersion = properties.getProperty( "version" ); } } } catch ( IOException e ) { // nop } finally { IOUtil.close( resourceAsStream ); } sb.append( clirrVersion ).append( ":" ).append( CLIRR_MAVEN_PLUGIN_GOAL ); return sb.toString(); } /** * Default comment for class. * * @param javaClass not null * @return a default comment for class. */ private static String getDefaultClassJavadocComment( final JavaClass javaClass ) { StringBuilder sb = new StringBuilder(); sb.append( "" ); if ( javaClass.isAbstract() ) { sb.append( "Abstract " ); } sb.append( javaClass.getName() ); if ( !javaClass.isInterface() ) { sb.append( " class." ); } else { sb.append( " interface." ); } sb.append( "
" ); return sb.toString(); } /** * Default comment for method with taking care of getter/setter in the javaMethod name. * * @param javaExecutable not null * @return a default comment for method. */ private static String getDefaultMethodJavadocComment( final JavaExecutable javaExecutable ) { if ( javaExecutable instanceof JavaConstructor ) { return "Constructor for " + javaExecutable.getName() + ".
"; } if ( javaExecutable.getName().length() > 3 && ( javaExecutable.getName().startsWith( "get" ) || javaExecutable.getName().startsWith( "set" ) ) ) { String field = StringUtils.lowercaseFirstLetter( javaExecutable.getName().substring( 3 ) ); JavaClass clazz = javaExecutable.getDeclaringClass(); if ( clazz.getFieldByName( field ) == null ) { return "" + javaExecutable.getName() + ".
"; } StringBuilder sb = new StringBuilder(); sb.append( "" ); if ( javaExecutable.getName().startsWith( "get" ) ) { sb.append( "Getter " ); } else if ( javaExecutable.getName().startsWith( "set" ) ) { sb.append( "Setter " ); } sb.append( "for the field
" ); return sb.toString(); } return "" ).append( field ).append( "
." + javaExecutable.getName() + ".
"; } /** * Try to find if a Javadoc comment has an {@link #INHERITED_TAG} for instance: ** /** {@inheritDoc} */ ** or ** /** * * {@inheritDoc} * */ ** * @param content not null * @returntrue
if the content has an inherited tag,false
otherwise. */ private static boolean hasInheritedTag( final String content ) { final String inheritedTagPattern = "^\\s*(\\/\\*\\*)?(\\s*(\\*)?)*(\\{)@inheritDoc\\s*(\\})(\\s*(\\*)?)*(\\*\\/)?$"; return Pattern.matches( inheritedTagPattern, StringUtils.removeDuplicateWhitespace( content ) ); } /** * Workaround for QDOX-146 about whitespace. * Ideally we want to useentity.getComment()
*
* For instance, with the following snippet: *
* ** 1
* *
* 2 * /**
* 3 * * Dummy Javadoc comment.
* 4 * * @param * s a String
* 5 * */
* 6 * public void * dummyMethod( * String s ){}
*
* The return will be: *
* ** 1 * * Dummy Javadoc comment.
* * @param javaClassContent original class content not null * @param entity not null * @return the javadoc comment for the entity without any tags. * @throws IOException if any */ static String getJavadocComment( final String javaClassContent, final JavaAnnotatedElement entity ) throws IOException { if ( entity.getComment() == null ) { return ""; } String originalJavadoc = extractOriginalJavadocContent( javaClassContent, entity ); StringBuilder sb = new StringBuilder(); BufferedReader lr = new BufferedReader( new StringReader( originalJavadoc ) ); String line; while ( ( line = lr.readLine() ) != null ) { String l = StringUtils.removeDuplicateWhitespace( line.trim() ); if ( l.startsWith( "* @" ) || l.startsWith( "*@" ) ) { break; } sb.append( line ).append( EOL ); } return trimRight( sb.toString() ); } /** * Work around for QDOX-146 about whitespace. * Ideally we want to use
*docletTag.getValue()
*
* For instance, with the following snippet: *
* ** 1
* *
* 2 * /**
* 3 * * Dummy Javadoc comment.
* 4 * * @param * s a String
* 5 * */
* 6 * public void * dummyMethod( * String s ){}
*
* The return will be: *
* ** 1 * * @param * s a String
* * @param javaClassContent original class content not null * @param entity not null * @param docletTag not null * @return the javadoc comment for the entity without Javadoc tags. * @throws IOException if any */ String getJavadocComment( final String javaClassContent, final JavaAnnotatedElement entity, final DocletTag docletTag ) throws IOException { if ( docletTag.getValue() == null || docletTag.getParameters().isEmpty() ) { return ""; } String originalJavadoc = extractOriginalJavadocContent( javaClassContent, entity ); List
*params = docletTag.getParameters(); String paramValue = params.get( 0 ); StringBuilder sb = new StringBuilder(); BufferedReader lr = new BufferedReader( new StringReader( originalJavadoc ) ); String line; boolean found = false; while ( ( line = lr.readLine() ) != null ) { String l = StringUtils.removeDuplicateWhitespace( line.trim() ); if ( l.startsWith( "* @" + docletTag.getName() + " " + paramValue ) || l.startsWith( "*@" + docletTag.getName() + " " + paramValue ) ) { if ( fixTag( LINK_TAG ) ) { line = replaceLinkTags( line, entity ); } sb.append( line ).append( EOL ); found = true; } else { if ( l.startsWith( "* @" ) || l.startsWith( "*@" ) ) { found = false; } if ( found ) { if ( fixTag( LINK_TAG ) ) { line = replaceLinkTags( line, entity ); } sb.append( line ).append( EOL ); } } } return trimRight( sb.toString() ); } /** * Extract the original Javadoc and others comments up to {@link #START_JAVADOC} form the entity. This method * takes care of the Javadoc indentation. All javadoc lines will be trimmed on right. *
* For instance, with the following snippet: *
* ** 1
* *
* 2 * /**
* 3 * * Dummy Javadoc comment.
* 4 * * @param * s a String
* 5 * */
* 6 * public void * dummyMethod( * String s ){}
*
* The return will be: *
* ** 1 * /**
* * @param javaClassContent not null * @param entity not null * @return return the original javadoc as String for the current entity * @throws IOException if any */ static String extractOriginalJavadoc( final String javaClassContent, final JavaAnnotatedElement entity ) throws IOException { if ( entity.getComment() == null ) { return ""; } String[] javaClassContentLines = getLines( javaClassContent ); List
* 2 * * Dummy Javadoc comment.
* 3 * * @param * s a String
* 4 * */
*list = new LinkedList<>(); for ( int i = entity.getLineNumber() - 2; i >= 0; i-- ) { String line = javaClassContentLines[i]; list.add( trimRight( line ) ); if ( line.trim().startsWith( START_JAVADOC ) ) { break; } } Collections.reverse( list ); return StringUtils.join( list.iterator(), EOL ); } /** * Extract the Javadoc comment between {@link #START_JAVADOC} and {@link #END_JAVADOC} form the entity. This method * takes care of the Javadoc indentation. All javadoc lines will be trimmed on right. *
* For instance, with the following snippet: *
* ** 1
* *
* 2 * /**
* 3 * * Dummy Javadoc comment.
* 4 * * @param * s a String
* 5 * */
* 6 * public void * dummyMethod( * String s ){}
*
* The return will be: *
* ** 1 * * Dummy Javadoc comment.
* * @param javaClassContent not null * @param entity not null * @return return the original javadoc as String for the current entity * @throws IOException if any */ static String extractOriginalJavadocContent( final String javaClassContent, final JavaAnnotatedElement entity ) throws IOException { if ( entity.getComment() == null ) { return ""; } String originalJavadoc = extractOriginalJavadoc( javaClassContent, entity ); int index = originalJavadoc.indexOf( START_JAVADOC ); if ( index != -1 ) { originalJavadoc = originalJavadoc.substring( index + START_JAVADOC.length() ); } index = originalJavadoc.indexOf( END_JAVADOC ); if ( index != -1 ) { originalJavadoc = originalJavadoc.substring( 0, index ); } if ( originalJavadoc.startsWith( "\r\n" ) ) { originalJavadoc = originalJavadoc.substring( 2 ); } else if ( originalJavadoc.startsWith( "\n" ) || originalJavadoc.startsWith( "\r" ) ) { originalJavadoc = originalJavadoc.substring( 1 ); } return trimRight( originalJavadoc ); } /** * @param content not null * @return the content without last lines containing javadoc separator (ie
* 2 * * @param * s a String
**
) * @throws IOException if any * @see #getJavadocComment(String, JavaAnnotatedElement, DocletTag) */ private static String removeLastEmptyJavadocLines( final String content ) throws IOException { if ( !content.contains( EOL ) ) { return content; } String[] lines = getLines( content ); if ( lines.length == 1 ) { return content; } ListlinesList = new LinkedList<>( Arrays.asList( lines ) ); Collections.reverse( linesList ); for ( Iterator it = linesList.iterator(); it.hasNext(); ) { String line = it.next(); if ( line.trim().equals( "*" ) ) { it.remove(); } else { break; } } Collections.reverse( linesList ); return StringUtils.join( linesList.iterator(), EOL ); } /** * @param content not null * @return the javadoc comment with the given indentation * @throws IOException if any * @see #getJavadocComment(String, JavaAnnotatedElement, DocletTag) */ private static String alignIndentationJavadocLines( final String content, final String indent ) throws IOException { StringBuilder sb = new StringBuilder(); for ( String line : getLines( content ) ) { if ( sb.length() > 0 ) { sb.append( EOL ); } if ( !line.trim().startsWith( "*" ) ) { line = "*" + line; } sb.append( indent ).append( " " ).append( trimLeft( line ) ); } return sb.toString(); } /** * Autodetect the indentation of a given line: * * autodetectIndentation( null ) = ""; * autodetectIndentation( "a" ) = ""; * autodetectIndentation( " a" ) = " "; * autodetectIndentation( "\ta" ) = "\t"; ** * @param line not null * @return the indentation for the given line. */ private static String autodetectIndentation( final String line ) { if ( StringUtils.isEmpty( line ) ) { return ""; } return line.substring( 0, line.indexOf( trimLeft( line ) ) ); } /** * @param content not null * @return an array of all content lines * @throws IOException if any */ private static String[] getLines( final String content ) throws IOException { Listlines = new LinkedList<>(); BufferedReader reader = new BufferedReader( new StringReader( content ) ); String line = reader.readLine(); while ( line != null ) { lines.add( line ); line = reader.readLine(); } return lines.toArray( new String[lines.size()] ); } /** * Trim a given line on the left: * * trimLeft( null ) = ""; * trimLeft( " " ) = ""; * trimLeft( "a" ) = "a"; * trimLeft( " a" ) = "a"; * trimLeft( "\ta" ) = "a"; * trimLeft( " a " ) = "a "; ** * @param text * @return the text trimmed on left side or empty if text is null. */ private static String trimLeft( final String text ) { if ( StringUtils.isEmpty( text ) || StringUtils.isEmpty( text.trim() ) ) { return ""; } String textTrimmed = text.trim(); return text.substring( text.indexOf( textTrimmed ), text.length() ); } /** * Trim a given line on the right: ** trimRight( null ) = ""; * trimRight( " " ) = ""; * trimRight( "a" ) = "a"; * trimRight( "a\t" ) = "a"; * trimRight( " a " ) = " a"; ** * @param text * @return the text trimmed on tight side or empty if text is null. */ private static String trimRight( final String text ) { if ( StringUtils.isEmpty( text ) || StringUtils.isEmpty( text.trim() ) ) { return ""; } String textTrimmed = text.trim(); return text.substring( 0, text.indexOf( textTrimmed ) + textTrimmed.length() ); } /** * Wrapper class for the entity's tags. */ class JavaEntityTags { private final JavaAnnotatedElement entity; private final boolean isJavaMethod; /** * List of tag names. */ private ListnamesTags; /** * Map with java parameter as key and original Javadoc lines as values. */ private Map tagParams; /** * Original javadoc lines. */ private String tagReturn; /** * Map with java throw as key and original Javadoc lines as values. */ private Map tagThrows; /** * Original javadoc lines for unknown tags. */ private List unknownsTags; JavaEntityTags( JavaAnnotatedElement entity, boolean isJavaMethod ) { this.entity = entity; this.isJavaMethod = isJavaMethod; this.namesTags = new LinkedList<>(); this.tagParams = new LinkedHashMap<>(); this.tagThrows = new LinkedHashMap<>(); this.unknownsTags = new LinkedList<>(); } public List getNamesTags() { return namesTags; } public String getJavadocReturnTag() { return tagReturn; } public void setJavadocReturnTag( String s ) { tagReturn = s; } public List getUnknownTags() { return unknownsTags; } public void putJavadocParamTag( String paramName, String originalJavadocTag ) { tagParams.put( paramName, originalJavadocTag ); } public String getJavadocParamTag( String paramName ) { return getJavadocParamTag( paramName, false ); } public String getJavadocParamTag( String paramName, boolean nullable ) { String originalJavadocTag = tagParams.get( paramName ); if ( !nullable && originalJavadocTag == null && getLog().isWarnEnabled() ) { getLog().warn( getMessage( paramName, "javaEntityTags.tagParams" ) ); } return originalJavadocTag; } public void putJavadocThrowsTag( String paramName, String originalJavadocTag ) { tagThrows.put( paramName, originalJavadocTag ); } public String getJavadocThrowsTag( String paramName ) { return getJavadocThrowsTag( paramName, false ); } public String getJavadocThrowsTag( String paramName, boolean nullable ) { String originalJavadocTag = tagThrows.get( paramName ); if ( !nullable && originalJavadocTag == null && getLog().isWarnEnabled() ) { getLog().warn( getMessage( paramName, "javaEntityTags.tagThrows" ) ); } return originalJavadocTag; } private String getMessage( String paramName, String mapName ) { StringBuilder msg = new StringBuilder(); msg.append( "No param '" ).append( paramName ).append( "' key found in " ).append( mapName ) .append( " for the entity: " ); if ( isJavaMethod ) { JavaMethod javaMethod = (JavaMethod) entity; msg.append( getJavaMethodAsString( javaMethod ) ); } else { JavaClass javaClass = (JavaClass) entity; msg.append( javaClass.getFullyQualifiedName() ); } return msg.toString(); } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append( "namesTags=" ).append( namesTags ).append( "\n" ); sb.append( "tagParams=" ).append( tagParams ).append( "\n" ); sb.append( "tagReturn=" ).append( tagReturn ).append( "\n" ); sb.append( "tagThrows=" ).append( tagThrows ).append( "\n" ); sb.append( "unknownsTags=" ).append( unknownsTags ).append( "\n" ); return sb.toString(); } } }