Java tutorial
package org.apache.maven.plugin.javadoc; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import com.thoughtworks.qdox.JavaDocBuilder; import com.thoughtworks.qdox.model.AbstractInheritableJavaEntity; import com.thoughtworks.qdox.model.AbstractJavaEntity; import com.thoughtworks.qdox.model.Annotation; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaField; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaParameter; import com.thoughtworks.qdox.model.Type; import com.thoughtworks.qdox.model.TypeVariable; import com.thoughtworks.qdox.parser.ParseException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ClassUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; import org.apache.maven.shared.invoker.MavenInvocationException; import org.codehaus.plexus.components.interactivity.InputHandler; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.ReaderFactory; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.WriterFactory; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Abstract class to fix Javadoc documentation and tags in source files. * <br/> * See <a href="http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#wheretags">Where Tags Can Be Used</a>. * * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> * @version $Id$ * @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"; public static final String JAVA_FILES = "**\\/*.java"; 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; /** * 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><a href="http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javadoc.html#public">public</a> * (shows only public classes and members)</li> * <li><a href="http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javadoc.html#protected">protected</a> * (shows only public and protected classes and members)</li> * <li><a href="http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javadoc.html#package">package</a> * (shows all classes and members not marked private)</li> * <li><a href="http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javadoc.html#private">private</a> * (shows all classes and members)</li> * </ul> */ @Parameter(property = "level", defaultValue = "protected") private String level; /** * The local repository where the artifacts are located, used by the tests. */ @Parameter(property = "localRepository") private ArtifactRepository localRepository; /** * Output directory where Java classes will be rewritten. */ @Parameter(property = "outputDirectory", defaultValue = "${project.build.sourceDirectory}") private File outputDirectory; /** * The Maven Project Object. */ @Component private MavenProject project; /** * The current user system settings for use in Maven. */ @Component private Settings settings; // ---------------------------------------------------------------------- // Internal fields // ---------------------------------------------------------------------- /** * The current project class loader. */ private ClassLoader projectClassLoader; /** * Split {@link #fixTags} by comma. * * @see {@link #init()} */ private String[] fixTagsSplitted; /** * New classes found by Clirr. */ private List<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} */ 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 { JavaClass[] javaClasses = getQdoxClasses(); if (javaClasses != null) { for (JavaClass javaClass : javaClasses) { processFix(javaClass); } } } catch (IOException e) { throw new MojoExecutionException("IOException: " + e.getMessage(), e); } } // ---------------------------------------------------------------------- // protected methods // ---------------------------------------------------------------------- /** * @param p not null maven project. * @return the artifact type. */ protected String getArtifactType(MavenProject p) { return p.getArtifact().getType(); } /** * @param p not null maven project. * @return the list of source paths for the given project. */ protected List<String> getProjectSourceRoots(MavenProject p) { return (p.getCompileSourceRoots() == null ? Collections.<String>emptyList() : new LinkedList<String>(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<String>(p.getCompileClasspathElements())); } /** * @param javaMethod not null * @return the fully qualify name of javaMethod with signature */ protected static String getJavaMethodAsString(JavaMethod javaMethod) { return javaMethod.getParentClass().getFullyQualifiedName() + "#" + javaMethod.getCallSignature(); } // ---------------------------------------------------------------------- // private methods // ---------------------------------------------------------------------- /** * Init goal parameters. */ private void init() { // defaultAuthor if (StringUtils.isEmpty(defaultAuthor)) { defaultAuthor = System.getProperty("user.name"); } // defaultSince int i = defaultSince.indexOf("-" + Artifact.SNAPSHOT_VERSION); if (i != -1) { defaultSince = defaultSince.substring(0, i); } // fixTags if (!FIX_TAGS_ALL.equalsIgnoreCase(fixTags.trim())) { String[] split = StringUtils.split(fixTags, ","); List<String> filtered = new LinkedList<String>(); 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)) { filtered.add(s); } else { if (getLog().isWarnEnabled()) { getLog().warn("Unrecognized '" + s + "' for fixTags parameter. Ignored it!"); } } } fixTags = StringUtils.join(filtered.iterator(), ","); } fixTagsSplitted = StringUtils.split(fixTags, ","); // encoding if (StringUtils.isEmpty(encoding)) { if (getLog().isWarnEnabled()) { getLog().warn("File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING + ", i.e. build is platform dependent!"); } encoding = ReaderFactory.FILE_ENCODING; } // level level = level.trim(); if (!JavadocUtil.equalsIgnoreCase(level, LEVEL_PUBLIC, LEVEL_PROTECTED, LEVEL_PACKAGE, LEVEL_PRIVATE)) { if (getLog().isWarnEnabled()) { getLog().warn("Unrecognized '" + level + "' for level parameter, using 'protected' level."); } level = "protected"; } } /** * @return <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"); File invokerDir = new File(project.getBuild().getDirectory(), "invoker"); invokerDir.mkdirs(); File invokerLogFile = FileUtils.createTempFile("clirr-maven-plugin", ".txt", invokerDir); new File(project.getBuild().getDirectory(), "invoker-clirr-maven-plugin.txt"); JavadocUtil.invokeMaven(getLog(), new File(localRepository.getBasedir()), project.getFile(), Collections.singletonList(clirrGoal), properties, invokerLogFile); try { if (invokerLogFile.exists()) { String invokerLogContent = StringUtils .unifyLineSeparators(FileUtils.fileRead(invokerLogFile, "UTF-8")); // see org.codehaus.mojo.clirr.AbstractClirrMojo#getComparisonArtifact() final String artifactNotFoundMsg = "Unable to find a previous version of the project in the repository"; if (invokerLogContent.contains(artifactNotFoundMsg)) { getLog().warn("No previous artifact has been deployed, Clirr is ignored."); return; } } } catch (IOException e) { getLog().debug("IOException: " + e.getMessage()); } try { parseClirrTextOutputFile(clirrTextOutputFile); } catch (IOException e) { if (getLog().isDebugEnabled()) { getLog().debug("IOException: " + e.getMessage(), e); } getLog().info("IOException when parsing Clirr output '" + clirrTextOutputFile.getAbsolutePath() + "', Clirr is ignored."); } } /** * @param clirrTextOutputFile not null * @throws IOException if any */ private void parseClirrTextOutputFile(File clirrTextOutputFile) throws IOException { if (!clirrTextOutputFile.exists()) { if (getLog().isInfoEnabled()) { getLog().info("No Clirr output file '" + clirrTextOutputFile.getAbsolutePath() + "' exists, Clirr is ignored."); } return; } if (getLog().isInfoEnabled()) { getLog().info("Clirr output file was created: " + clirrTextOutputFile.getAbsolutePath()); } clirrNewClasses = new LinkedList<String>(); clirrNewMethods = new LinkedHashMap<String, List<String>>(); BufferedReader input = null; Reader reader = null; try { reader = ReaderFactory.newReader(clirrTextOutputFile, "UTF-8"); input = new BufferedReader(reader); String line; while ((line = input.readLine()) != null) { String[] split = StringUtils.split(line, ":"); if (split.length != 4) { if (getLog().isDebugEnabled()) { getLog().debug("Unable to parse the clirr line: " + line); } continue; } int code; try { code = Integer.parseInt(split[1].trim()); } catch (NumberFormatException e) { if (getLog().isDebugEnabled()) { getLog().debug("Unable to parse the clirr line: " + line); } continue; } // http://clirr.sourceforge.net/clirr-core/exegesis.html // 7011 - Method Added // 7012 - Method Added to Interface // 8000 - Class Added List<String> list; String[] splits2; switch (code) { case 7011: list = clirrNewMethods.get(split[2].trim()); if (list == null) { list = new ArrayList<String>(); } splits2 = StringUtils.split(split[3].trim(), "'"); if (splits2.length != 3) { continue; } list.add(splits2[1].trim()); clirrNewMethods.put(split[2].trim(), list); break; case 7012: list = clirrNewMethods.get(split[2].trim()); if (list == null) { list = new ArrayList<String>(); } splits2 = StringUtils.split(split[3].trim(), "'"); if (splits2.length != 3) { continue; } list.add(splits2[1].trim()); clirrNewMethods.put(split[2].trim(), list); break; case 8000: clirrNewClasses.add(split[2].trim()); break; default: break; } } } finally { IOUtils.closeQuietly(reader); IOUtils.closeQuietly(input); } if (clirrNewClasses.isEmpty() && clirrNewMethods.isEmpty()) { getLog().info("Clirr NOT found API differences."); } else { getLog().info("Clirr found API differences, i.e. new classes/interfaces or methods."); } } /** * @param tag not null * @return <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 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<File>(); 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."); } } } JavaDocBuilder builder = new JavaDocBuilder(); builder.getClassLibrary().addClassLoader(getProjectClassLoader()); 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<URL>(classPath.size()); for (String filename : classPath) { try { urls.add(new File(filename).toURL()); } catch (MalformedURLException e) { throw new MojoExecutionException("MalformedURLException: " + e.getMessage(), e); } } projectClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), null); } return projectClassLoader; } /** * Process the given {@link JavaClass}, ie add missing javadoc tags depending user parameters. * * @param javaClass not null * @throws IOException if any * @throws MojoExecutionException if any */ private void processFix(JavaClass javaClass) throws IOException, MojoExecutionException { // Skipping inner classes if (javaClass.isInner()) { return; } File javaFile = new File(javaClass.getSource().getURL().getFile()); // the original java content in memory final String originalContent = StringUtils.unifyLineSeparators(FileUtils.fileRead(javaFile, encoding)); if (getLog().isDebugEnabled()) { getLog().debug("Fixing " + javaClass.getFullyQualifiedName()); } final StringWriter stringWriter = new StringWriter(); BufferedReader reader = null; try { reader = new BufferedReader(new StringReader(originalContent)); String line; int lineNumber = 0; while ((line = reader.readLine()) != null) { lineNumber++; final String indent = autodetectIndentation(line); // fixing classes if (javaClass.getComment() == null && javaClass.getAnnotations() != null && javaClass.getAnnotations().length != 0) { if (lineNumber == javaClass.getAnnotations()[0].getLineNumber()) { fixClassComment(stringWriter, originalContent, javaClass, indent); takeCareSingleComment(stringWriter, originalContent, javaClass); } } else { if (lineNumber == javaClass.getLineNumber()) { fixClassComment(stringWriter, originalContent, javaClass, indent); takeCareSingleComment(stringWriter, originalContent, javaClass); } } // fixing fields if (javaClass.getFields() != null) { for (int i = 0; i < javaClass.getFields().length; i++) { JavaField field = javaClass.getFields()[i]; if (lineNumber == field.getLineNumber()) { fixFieldComment(stringWriter, javaClass, field, indent); } } } // fixing methods if (javaClass.getMethods() != null) { for (int i = 0; i < javaClass.getMethods().length; i++) { JavaMethod method = javaClass.getMethods()[i]; if (lineNumber == method.getLineNumber()) { fixMethodComment(stringWriter, originalContent, method, indent); takeCareSingleComment(stringWriter, originalContent, method); } } } stringWriter.write(line); stringWriter.write(EOL); } } finally { IOUtil.close(reader); } if (getLog().isDebugEnabled()) { getLog().debug("Saving " + 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()); } /** * 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(String, AbstractJavaEntity) */ private void takeCareSingleComment(final StringWriter stringWriter, final String originalContent, final AbstractInheritableJavaEntity entity) throws IOException { if (entity.getComment() == null) { return; } String javadocComment = trimRight(extractOriginalJavadoc(originalContent, entity)); String extraComment = javadocComment.substring(javadocComment.indexOf(END_JAVADOC) + END_JAVADOC.length()); if (StringUtils.isNotEmpty(extraComment)) { if (extraComment.contains(EOL)) { stringWriter.write(extraComment.substring(extraComment.indexOf(EOL) + EOL.length())); } else { stringWriter.write(extraComment); } stringWriter.write(EOL); } } /** * Add/update Javadoc class comment. * * @param stringWriter * @param originalContent * @param javaClass * @param indent * @throws MojoExecutionException * @throws IOException */ private void fixClassComment(final StringWriter stringWriter, final String originalContent, final JavaClass javaClass, final String indent) throws MojoExecutionException, IOException { if (!fixClassComment) { return; } if (!isInLevel(javaClass.getModifiers())) { return; } // add if (javaClass.getComment() == null) { addDefaultClassComment(stringWriter, javaClass, indent); return; } // update 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(String[] modifiers) { List<String> modifiersAsList = Arrays.asList(modifiers); if (LEVEL_PUBLIC.equalsIgnoreCase(level.trim())) { return modifiersAsList.contains(LEVEL_PUBLIC); } if (LEVEL_PROTECTED.equalsIgnoreCase(level.trim())) { return (modifiersAsList.contains(LEVEL_PUBLIC) || modifiersAsList.contains(LEVEL_PROTECTED)); } if (LEVEL_PACKAGE.equalsIgnoreCase(level.trim())) { return !modifiersAsList.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 buffer not null * @param javaClass not null * @param indent not null * @see #getDefaultClassJavadocComment(JavaClass) * @see #appendDefaultAuthorTag(StringBuilder, String) * @see #appendDefaultSinceTag(StringBuilder, String) * @see #appendDefaultVersionTag(StringBuilder, String) */ private void addDefaultClassComment(final StringWriter stringWriter, final JavaClass javaClass, final String indent) { StringBuilder sb = new StringBuilder(); sb.append(indent).append(START_JAVADOC); sb.append(EOL); sb.append(indent).append(SEPARATOR_JAVADOC); sb.append(getDefaultClassJavadocComment(javaClass)); sb.append(EOL); appendSeparator(sb, indent); appendDefaultAuthorTag(sb, indent); appendDefaultVersionTag(sb, indent); if (fixTag(SINCE_TAG)) { if (!ignoreClirr) { if (isNewClassFromLastVersion(javaClass)) { appendDefaultSinceTag(sb, indent); } } else { appendDefaultSinceTag(sb, indent); addSinceClasses(javaClass); } } sb.append(indent).append(" ").append(END_JAVADOC); sb.append(EOL); stringWriter.write(sb.toString()); } /** * Add Javadoc field comment, only for static fields or interface fields. * * @param stringWriter not null * @param javaClass not null * @param field not null * @param indent not null * @throws IOException if any */ private void fixFieldComment(final StringWriter stringWriter, final JavaClass javaClass, final JavaField field, final String indent) throws IOException { if (!fixFieldComment) { return; } if (!javaClass.isInterface() && (!isInLevel(field.getModifiers()) || !field.isStatic())) { return; } // add if (field.getComment() == null) { addDefaultFieldComment(stringWriter, field, indent); return; } // no update } /** * 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().getJavaClass().getFullyQualifiedName(); if (qualifiedName.equals(Byte.TYPE.toString()) || qualifiedName.equals(Short.TYPE.toString()) || qualifiedName.equals(Integer.TYPE.toString()) || qualifiedName.equals(Long.TYPE.toString()) || qualifiedName.equals(Float.TYPE.toString()) || qualifiedName.equals(Double.TYPE.toString()) || qualifiedName.equals(Boolean.TYPE.toString()) || qualifiedName.equals(Character.TYPE.toString())) { sb.append("="); sb.append(field.getInitializationExpression().trim()); } if (qualifiedName.equals(String.class.getName())) { StringBuilder value = new StringBuilder(); String[] lines = getLines(field.getInitializationExpression()); for (String line : lines) { StringTokenizer token = new StringTokenizer(line.trim(), "\"\n\r"); while (token.hasMoreTokens()) { String s = token.nextToken(); if (s.trim().equals("+")) { continue; } if (s.trim().endsWith("\\")) { s += "\""; } value.append(s); } } sb.append("=\""); // reduce the size if (value.length() < 40) { sb.append(value.toString()).append("\""); } else { sb.append(value.toString().substring(0, 39)).append("\"{trunked}"); } } } 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 javaMethod not null * @param indent not null * @throws MojoExecutionException if any * @throws IOException if any */ private void fixMethodComment(final StringWriter stringWriter, final String originalContent, final JavaMethod javaMethod, final String indent) throws MojoExecutionException, IOException { if (!fixMethodComment) { return; } if (!javaMethod.getParentClass().isInterface() && !isInLevel(javaMethod.getModifiers())) { return; } // add if (javaMethod.getComment() == null) { addDefaultMethodComment(stringWriter, javaMethod, indent); return; } // update updateEntityComment(stringWriter, originalContent, javaMethod, 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 buffer not null * @param javaMethod not null * @param indent not null * @throws MojoExecutionException if any * @see #getDefaultMethodJavadocComment(JavaMethod) * @see #appendDefaultSinceTag(StringBuilder, String) */ private void addDefaultMethodComment(final StringWriter stringWriter, final JavaMethod javaMethod, final String indent) throws MojoExecutionException { StringBuilder sb = new StringBuilder(); // special case if (isInherited(javaMethod)) { 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(javaMethod)); sb.append(EOL); boolean separatorAdded = false; if (fixTag(PARAM_TAG)) { if (javaMethod.getParameters() != null) { for (int i = 0; i < javaMethod.getParameters().length; i++) { JavaParameter javaParameter = javaMethod.getParameters()[i]; separatorAdded = appendDefaultParamTag(sb, indent, separatorAdded, javaParameter); } } // is generic? if (javaMethod.getTypeParameters() != null) { for (int i = 0; i < javaMethod.getTypeParameters().length; i++) { TypeVariable typeParam = javaMethod.getTypeParameters()[i]; separatorAdded = appendDefaultParamTag(sb, indent, separatorAdded, typeParam); } } } if (fixTag(RETURN_TAG) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid()) { separatorAdded = appendDefaultReturnTag(sb, indent, separatorAdded, javaMethod); } if (fixTag(THROWS_TAG) && javaMethod.getExceptions() != null && javaMethod.getExceptions().length > 0) { for (int i = 0; i < javaMethod.getExceptions().length; i++) { Type exception = javaMethod.getExceptions()[i]; separatorAdded = appendDefaultThrowsTag(sb, indent, separatorAdded, exception); } } if (fixTag(SINCE_TAG) && isNewMethodFromLastRevision(javaMethod)) { 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 * @throws MojoExecutionException if any * @throws IOException if any */ private void updateEntityComment(final StringWriter stringWriter, final String originalContent, final AbstractInheritableJavaEntity entity, final String indent) throws MojoExecutionException, IOException { 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)); } stringWriter.getBuffer().delete(0, stringWriter.getBuffer().length()); stringWriter.write(tmp); stringWriter.write(EOL); } updateJavadocComment(stringWriter, originalContent, entity, indent); } /** * @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 AbstractInheritableJavaEntity entity, final String indent) throws MojoExecutionException, IOException { if (entity.getComment() == null && (entity.getTags() == null || entity.getTags().length == 0)) { return; } boolean isJavaMethod = false; if (entity instanceof JavaMethod) { isJavaMethod = true; } StringBuilder sb = new StringBuilder(); // special case for inherited method if (isJavaMethod) { JavaMethod javaMethod = (JavaMethod) 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().length == 0)) { 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 (int i = 0; i < javaMethod.getTags().length; i++) { DocletTag docletTag = javaMethod.getTags()[i]; // 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, isJavaMethod); } // tags if (entity.getTags() != null && entity.getTags().length > 0) { updateJavadocTags(sb, originalContent, entity, indent, isJavaMethod); } else { addDefaultJavadocTags(sb, entity, indent, isJavaMethod); } 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 AbstractInheritableJavaEntity 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 String replaceLinkTags(String comment, AbstractInheritableJavaEntity entity) { StringBuilder resolvedComment = new StringBuilder(); // scan comment for {@link someClassName} and try to resolve this Matcher linktagMatcher = Pattern.compile("\\{@link\\s").matcher(comment); int startIndex = 0; while (linktagMatcher.find()) { int startName = linktagMatcher.end(); resolvedComment.append(comment.substring(startIndex, startName)); int endName = comment.indexOf("}", startName); if (endName >= 0) { String name; String link = comment.substring(startName, endName); int hashIndex = link.indexOf('#'); if (hashIndex >= 0) { name = link.substring(0, hashIndex); } else { name = link; } if (StringUtils.isNotBlank(name)) { String typeName; if (entity instanceof JavaClass) { typeName = ((JavaClass) entity).resolveType(name.trim()); } else { typeName = entity.getParentClass().resolveType(name.trim()); } 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 isJavaMethod */ private void addDefaultJavadocComment(final StringBuilder sb, final AbstractInheritableJavaEntity entity, final String indent, final boolean isJavaMethod) { sb.append(indent).append(SEPARATOR_JAVADOC); if (isJavaMethod) { sb.append(getDefaultMethodJavadocComment((JavaMethod) 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 isJavaMethod * @throws IOException if any * @throws MojoExecutionException if any */ private void updateJavadocTags(final StringBuilder sb, final String originalContent, final AbstractInheritableJavaEntity entity, final String indent, final boolean isJavaMethod) throws IOException, MojoExecutionException { appendSeparator(sb, indent); // parse tags JavaEntityTags javaEntityTags = parseJavadocTags(originalContent, entity, indent, isJavaMethod); // update and write tags updateJavadocTags(sb, entity, isJavaMethod, javaEntityTags); // add missing tags... addMissingJavadocTags(sb, entity, indent, isJavaMethod, 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 */ private JavaEntityTags parseJavadocTags(final String originalContent, final AbstractInheritableJavaEntity entity, final String indent, final boolean isJavaMethod) throws IOException { JavaEntityTags javaEntityTags = new JavaEntityTags(entity, isJavaMethod); for (int i = 0; i < entity.getTags().length; i++) { DocletTag docletTag = entity.getTags()[i]; String originalJavadocTag = getJavadocComment(originalContent, entity, docletTag); originalJavadocTag = removeLastEmptyJavadocLines(originalJavadocTag); originalJavadocTag = alignIndentationJavadocLines(originalJavadocTag, indent); javaEntityTags.getNamesTags().add(docletTag.getName()); if (isJavaMethod) { String[] params = docletTag.getParameters(); if (params.length < 1) { continue; } params = fixQdox173(params); String paramName = params[0]; if (docletTag.getName().equals(PARAM_TAG)) { javaEntityTags.putJavadocParamTag(paramName, originalJavadocTag); } else if (docletTag.getName().equals(RETURN_TAG)) { javaEntityTags.setJavadocReturnTag(originalJavadocTag); } else if (docletTag.getName().equals(THROWS_TAG)) { javaEntityTags.putJavadocThrowsTag(paramName, originalJavadocTag); } else { javaEntityTags.getUnknownTags().add(originalJavadocTag); } } else { javaEntityTags.getUnknownTags().add(originalJavadocTag); } } return javaEntityTags; } /** * Write tags according javaEntityTags. * * @param sb not null * @param entity not null * @param isJavaMethod * @param javaEntityTags not null */ private void updateJavadocTags(final StringBuilder sb, final AbstractInheritableJavaEntity entity, final boolean isJavaMethod, final JavaEntityTags javaEntityTags) { for (int i = 0; i < entity.getTags().length; i++) { DocletTag docletTag = entity.getTags()[i]; if (isJavaMethod) { JavaMethod javaMethod = (JavaMethod) entity; String[] params = docletTag.getParameters(); if (params.length < 1) { continue; } if (docletTag.getName().equals(PARAM_TAG)) { writeParamTag(sb, javaMethod, javaEntityTags, params); } else if (docletTag.getName().equals(RETURN_TAG)) { writeReturnTag(sb, javaMethod, javaEntityTags); } else if (docletTag.getName().equals(THROWS_TAG)) { writeThrowsTag(sb, javaMethod, javaEntityTags, params); } else { // write unknown tags for (Iterator<String> it = javaEntityTags.getUnknownTags().iterator(); it.hasNext();) { String originalJavadocTag = it.next(); if (StringUtils.removeDuplicateWhitespace(originalJavadocTag).trim() .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(); if (StringUtils.removeDuplicateWhitespace(originalJavadocTag).trim() .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 JavaMethod javaMethod, final JavaEntityTags javaEntityTags, String[] params) { params = fixQdox173(params); String paramName = params[0]; if (!fixTag(PARAM_TAG)) { // write original param tag if found String originalJavadocTag = javaEntityTags.getJavadocParamTag(paramName); if (originalJavadocTag != null) { sb.append(originalJavadocTag); } return; } boolean found = false; JavaParameter javaParam = javaMethod.getParameterByName(paramName); if (javaParam == null) { // is generic? TypeVariable[] typeParams = javaMethod.getTypeParameters(); for (TypeVariable typeParam : typeParams) { if (typeParam.getGenericValue().equals(paramName)) { found = true; } } } else { found = true; } if (!found) { if (getLog().isWarnEnabled()) { getLog().warn( "Fixed unknown param '" + paramName + "' defined in " + getJavaMethodAsString(javaMethod)); } if (sb.toString().endsWith(EOL)) { sb.delete(sb.toString().lastIndexOf(EOL), sb.toString().length()); } } else { String originalJavadocTag = javaEntityTags.getJavadocParamTag(paramName); if (originalJavadocTag != null) { sb.append(originalJavadocTag); String s = "@" + PARAM_TAG + " " + paramName; if (StringUtils.removeDuplicateWhitespace(originalJavadocTag).trim().endsWith(s)) { sb.append(" "); sb.append(getDefaultJavadocForType(javaParam.getType())); } } } } private void writeReturnTag(final StringBuilder sb, final JavaMethod javaMethod, final JavaEntityTags javaEntityTags) { String originalJavadocTag = javaEntityTags.getJavadocReturnTag(); if (originalJavadocTag == null) { return; } if (!fixTag(RETURN_TAG)) { // write original param tag if found sb.append(originalJavadocTag); return; } if (StringUtils.isNotEmpty(originalJavadocTag) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid()) { sb.append(originalJavadocTag); if (originalJavadocTag.trim().endsWith("@" + RETURN_TAG)) { sb.append(" "); sb.append(getDefaultJavadocForType(javaMethod.getReturns())); } } } private void writeThrowsTag(final StringBuilder sb, final JavaMethod javaMethod, final JavaEntityTags javaEntityTags, final String[] params) { String exceptionClassName = params[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 (javaMethod.getExceptions() != null) { for (int j = 0; j < javaMethod.getExceptions().length; j++) { Type exception = javaMethod.getExceptions()[j]; if (exception.getValue().endsWith(exceptionClassName)) { originalJavadocTag = StringUtils.replace(originalJavadocTag, exceptionClassName, exception.getValue()); 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; } } } // Maybe a RuntimeException Class<?> clazz = getRuntimeExceptionClass(javaMethod.getParentClass(), exceptionClassName); if (clazz != null) { sb.append(StringUtils.replace(originalJavadocTag, exceptionClassName, clazz.getName())); // added qualified name javaEntityTags.putJavadocThrowsTag(clazz.getName(), originalJavadocTag); return; } if (getLog().isWarnEnabled()) { getLog().warn("Unknown throws exception '" + exceptionClassName + "' defined in " + getJavaMethodAsString(javaMethod)); } sb.append(originalJavadocTag); if (params.length == 1) { sb.append(" if any."); } } /** * Add missing tags not already written. * * @param sb not null * @param entity not null * @param indent not null * @param isJavaMethod * @param javaEntityTags not null * @throws MojoExecutionException if any */ private void addMissingJavadocTags(final StringBuilder sb, final AbstractInheritableJavaEntity entity, final String indent, final boolean isJavaMethod, final JavaEntityTags javaEntityTags) throws MojoExecutionException { if (isJavaMethod) { JavaMethod javaMethod = (JavaMethod) entity; if (fixTag(PARAM_TAG)) { if (javaMethod.getParameters() != null) { for (int i = 0; i < javaMethod.getParameters().length; i++) { JavaParameter javaParameter = javaMethod.getParameters()[i]; if (javaEntityTags.getJavadocParamTag(javaParameter.getName(), true) == null) { appendDefaultParamTag(sb, indent, javaParameter); } } } // is generic? if (javaMethod.getTypeParameters() != null) { for (int i = 0; i < javaMethod.getTypeParameters().length; i++) { TypeVariable typeParam = javaMethod.getTypeParameters()[i]; if (javaEntityTags.getJavadocParamTag("<" + typeParam.getName() + ">", true) == null) { appendDefaultParamTag(sb, indent, typeParam); } } } } if (fixTag(RETURN_TAG) && StringUtils.isEmpty(javaEntityTags.getJavadocReturnTag()) && javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid()) { appendDefaultReturnTag(sb, indent, javaMethod); } if (fixTag(THROWS_TAG) && javaMethod.getExceptions() != null) { for (int i = 0; i < javaMethod.getExceptions().length; i++) { Type exception = javaMethod.getExceptions()[i]; 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 (!isJavaMethod) { if (!ignoreClirr) { if (isNewClassFromLastVersion((JavaClass) entity)) { appendDefaultSinceTag(sb, indent); } } else { appendDefaultSinceTag(sb, indent); addSinceClasses((JavaClass) entity); } } else { if (!ignoreClirr) { if (isNewMethodFromLastRevision((JavaMethod) entity)) { appendDefaultSinceTag(sb, indent); } } else { if (sinceClasses != null && !sinceClassesContains(entity.getParentClass())) { appendDefaultSinceTag(sb, indent); } } } } } /** * @param sb not null * @param entity not null * @param indent not null * @param isJavaMethod * @throws MojoExecutionException if any */ private void addDefaultJavadocTags(final StringBuilder sb, final AbstractInheritableJavaEntity entity, final String indent, final boolean isJavaMethod) throws MojoExecutionException { boolean separatorAdded = false; if (isJavaMethod) { JavaMethod javaMethod = (JavaMethod) entity; if (fixTag(PARAM_TAG) && javaMethod.getParameters() != null) { for (int i = 0; i < javaMethod.getParameters().length; i++) { JavaParameter javaParameter = javaMethod.getParameters()[i]; separatorAdded = appendDefaultParamTag(sb, indent, separatorAdded, javaParameter); } } if (fixTag(RETURN_TAG)) { if (javaMethod.getReturns() != null && !javaMethod.getReturns().isVoid()) { separatorAdded = appendDefaultReturnTag(sb, indent, separatorAdded, javaMethod); } } if (fixTag(THROWS_TAG) && javaMethod.getExceptions() != null) { for (int i = 0; i < javaMethod.getExceptions().length; i++) { Type exception = javaMethod.getExceptions()[i]; separatorAdded = appendDefaultThrowsTag(sb, indent, separatorAdded, exception); } } } else { separatorAdded = appendDefaultAuthorTag(sb, indent, separatorAdded); separatorAdded = appendDefaultVersionTag(sb, indent, separatorAdded); } if (fixTag(SINCE_TAG)) { if (!isJavaMethod) { JavaClass javaClass = (JavaClass) entity; if (!ignoreClirr) { if (isNewClassFromLastVersion(javaClass)) { separatorAdded = appendDefaultSinceTag(sb, indent, separatorAdded); } } else { separatorAdded = appendDefaultSinceTag(sb, indent, separatorAdded); addSinceClasses(javaClass); } } else { JavaMethod javaMethod = (JavaMethod) entity; if (!ignoreClirr) { if (isNewMethodFromLastRevision(javaMethod)) { separatorAdded = appendDefaultSinceTag(sb, indent, separatorAdded); } } else { if (sinceClasses != null && !sinceClassesContains(javaMethod.getParentClass())) { separatorAdded = appendDefaultSinceTag(sb, indent, separatorAdded); } } } } } /** * @param sb not null * @param indent not null * @param separatorAdded * @return true if separator has been added. */ private boolean appendDefaultAuthorTag(final StringBuilder sb, final String indent, boolean separatorAdded) { if (!fixTag(AUTHOR_TAG)) { return separatorAdded; } if (!separatorAdded) { appendSeparator(sb, indent); separatorAdded = true; } appendDefaultAuthorTag(sb, indent); return separatorAdded; } /** * @param sb not null * @param indent not null */ private void appendDefaultAuthorTag(final StringBuilder sb, final String indent) { if (!fixTag(AUTHOR_TAG)) { return; } sb.append(indent).append(" * @").append(AUTHOR_TAG).append(" "); sb.append(defaultAuthor); sb.append(EOL); } /** * @param sb not null * @param indent not null * @param separatorAdded * @return true if separator has been added. */ private boolean appendDefaultSinceTag(final StringBuilder sb, final String indent, boolean separatorAdded) { if (!fixTag(SINCE_TAG)) { return separatorAdded; } if (!separatorAdded) { appendSeparator(sb, indent); separatorAdded = true; } appendDefaultSinceTag(sb, indent); return separatorAdded; } /** * @param sb not null * @param indent not null */ private void appendDefaultSinceTag(final StringBuilder sb, final String indent) { if (!fixTag(SINCE_TAG)) { return; } sb.append(indent).append(" * @").append(SINCE_TAG).append(" "); sb.append(defaultSince); sb.append(EOL); } /** * @param sb not null * @param indent not null * @param separatorAdded * @return true if separator has been added. */ private boolean appendDefaultVersionTag(final StringBuilder sb, final String indent, boolean separatorAdded) { if (!fixTag(VERSION_TAG)) { return separatorAdded; } if (!separatorAdded) { appendSeparator(sb, indent); separatorAdded = true; } appendDefaultVersionTag(sb, indent); return separatorAdded; } /** * @param sb not null * @param indent not null */ private void appendDefaultVersionTag(final StringBuilder sb, final String indent) { if (!fixTag(VERSION_TAG)) { return; } sb.append(indent).append(" * @").append(VERSION_TAG).append(" "); sb.append(defaultVersion); sb.append(EOL); } /** * @param sb not null * @param indent not null * @param separatorAdded * @param javaParameter not null * @return true if separator has been added. */ private boolean appendDefaultParamTag(final StringBuilder sb, final String indent, boolean separatorAdded, final JavaParameter javaParameter) { if (!fixTag(PARAM_TAG)) { return separatorAdded; } if (!separatorAdded) { appendSeparator(sb, indent); separatorAdded = true; } appendDefaultParamTag(sb, indent, javaParameter); 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 TypeVariable 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 javaParameter not null */ private void appendDefaultParamTag(final StringBuilder sb, final String indent, final JavaParameter javaParameter) { if (!fixTag(PARAM_TAG)) { return; } sb.append(indent).append(" * @").append(PARAM_TAG).append(" "); sb.append(javaParameter.getName()); sb.append(" "); sb.append(getDefaultJavadocForType(javaParameter.getType())); 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 TypeVariable 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 Type 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 Type exception) { if (!fixTag(THROWS_TAG)) { return; } sb.append(indent).append(" * @").append(THROWS_TAG).append(" "); sb.append(exception.getJavaClass().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(JavaMethod javaMethod) throws MojoExecutionException, SecurityException { if (javaMethod.getAnnotations() != null) { for (int i = 0; i < javaMethod.getAnnotations().length; i++) { Annotation annotation = javaMethod.getAnnotations()[i]; if (annotation.toString().equals("@java.lang.Override()")) { return true; } } } Class<?> clazz = getClass(javaMethod.getParentClass().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(JavaMethod) */ private boolean isInherited(Class<?> clazz, JavaMethod javaMethod) { for (Method method : clazz.getDeclaredMethods()) { if (!method.getName().equals(javaMethod.getName())) { continue; } if (method.getParameterTypes().length != javaMethod.getParameters().length) { continue; } boolean found = false; int j = 0; for (Class<?> paramType : method.getParameterTypes()) { String name1 = paramType.getName(); String name2 = javaMethod.getParameters()[j++].getType().getFullQualifiedName(); found = name1.equals(name2); // TODO check algo, seems broken (only takes in account the last param) } return found; } return false; } /** * @param type * @return */ private String getDefaultJavadocForType(Type type) { StringBuilder sb = new StringBuilder(); if (!TypeVariable.class.isAssignableFrom(type.getClass()) && type.isPrimitive()) { if (type.isArray()) { sb.append("an array of "); } else { sb.append("a "); } return sb.append(type.getJavaClass().getFullyQualifiedName()).append(".").toString(); } StringBuilder javadocLink = new StringBuilder(); try { getClass(type.getJavaClass().getFullyQualifiedName()); String s = type.getJavaClass().getFullyQualifiedName(); s = StringUtils.replace(s, "$", "."); javadocLink.append("{@link ").append(s).append("}"); } catch (Exception e) { javadocLink.append(type.getJavaClass().getFullyQualifiedName()); } if (type.isArray()) { sb.append("an array of ").append(javadocLink.toString()).append(" objects."); } else { sb.append("a ").append(javadocLink.toString()).append(" object."); } return sb.toString(); } /** * 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 javaMethod 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(JavaMethod javaMethod) throws MojoExecutionException { if (clirrNewMethods == null) { return false; } List<String> clirrMethods = clirrNewMethods.get(javaMethod.getParentClass().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 (javaMethod.getReturns() != null) { retrn = javaMethod.getReturns().getFullQualifiedName(); } StringBuilder params = new StringBuilder(); for (JavaParameter parameter : javaMethod.getParameters()) { if (params.length() > 0) { params.append(", "); } params.append(parameter.getResolvedValue()); } if (clirrMethod.contains(retrn + " ") && clirrMethod.contains(javaMethod.getName() + "(") && clirrMethod.contains("(" + params.toString() + ")")) { return true; } } return false; } /** * @param className not null * @return the Class corresponding to the given class name using the project classloader. * @throws MojoExecutionException if class not found * @see {@link ClassUtils#getClass(ClassLoader, String, boolean)} * @see {@link #getProjectClassLoader()} */ private Class<?> getClass(String className) throws MojoExecutionException { try { return ClassUtils.getClass(getProjectClassLoader(), className, false); } catch (ClassNotFoundException e) { throw new MojoExecutionException("ClassNotFoundException: " + e.getMessage(), e); } } /** * Returns the Class object assignable for {@link RuntimeException} class and associated with the given * exception class name. * * @param currentClass not null * @param exceptionClassName not null, an exception class name defined as: * <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 a RuntimeException assignable class. * @see #getClass(String) */ private Class<?> getRuntimeExceptionClass(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 && ClassUtils.isAssignable(clazz, RuntimeException.class)) { return clazz; } } return null; } /** * @param javaClass not null */ private void addSinceClasses(JavaClass javaClass) { if (sinceClasses == null) { sinceClasses = new ArrayList<String>(); } sinceClasses.add(javaClass.getFullyQualifiedName()); } private boolean sinceClassesContains(JavaClass javaClass) { return sinceClasses.contains(javaClass.getFullyQualifiedName()); } // ---------------------------------------------------------------------- // Static methods // ---------------------------------------------------------------------- /** * Write content into the given javaFile and using the given encoding. * All line separators will be unified. * * @param javaFile not null * @param encoding not null * @param content not null * @throws IOException if any */ private static void writeFile(final File javaFile, final String encoding, final String content) throws IOException { Writer writer = null; try { writer = WriterFactory.newWriter(javaFile, encoding); writer.write(StringUtils.unifyLineSeparators(content)); } finally { IOUtil.close(writer); } } /** * @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; InputStream resourceAsStream = null; try { String resource = "META-INF/maven/" + CLIRR_MAVEN_PLUGIN_GROUPID + "/" + CLIRR_MAVEN_PLUGIN_ARTIFACTID + "/pom.properties"; resourceAsStream = AbstractFixJavadocMojo.class.getClassLoader().getResourceAsStream(resource); if (resourceAsStream != null) { Properties properties = new Properties(); properties.load(resourceAsStream); if (StringUtils.isNotEmpty(properties.getProperty("version"))) { clirrVersion = properties.getProperty("version"); } } } catch (IOException e) { // nop } finally { IOUtil.close(resourceAsStream); } sb.append(clirrVersion).append(":").append(CLIRR_MAVEN_PLUGIN_GOAL); return sb.toString(); } /** * Default comment for class. * * @param javaClass not null * @return a default comment for class. */ private static String getDefaultClassJavadocComment(final JavaClass javaClass) { StringBuilder sb = new StringBuilder(); sb.append("<p>"); if (Arrays.asList(javaClass.getModifiers()).contains("abstract")) { 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 javaMethod not null * @return a default comment for method. */ private static String getDefaultMethodJavadocComment(final JavaMethod javaMethod) { if (javaMethod.isConstructor()) { return "<p>Constructor for " + javaMethod.getName() + ".</p>"; } if (javaMethod.getName().length() > 3 && (javaMethod.getName().startsWith("get") || javaMethod.getName().startsWith("set"))) { String field = StringUtils.lowercaseFirstLetter(javaMethod.getName().substring(3)); JavaClass clazz = javaMethod.getParentClass(); if (clazz.getFieldByName(field) == null) { return "<p>" + javaMethod.getName() + ".</p>"; } StringBuilder sb = new StringBuilder(); sb.append("<p>"); if (javaMethod.getName().startsWith("get")) { sb.append("Getter "); } else if (javaMethod.getName().startsWith("set")) { sb.append("Setter "); } sb.append("for the field <code>").append(field).append("</code>.</p>"); return sb.toString(); } return "<p>" + javaMethod.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 */ private static String getJavadocComment(final String javaClassContent, final AbstractJavaEntity 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 */ private String getJavadocComment(final String javaClassContent, final AbstractInheritableJavaEntity entity, final DocletTag docletTag) throws IOException { if (docletTag.getValue() == null || docletTag.getParameters().length == 0) { return ""; } String originalJavadoc = extractOriginalJavadocContent(javaClassContent, entity); String[] params = fixQdox173(docletTag.getParameters()); String paramValue = params[0]; StringBuilder sb = new StringBuilder(); BufferedReader lr = new BufferedReader(new StringReader(originalJavadoc)); String line; boolean found = false; while ((line = lr.readLine()) != null) { String l = StringUtils.removeDuplicateWhitespace(line.trim()); if (l.startsWith("* @" + docletTag.getName() + " " + paramValue) || l.startsWith("*@" + docletTag.getName() + " " + paramValue)) { if (fixTag(LINK_TAG)) { line = replaceLinkTags(line, entity); } sb.append(line).append(EOL); found = true; } else { if (l.startsWith("* @") || l.startsWith("*@")) { found = false; } if (found) { if (fixTag(LINK_TAG)) { line = replaceLinkTags(line, entity); } sb.append(line).append(EOL); } } } return trimRight(sb.toString()); } /** * Extract the original Javadoc and others comments up to {@link #START_JAVADOC} form the entity. This method * takes care of the Javadoc indentation. All javadoc lines will be trimmed on right. * <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 */ private static String extractOriginalJavadoc(final String javaClassContent, final AbstractJavaEntity entity) throws IOException { if (entity.getComment() == null) { return ""; } String[] javaClassContentLines = getLines(javaClassContent); List<String> list = new LinkedList<String>(); 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 */ private static String extractOriginalJavadocContent(final String javaClassContent, final AbstractJavaEntity entity) throws IOException { if (entity.getComment() == null) { return ""; } String originalJavadoc = extractOriginalJavadoc(javaClassContent, entity); int index; if ((index = originalJavadoc.indexOf(START_JAVADOC)) != -1) { originalJavadoc = originalJavadoc.substring(index + START_JAVADOC.length()); } if ((index = originalJavadoc.indexOf(END_JAVADOC)) != -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, AbstractInheritableJavaEntity, 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<String>(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, AbstractInheritableJavaEntity, 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 (StringUtils.isEmpty(line)) { return ""; } return line.substring(0, line.indexOf(trimLeft(line))); } /** * @param content not null * @return an array of all content lines * @throws IOException if any */ private static String[] getLines(final String content) throws IOException { List<String> lines = new LinkedList<String>(); 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 (StringUtils.isEmpty(text) || StringUtils.isEmpty(text.trim())) { return ""; } String textTrimmed = text.trim(); return text.substring(text.indexOf(textTrimmed), text.length()); } /** * Trim a given line on the right: * <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 (StringUtils.isEmpty(text) || StringUtils.isEmpty(text.trim())) { return ""; } String textTrimmed = text.trim(); return text.substring(0, text.indexOf(textTrimmed) + textTrimmed.length()); } /** * Workaroung for QDOX-173 about generic. * * @param params not null * @return the wanted params. */ private static String[] fixQdox173(String[] params) { if (params == null || params.length == 0 || params.length < 3) { return params; } if (params[0].trim().equals("<") && params[2].trim().equals(">")) { String param = params[1]; List<String> l = new ArrayList<String>(Arrays.asList(params)); l.set(1, "<" + param + ">"); l.remove(0); l.remove(1); return l.toArray(new String[l.size()]); } return params; } /** * Wrapper class for the entity's tags. */ private class JavaEntityTags { private final AbstractInheritableJavaEntity 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; /** * 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; public JavaEntityTags(AbstractInheritableJavaEntity entity, boolean isJavaMethod) { this.entity = entity; this.isJavaMethod = isJavaMethod; this.namesTags = new LinkedList<String>(); this.tagParams = new LinkedHashMap<String, String>(); this.tagThrows = new LinkedHashMap<String, String>(); this.unknownsTags = new LinkedList<String>(); } 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 originalJavadocTag) { tagParams.put(paramName, originalJavadocTag); } public String getJavadocParamTag(String paramName) { return getJavadocParamTag(paramName, false); } public String getJavadocParamTag(String paramName, boolean nullable) { String originalJavadocTag = tagParams.get(paramName); if (!nullable && originalJavadocTag == null && getLog().isWarnEnabled()) { getLog().warn(getMessage(paramName, "javaEntityTags.tagParams")); } return originalJavadocTag; } public void putJavadocThrowsTag(String paramName, String originalJavadocTag) { tagThrows.put(paramName, originalJavadocTag); } public String getJavadocThrowsTag(String paramName) { return getJavadocThrowsTag(paramName, false); } public String getJavadocThrowsTag(String paramName, boolean nullable) { String originalJavadocTag = tagThrows.get(paramName); if (!nullable && originalJavadocTag == null && getLog().isWarnEnabled()) { getLog().warn(getMessage(paramName, "javaEntityTags.tagThrows")); } return originalJavadocTag; } private String getMessage(String paramName, String mapName) { StringBuilder msg = new StringBuilder(); msg.append("No param '").append(paramName).append("' key found in ").append(mapName) .append(" for the entity: "); if (isJavaMethod) { JavaMethod javaMethod = (JavaMethod) entity; msg.append(getJavaMethodAsString(javaMethod)); } else { JavaClass javaClass = (JavaClass) entity; msg.append(javaClass.getFullyQualifiedName()); } return msg.toString(); } /** * {@inheritDoc} */ 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(); } } }