All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.javadoc.AbstractFixJavadocMojo Maven / Gradle / Ivy

/*
 * 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.
 */
package org.apache.maven.plugins.javadoc;

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.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
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.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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.lang3.ClassUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.execution.MavenSession;
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.languages.java.version.JavaVersion;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;

/**
 * Abstract class to fix Javadoc documentation and tags in source files.
 *
 * @see Where Tags Can Be Used
 * @author Vincent Siveton
 * @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 groupId org.codehaus.mojo *
     */
    private static final String CLIRR_MAVEN_PLUGIN_GROUPID = "org.codehaus.mojo";

    /**
     * The Clirr Maven plugin artifactId clirr-maven-plugin *
     */
    private static final String CLIRR_MAVEN_PLUGIN_ARTIFACTID = "clirr-maven-plugin";

    /**
     * The latest Clirr Maven plugin version 2.2.2 *
     */
    private static final String CLIRR_MAVEN_PLUGIN_VERSION = "2.2.2";

    /**
     * The Clirr Maven plugin goal check *
     */
    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, the user.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: *
    *
  • 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)
  • *
*/ @Parameter(property = "fixTags", defaultValue = "all") private String fixTags; /** * Flag to fix the classes or interfaces Javadoc comments according the level. */ @Parameter(property = "fixClassComment", defaultValue = "true") private boolean fixClassComment; /** * Flag to fix the fields Javadoc comments according the level. */ @Parameter(property = "fixFieldComment", defaultValue = "true") private boolean fixFieldComment; /** * Flag to fix the methods Javadoc comments according the level. */ @Parameter(property = "fixMethodComment", defaultValue = "true") private boolean fixMethodComment; /** *

Flag to remove throws tags from unknown classes.

*

NOTE:Since 3.1.0 the default value has been changed to {@code true}, * due to JavaDoc 8 strictness. */ @Parameter(property = "removeUnknownThrows", defaultValue = "true") 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: *

    *
  • 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)
  • *
* @see private, protected, public, package options for Javadoc */ @Parameter(property = "level", defaultValue = "protected") private String level; /** * 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; @Parameter(defaultValue = "${session}", readonly = true, required = true) private MavenSession session; /** * 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 #init() */ private String[] fixTagsSplitted; /** * New classes found by Clirr. */ private List 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 List sinceClasses; /** * {@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 // ---------------------------------------------------------------------- protected final MavenProject getProject() { return project; } /** * @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 (defaultAuthor == null || defaultAuthor.isEmpty()) { 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 (encoding == null || encoding.isEmpty()) { 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 the configuration 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"); if (JavaVersion.JAVA_SPECIFICATION_VERSION.isBefore("8")) { // ensure that Java7 picks up TLSv1.2 when connecting with Central properties.put("https.protocols", "TLSv1.2"); } File invokerDir = new File(project.getBuild().getDirectory(), "invoker"); invokerDir.mkdirs(); File invokerLogFile = FileUtils.createTempFile("clirr-maven-plugin", ".txt", invokerDir); JavadocUtil.invokeMaven( getLog(), session.getRepositorySession().getLocalRepository().getBasedir(), project.getFile(), Collections.singletonList(clirrGoal), properties, invokerLogFile, session.getRequest().getGlobalSettingsFile()); 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<>(); try (BufferedReader 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 // CHECKSTYLE_OFF: MagicNumber switch (code) { case 7011: methodAdded(split); break; case 7012: methodAdded(split); break; case 8000: clirrNewClasses.add(split[2].trim()); break; default: break; } // CHECKSTYLE_ON: MagicNumber } } 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."); } } private void methodAdded(String[] split) { List list = clirrNewMethods.get(split[2].trim()); if (list == null) { list = new ArrayList<>(); } String[] splits2 = StringUtils.split(split[3].trim(), "'"); if (splits2.length != 3) { return; } list.add(splits2[1].trim()); clirrNewMethods.put(split[2].trim(), list); } /** * @param tag not null * @return true if tag 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 Collection getQdoxClasses() 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).toURI().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; try { javaFile = Paths.get(javaClass.getSource().getURL().toURI()).toFile(); } catch (URISyntaxException e) { throw new MojoExecutionException(e.getMessage()); } // 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(); boolean changeDetected = false; try (BufferedReader 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()) { final boolean commentUpdated = fixMethodComment(stringWriter, originalContent, method, indent); if (commentUpdated) { takeCareSingleComment(stringWriter, originalContent, method); } changeDetected |= commentUpdated; } } } // 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) { final boolean commentUpdated = fixMethodComment(stringWriter, originalContent, method, indent); if (commentUpdated) { takeCareSingleComment(stringWriter, originalContent, method); } changeDetected |= commentUpdated; } } stringWriter.write(line); stringWriter.write(EOL); } } 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   * /**
* 2    * * {Javadoc Comment}
* 3    * */
* 4   * /*
* 5    * * {Block Comment}
* 6    * */
* 7   * // {Single comment}
* 8   * public void  * dummyMethod * String s ){} *
* * @param stringWriter not null * @param originalContent not null * @param entity not null * @throws IOException if any * @see #extractOriginalJavadoc */ 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 (extraComment != null && !extraComment.isEmpty()) { 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 true if modifier is align with level. */ private boolean isInLevel(List modifiers) { 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 {}
*
* * @param stringWriter 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.: *
* * 1      * /** Constant <code> * MY_STRING_CONSTANT="value" * </code> */
* 2      * public static final  * String MY_STRING_CONSTANT =  * "value"; *
* * @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 ").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(StringEscapeUtils.escapeHtml4( 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("=\""); String escapedValue = StringEscapeUtils.escapeHtml4(value.toString()); // reduce the size // CHECKSTYLE_OFF: MagicNumber if (escapedValue.length() < 40) { sb.append(escapedValue).append("\""); } else { sb.append(escapedValue, 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   * /**
* 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  * ){} *
* * @param stringWriter not null * @param javaExecutable not null * @param indent not null * @throws MojoExecutionException if any * @see #getDefaultMethodJavadocComment * @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 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 * @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 = entity instanceof JavaExecutable; 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 updateJavadocTags(sb, originalContent, 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); } } private static final Pattern REPLACE_LINK_TAGS_PATTERN = Pattern.compile("\\{@link\\s"); static String replaceLinkTags(String comment, JavaAnnotatedElement entity) { StringBuilder resolvedComment = new StringBuilder(); // scan comment for {@link someClassName} and try to resolve this Matcher linktagMatcher = REPLACE_LINK_TAGS_PATTERN.matcher(comment); int startIndex = 0; while (linktagMatcher.find()) { int startName = linktagMatcher.end(); resolvedComment.append(comment, 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); switch (docletTag.getName()) { case PARAM_TAG: javaEntityTags.putJavadocParamTag(paramName, docletTag.getValue(), originalJavadocTag); break; case RETURN_TAG: javaEntityTags.setJavadocReturnTag(originalJavadocTag); break; case THROWS_TAG: javaEntityTags.putJavadocThrowsTag(paramName, originalJavadocTag); break; default: javaEntityTags.getUnknownTags().add(originalJavadocTag); break; } } 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.get(0), docletTag.getValue()); } 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, String paramName, String paramValue) { if (!fixTag(PARAM_TAG)) { // write original param tag if found String originalJavadocTag = javaEntityTags.getJavadocParamTag(paramValue); 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.getName() + ">").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(paramValue); 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 ((originalJavadocTag != null && !originalJavadocTag.isEmpty()) && 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.getFullyQualifiedName().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 (RuntimeException.class.isAssignableFrom(clazz)) { sb.append(StringUtils.replace(originalJavadocTag, exceptionClassName, clazz.getName())); // added qualified name javaEntityTags.putJavadocThrowsTag(clazz.getName(), originalJavadocTag); } else if (Throwable.class.isAssignableFrom(clazz)) { 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.hasJavadocParamTag(javaParameter.getName())) { appendDefaultParamTag(sb, indent, javaParameter); } } } // is generic? if (javaExecutable.getTypeParameters() != null) { for (JavaTypeVariable typeParam : javaExecutable.getTypeParameters()) { if (!javaEntityTags.hasJavadocParamTag("<" + typeParam.getName() + ">")) { 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 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 * @return true 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 if javaMethod exists in the given clazz, * false otherwise. * @see #isInherited(JavaExecutable) */ 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.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(JavaTypeVariable typeParameter) { return "a " + typeParameter.getName() + " class"; } /** * 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 * @return true 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; } List clirrMethods = 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 ClassUtils#getClass(ClassLoader, String, boolean) * @see #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: *
    *
  • exception class fully qualified
  • *
  • exception class in the same package
  • *
  • exception inner class
  • *
  • exception class in java.lang package
  • *
* @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 { String unified = StringUtils.unifyLineSeparators(content); FileUtils.fileWrite(javaFile, encoding, unified); } /** * @return the full clirr goal, i.e. 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; String resource = "META-INF/maven/" + CLIRR_MAVEN_PLUGIN_GROUPID + "/" + CLIRR_MAVEN_PLUGIN_ARTIFACTID + "/pom.properties"; try (InputStream resourceAsStream = AbstractFixJavadocMojo.class.getClassLoader().getResourceAsStream(resource)) { if (resourceAsStream != null) { Properties properties = new Properties(); properties.load(resourceAsStream); if (StringUtils.isNotEmpty(properties.getProperty("version"))) { clirrVersion = properties.getProperty("version"); } } } catch (IOException e) { // nop } 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 ").append(field).append(".

"); return sb.toString(); } return "

" + javaExecutable.getName() + ".

"; } /** * Try to find if a Javadoc comment has an {@link #INHERITED_TAG} for instance: *
     * /** {@inheritDoc} */
     * 
* or *
     * /**
     *  * {@inheritDoc}
     *  */
     * 
* * @param content not null * @return true 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 use entity.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); StringBuilder sb = new StringBuilder(); BufferedReader lr = new BufferedReader(new StringReader(originalJavadoc)); String line; boolean found = false; // matching first line of doclettag Pattern p = Pattern.compile("(\\s*\\*\\s?@" + docletTag.getName() + ")\\s+" + "(\\Q" + docletTag.getValue().split("\r\n|\r|\n")[0] + "\\E)"); while ((line = lr.readLine()) != null) { Matcher m = p.matcher(line); if (m.matches()) { if (fixTag(LINK_TAG)) { line = replaceLinkTags(line, entity); } sb.append(line).append(EOL); found = true; } else { if (line.trim().startsWith("* @") || line.trim().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      * /**
* 2       * * Dummy Javadoc comment.
* 3       * @param  * s a String
* 4       * */
*
* * @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 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.
* 2       * @param  * s a String
*
* * @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 * ) * @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; } List linesList = 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 (line == null || line.isEmpty()) { 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 { List lines = 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 ((text == null || text.isEmpty()) || StringUtils.isEmpty(text.trim())) { return ""; } String textTrimmed = text.trim(); return text.substring(text.indexOf(textTrimmed)); } /** * 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 ((text == null || text.isEmpty()) || 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 List namesTags; /** * Map with java parameter as key and original Javadoc lines as values. */ private Map tagParams; private Set documentedParams = new HashSet<>(); /** * 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 paramValue, String originalJavadocTag) { documentedParams.add(paramName); tagParams.put(paramValue, originalJavadocTag); } public String getJavadocParamTag(String paramValue) { String originalJavadocTag = tagParams.get(paramValue); if (originalJavadocTag == null && getLog().isWarnEnabled()) { getLog().warn(getMessage(paramValue, "javaEntityTags.tagParams")); } return originalJavadocTag; } public boolean hasJavadocParamTag(String paramName) { return documentedParams.contains(paramName); } 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(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy