| /* |
| * 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 <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html#where-tags-can-be-used">Where Tags Can Be Used</a> |
| * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> |
| * @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. <code>/**</code> * |
| */ |
| private static final String START_JAVADOC = "/**"; |
| |
| /** |
| * End Javadoc String i.e. <code>*/</code> * |
| */ |
| private static final String END_JAVADOC = "*/"; |
| |
| /** |
| * Javadoc Separator i.e. <code> * </code> * |
| */ |
| private static final String SEPARATOR_JAVADOC = " * "; |
| |
| /** |
| * Inherited Javadoc i.e. <code>/** {@inheritDoc} */</code> * |
| */ |
| private static final String INHERITED_JAVADOC = START_JAVADOC + " " + INHERITED_TAG + " " + END_JAVADOC; |
| |
| /** |
| * <code>all</code> parameter used by {@link #fixTags} * |
| */ |
| private static final String FIX_TAGS_ALL = "all"; |
| |
| /** |
| * <code>public</code> parameter used by {@link #level} * |
| */ |
| private static final String LEVEL_PUBLIC = "public"; |
| |
| /** |
| * <code>protected</code> parameter used by {@link #level} * |
| */ |
| private static final String LEVEL_PROTECTED = "protected"; |
| |
| /** |
| * <code>package</code> parameter used by {@link #level} * |
| */ |
| private static final String LEVEL_PACKAGE = "package"; |
| |
| /** |
| * <code>private</code> parameter used by {@link #level} * |
| */ |
| private static final String LEVEL_PRIVATE = "private"; |
| |
| /** |
| * The Clirr Maven plugin groupId <code>org.codehaus.mojo</code> * |
| */ |
| private static final String CLIRR_MAVEN_PLUGIN_GROUPID = "org.codehaus.mojo"; |
| |
| /** |
| * The Clirr Maven plugin artifactId <code>clirr-maven-plugin</code> * |
| */ |
| private static final String CLIRR_MAVEN_PLUGIN_ARTIFACTID = "clirr-maven-plugin"; |
| |
| /** |
| * The latest Clirr Maven plugin version <code>2.2.2</code> * |
| */ |
| private static final String CLIRR_MAVEN_PLUGIN_VERSION = "2.2.2"; |
| |
| /** |
| * The Clirr Maven plugin goal <code>check</code> * |
| */ |
| 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 |
| * <a href="http://mojo.codehaus.org/clirr-maven-plugin/">Clirr Maven Plugin</a>. |
| * <br/> |
| * See <a href="#defaultSince">defaultSince</a>. |
| */ |
| @Parameter(property = "comparisonVersion", defaultValue = "(,${project.version})") |
| private String comparisonVersion; |
| |
| /** |
| * Default value for the Javadoc tag <code>@author</code>. |
| * <br/> |
| * If not specified, the <code>user.name</code> defined in the System properties will be used. |
| */ |
| @Parameter(property = "defaultAuthor") |
| private String defaultAuthor; |
| |
| /** |
| * Default value for the Javadoc tag <code>@since</code>. |
| */ |
| @Parameter(property = "defaultSince", defaultValue = "${project.version}") |
| private String defaultSince; |
| |
| /** |
| * Default value for the Javadoc tag <code>@version</code>. |
| * <br/> |
| * By default, it is <code>$Id:$</code>, corresponding to a |
| * <a href="http://svnbook.red-bean.com/en/1.1/ch07s02.html#svn-ch-7-sect-2.3.4">SVN keyword</a>. |
| * 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 |
| * <code>project.build.sourceEncoding</code> 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. <code>**/*Test.java</code>. |
| */ |
| @Parameter(property = "excludes") |
| private String excludes; |
| |
| /** |
| * Comma separated tags to fix in classes, interfaces or methods Javadoc comments. |
| * Possible values are: |
| * <ul> |
| * <li>all (fix all Javadoc tags)</li> |
| * <li>author (fix only @author tag)</li> |
| * <li>version (fix only @version tag)</li> |
| * <li>since (fix only @since tag)</li> |
| * <li>param (fix only @param tag)</li> |
| * <li>return (fix only @return tag)</li> |
| * <li>throws (fix only @throws tag)</li> |
| * <li>link (fix only @link tag)</li> |
| * </ul> |
| */ |
| @Parameter(property = "fixTags", defaultValue = "all") |
| private String fixTags; |
| |
| /** |
| * Flag to fix the classes or interfaces Javadoc comments according the <code>level</code>. |
| */ |
| @Parameter(property = "fixClassComment", defaultValue = "true") |
| private boolean fixClassComment; |
| |
| /** |
| * Flag to fix the fields Javadoc comments according the <code>level</code>. |
| */ |
| @Parameter(property = "fixFieldComment", defaultValue = "true") |
| private boolean fixFieldComment; |
| |
| /** |
| * Flag to fix the methods Javadoc comments according the <code>level</code>. |
| */ |
| @Parameter(property = "fixMethodComment", defaultValue = "true") |
| private boolean fixMethodComment; |
| |
| /** |
| * <p>Flag to remove throws tags from unknown classes.</p> |
| * <p><strong>NOTE:</strong>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. <code>**/*Test.java</code>. |
| * <p/> |
| * <strong>Note:</strong> 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: |
| * <ul> |
| * <li>public (shows only public classes and members)</li> |
| * <li>protected (shows only public and protected classes and members)</li> |
| * <li>package (shows all classes and members not marked private)</li> |
| * <li>private (shows all classes and members)</li> |
| * </ul> |
| * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">private, protected, public, package options for Javadoc</a> |
| */ |
| @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<String> clirrNewClasses; |
| |
| /** |
| * New Methods in a Class (the key) found by Clirr. |
| */ |
| private Map<String, List<String>> clirrNewMethods; |
| |
| /** |
| * List of classes where <code>*since</code> is added. Will be used to add or not this tag in the methods. |
| */ |
| private List<String> 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<JavaClass> 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<String> getProjectSourceRoots(MavenProject p) { |
| return p.getCompileSourceRoots() == null |
| ? Collections.<String>emptyList() |
| : new LinkedList<>(p.getCompileSourceRoots()); |
| } |
| |
| /** |
| * @param p not null |
| * @return the compile classpath elements |
| * @throws DependencyResolutionRequiredException |
| * if any |
| */ |
| protected List<String> getCompileClasspathElements(MavenProject p) throws DependencyResolutionRequiredException { |
| return p.getCompileClasspathElements() == null |
| ? Collections.<String>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<String> 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 <code>true</code> if the user wants to proceed, <code>false</code> 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 <interactiveMode/> 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<String> 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 <code>true</code> if <code>tag</code> 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<JavaClass> getQdoxClasses() throws IOException, MojoExecutionException { |
| if ("pom".equalsIgnoreCase(project.getPackaging())) { |
| getLog().warn("This project has 'pom' packaging, no Java sources is available."); |
| return null; |
| } |
| |
| List<File> 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<String> classPath; |
| try { |
| classPath = getCompileClasspathElements(project); |
| } catch (DependencyResolutionRequiredException e) { |
| throw new MojoExecutionException("DependencyResolutionRequiredException: " + e.getMessage(), e); |
| } |
| |
| List<URL> 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()]), ClassLoader.getPlatformClassLoader()); |
| } |
| |
| 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: |
| * <br/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* {Javadoc Comment}</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f7f5f">/*</font><br /> |
| * <font color="#808080">5</font> <font color="#ffffff"> </font> |
| * <font color="#3f7f5f">* {Block Comment}</font><br /> |
| * <font color="#808080">6</font> <font color="#ffffff"> </font> |
| * <font color="#3f7f5f">*/</font><br /> |
| * <font color="#808080">7</font> <font color="#ffffff"> </font> |
| * <font color="#3f7f5f">// {Single comment}</font><br /> |
| * <font color="#808080">8</font> <font color="#ffffff"> </font> |
| * <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font> |
| * <font color="#000000">dummyMethod</font><font color="#000000">( </font> |
| * <font color="#000000">String s </font><font color="#000000">){}</font> |
| * </code> |
| * |
| * @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 <code>true</code> if modifier is align with <code>level</code>. |
| */ |
| private boolean isInLevel(List<String> 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.: |
| * <br/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* {Comment based on the class name}</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@author </font> |
| * <font color="#3f5fbf">X {added if addMissingAuthor}</font><br /> |
| * <font color="#808080">5</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@version </font> |
| * <font color="#3f5fbf">X {added if addMissingVersion}</font><br /> |
| * <font color="#808080">6</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@since </font> |
| * <font color="#3f5fbf">X {added if addMissingSince and new classes |
| * from previous version}</font><br /> |
| * <font color="#808080">7</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">8</font> <font color="#7f0055"><b>public class </b></font> |
| * <font color="#000000">DummyClass </font><font color="#000000">{}</font></code> |
| * </code> |
| * |
| * @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.: |
| * <br/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/** Constant </font><font color="#7f7f9f"><code></font> |
| * <font color="#3f5fbf">MY_STRING_CONSTANT="value"</font> |
| * <font color="#7f7f9f"></code> </font><font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#7f0055"><b>public static final </b></font> |
| * <font color="#000000">String MY_STRING_CONSTANT = </font> |
| * <font color="#2a00ff">"value"</font><font color="#000000">;</font> |
| * </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 <code>").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("</code> ").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: |
| * <br/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* {Comment based on the method name}</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">X {added if addMissingParam}</font><br /> |
| * <font color="#808080">5</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@return </font> |
| * <font color="#3f5fbf">X {added if addMissingReturn}</font><br /> |
| * <font color="#808080">6</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@throws </font> |
| * <font color="#3f5fbf">X {added if addMissingThrows}</font><br /> |
| * <font color="#808080">7</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@since </font> |
| * <font color="#3f5fbf">X {added if addMissingSince and new classes |
| * from previous version}</font><br /> |
| * <font color="#808080">8</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">9</font> <font color="#7f0055"><b>public </b></font> |
| * <font color="#7f0055"><b>void </b></font><font color="#000000">dummyMethod</font> |
| * <font color="#000000">( </font><font color="#000000">String s </font> |
| * <font color="#000000">){}</font> |
| * </code> |
| * |
| * @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<JavaGenericDeclaration> 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<String> 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<String> 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<String> 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<String> 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<JavaTypeVariable<JavaGenericDeclaration>> typeParams = javaExecutable.getTypeParameters(); |
| for (JavaTypeVariable<JavaGenericDeclaration> 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<String> 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<JavaGenericDeclaration> 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<JavaGenericDeclaration> 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<JavaGenericDeclaration> 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 <code>@java.lang.Override()</code> annotation or if it is an inherited method |
| * from an interface or a super class. The goal is to handle <code>{@inheritDoc}</code> tag. |
| * |
| * @param javaMethod not null |
| * @return <code>true</code> if the method is inherited, <code>false</code> 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<Class<?>> interfaces = ClassUtils.getAllInterfaces(clazz); |
| for (Class<?> intface : interfaces) { |
| if (isInherited(intface, javaMethod)) { |
| return true; |
| } |
| } |
| |
| List<Class<?>> 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 <code>true</code> if <code>javaMethod</code> exists in the given <code>clazz</code>, |
| * <code>false</code> 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<JavaGenericDeclaration> 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 <code>true</code> if Clirr said that this class is added from the last version, |
| * <code>false</code> 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 <code>true</code> if Clirr said that this method is added from the last version, |
| * <code>false</code> otherwise or if {@link #clirrNewMethods} is null. |
| * @throws MojoExecutionException if any |
| */ |
| private boolean isNewMethodFromLastRevision(JavaExecutable javaExecutable) throws MojoExecutionException { |
| if (clirrNewMethods == null) { |
| return false; |
| } |
| |
| List<String> 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: |
| * <ul> |
| * <li>exception class fully qualified</li> |
| * <li>exception class in the same package</li> |
| * <li>exception inner class</li> |
| * <li>exception class in java.lang package</li> |
| * </ul> |
| * @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. <code>groupId:artifactId:version:goal</code>. 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("<p>"); |
| if (javaClass.isAbstract()) { |
| sb.append("Abstract "); |
| } |
| |
| sb.append(javaClass.getName()); |
| |
| if (!javaClass.isInterface()) { |
| sb.append(" class."); |
| } else { |
| sb.append(" interface."); |
| } |
| |
| sb.append("</p>"); |
| |
| 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 "<p>Constructor for " + javaExecutable.getName() + ".</p>"; |
| } |
| |
| 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 "<p>" + javaExecutable.getName() + ".</p>"; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append("<p>"); |
| if (javaExecutable.getName().startsWith("get")) { |
| sb.append("Getter "); |
| } else if (javaExecutable.getName().startsWith("set")) { |
| sb.append("Setter "); |
| } |
| sb.append("for the field <code>").append(field).append("</code>.</p>"); |
| |
| return sb.toString(); |
| } |
| |
| return "<p>" + javaExecutable.getName() + ".</p>"; |
| } |
| |
| /** |
| * Try to find if a Javadoc comment has an {@link #INHERITED_TAG} for instance: |
| * <pre> |
| * /** {@inheritDoc} */ |
| * </pre> |
| * or |
| * <pre> |
| * /** |
| *  * {@inheritDoc} |
| *  */ |
| * </pre> |
| * |
| * @param content not null |
| * @return <code>true</code> if the content has an inherited tag, <code>false</code> 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 <code>entity.getComment()</code> |
| * <br/> |
| * For instance, with the following snippet: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"></font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* Dummy Javadoc comment.</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">s a String</font><br /> |
| * <font color="#808080">5</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">6</font> <font color="#ffffff"> </font> |
| * <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font> |
| * <font color="#000000">dummyMethod</font><font color="#000000">( </font> |
| * <font color="#000000">String s </font><font color="#000000">){}</font><br /> |
| * </code> |
| * <p/> |
| * <br/> |
| * The return will be: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* Dummy Javadoc comment.</font><br /> |
| * </code> |
| * |
| * @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 <code>docletTag.getValue()</code> |
| * <br/> |
| * For instance, with the following snippet: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"></font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* Dummy Javadoc comment.</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">s a String</font><br /> |
| * <font color="#808080">5</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">6</font> <font color="#ffffff"> </font> |
| * <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font> |
| * <font color="#000000">dummyMethod</font><font color="#000000">( </font> |
| * <font color="#000000">String s </font><font color="#000000">){}</font><br /> |
| * </code> |
| * <p/> |
| * <br/> |
| * The return will be: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">s a String</font><br /> |
| * </code> |
| * |
| * @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. |
| * <br/> |
| * For instance, with the following snippet: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"></font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* Dummy Javadoc comment.</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">s a String</font><br /> |
| * <font color="#808080">5</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">6</font> <font color="#ffffff"> </font> |
| * <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font> |
| * <font color="#000000">dummyMethod</font><font color="#000000">( </font> |
| * <font color="#000000">String s </font><font color="#000000">){}</font><br /> |
| * </code> |
| * <p/> |
| * <br/> |
| * The return will be: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* Dummy Javadoc comment.</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">s a String</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * </code> |
| * |
| * @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<String> 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. |
| * <br/> |
| * For instance, with the following snippet: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"></font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">/**</font><br /> |
| * <font color="#808080">3</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* Dummy Javadoc comment.</font><br /> |
| * <font color="#808080">4</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">s a String</font><br /> |
| * <font color="#808080">5</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">*/</font><br /> |
| * <font color="#808080">6</font> <font color="#ffffff"> </font> |
| * <font color="#7f0055"><b>public </b></font><font color="#7f0055"><b>void </b></font> |
| * <font color="#000000">dummyMethod</font><font color="#000000">( </font> |
| * <font color="#000000">String s </font><font color="#000000">){}</font><br /> |
| * </code> |
| * <p/> |
| * <br/> |
| * The return will be: |
| * <br/> |
| * <p/> |
| * <code> |
| * <font color="#808080">1</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* Dummy Javadoc comment.</font><br /> |
| * <font color="#808080">2</font> <font color="#ffffff"> </font> |
| * <font color="#3f5fbf">* </font><font color="#7f9fbf">@param </font> |
| * <font color="#3f5fbf">s a String</font><br /> |
| * </code> |
| * |
| * @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 <code> * </code>) |
| * @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<String> linesList = new LinkedList<>(Arrays.asList(lines)); |
| |
| Collections.reverse(linesList); |
| |
| for (Iterator<String> 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: |
| * <pre> |
| * autodetectIndentation( null ) = ""; |
| * autodetectIndentation( "a" ) = ""; |
| * autodetectIndentation( " a" ) = " "; |
| * autodetectIndentation( "\ta" ) = "\t"; |
| * </pre> |
| * |
| * @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<String> 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: |
| * <pre> |
| * trimLeft( null ) = ""; |
| * trimLeft( " " ) = ""; |
| * trimLeft( "a" ) = "a"; |
| * trimLeft( " a" ) = "a"; |
| * trimLeft( "\ta" ) = "a"; |
| * trimLeft( " a " ) = "a "; |
| * </pre> |
| * |
| * @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: |
| * <pre> |
| * trimRight( null ) = ""; |
| * trimRight( " " ) = ""; |
| * trimRight( "a" ) = "a"; |
| * trimRight( "a\t" ) = "a"; |
| * trimRight( " a " ) = " a"; |
| * </pre> |
| * |
| * @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<String> namesTags; |
| |
| /** |
| * Map with java parameter as key and original Javadoc lines as values. |
| */ |
| private Map<String, String> tagParams; |
| |
| private Set<String> documentedParams = new HashSet<>(); |
| |
| /** |
| * Original javadoc lines. |
| */ |
| private String tagReturn; |
| |
| /** |
| * Map with java throw as key and original Javadoc lines as values. |
| */ |
| private Map<String, String> tagThrows; |
| |
| /** |
| * Original javadoc lines for unknown tags. |
| */ |
| private List<String> 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<String> getNamesTags() { |
| return namesTags; |
| } |
| |
| public String getJavadocReturnTag() { |
| return tagReturn; |
| } |
| |
| public void setJavadocReturnTag(String s) { |
| tagReturn = s; |
| } |
| |
| public List<String> 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(); |
| } |
| } |
| } |