/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.javadoc.formatter; import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream; import com.liferay.portal.kernel.io.unsync.UnsyncStringReader; import com.liferay.portal.kernel.util.CharPool; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Tuple; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.tools.ArgumentsUtil; import com.liferay.portal.tools.JavaImportsFormatter; import com.liferay.portal.tools.ToolsUtil; import com.liferay.portal.xml.SAXReaderFactory; import com.liferay.util.xml.Dom4jDocUtil; import com.liferay.util.xml.Dom4jUtil; import com.liferay.util.xml.XMLSafeReader; import com.thoughtworks.qdox.JavaDocBuilder; import com.thoughtworks.qdox.model.AbstractBaseJavaEntity; 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.parser.ParseException; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.tools.ant.DirectoryScanner; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; /** * @author Brian Wing Shun Chan * @author Connor McKay * @author James Hinkey * @author Hugo Huijser */ public class JavadocFormatter { public static void main(String[] args) throws Exception { Map<String, String> arguments = ArgumentsUtil.parseArguments(args); try { new JavadocFormatter(arguments); } catch (Exception e) { ArgumentsUtil.processMainException(arguments, e); } } public JavadocFormatter(Map<String, String> arguments) throws Exception { String author = ArgumentsUtil.getString( arguments, "javadoc.author", JavadocFormatterArgs.AUTHOR); _author = author; _generateXml = GetterUtil.getBoolean( arguments.get("javadoc.generate.xml")); String init = arguments.get("javadoc.init"); _initializeMissingJavadocs = GetterUtil.getBoolean(init); String inputDirName = ArgumentsUtil.getString( arguments, "javadoc.input.dir", "./"); if (!inputDirName.endsWith("/")) { inputDirName += "/"; } _inputDirName = inputDirName; System.out.println("Input directory is " + _inputDirName); String[] limits = StringUtil.split(arguments.get("javadoc.limit"), ","); if (limits.length == 0) { limits = new String[] {StringPool.BLANK}; } File languagePropertiesFile = new File( "src/content/Language.properties"); if (!languagePropertiesFile.exists()) { languagePropertiesFile = new File( "src/main/resources/content/Language.properties"); } if (languagePropertiesFile.exists()) { _languageProperties = new Properties(); _languagePropertiesFile = languagePropertiesFile; _languageProperties.load( new FileInputStream(_languagePropertiesFile.getAbsolutePath())); } else { _languageProperties = null; _languagePropertiesFile = null; } String outputFilePrefix = ArgumentsUtil.getString( arguments, "javadoc.output.file.prefix", JavadocFormatterArgs.OUTPUT_FILE_PREFIX); _outputFilePrefix = outputFilePrefix; _updateJavadocs = GetterUtil.getBoolean( arguments.get("javadoc.update")); DirectoryScanner directoryScanner = new DirectoryScanner(); directoryScanner.setBasedir(_inputDirName); directoryScanner.setExcludes( new String[] { "**\\build\\**", "**\\classes\\**", "**\\portal-client\\**" }); for (String limit : limits) { List<String> includes = new ArrayList<>(); if (Validator.isNotNull(limit) && !limit.startsWith("$")) { System.out.println("Limit on " + limit); String[] limitArray = StringUtil.split(limit, '/'); for (String curLimit : limitArray) { includes.add( "**\\" + StringUtil.replace(curLimit, '.', '\\') + "\\**\\*.java"); includes.add("**\\" + curLimit + ".java"); } } else { includes.add("**\\*.java"); } directoryScanner.setIncludes( includes.toArray(new String[includes.size()])); directoryScanner.scan(); String[] fileNames = StringPool.EMPTY_ARRAY; fileNames = directoryScanner.getIncludedFiles(); if ((fileNames.length == 0) && Validator.isNotNull(limit) && !limit.startsWith("$")) { StringBundler sb = new StringBundler("Limit file not found: "); sb.append(limit); if (limit.contains(".")) { sb.append( " Specify limit filename without package path or "); sb.append("file type suffix."); } System.out.println(sb.toString()); } _populateJavadocBuilder(fileNames); for (String fileName : fileNames) { fileName = StringUtil.replace(fileName, '\\', '/'); try { _format(fileName); } catch (Exception e) { if (e instanceof ParseException) { if (!fileName.contains("/tools/templates/")) { System.out.println( "Qdox parsing error while formatting file " + fileName); } } else { throw new RuntimeException( "Unable to format file " + fileName, e); } } } } if (_generateXml) { for (Map.Entry<String, Tuple> entry : _javadocxXmlTuples.entrySet()) { Tuple tuple = entry.getValue(); File javadocsXmlFile = (File)tuple.getObject(1); String oldJavadocsXmlContent = (String)tuple.getObject(2); Document javadocsXmlDocument = (Document)tuple.getObject(3); Element javadocsXmlRootElement = javadocsXmlDocument.getRootElement(); _sortElementsByChildElement( javadocsXmlRootElement, "javadoc", "type"); String newJavadocsXmlContent = _formattedString( javadocsXmlDocument); if (!oldJavadocsXmlContent.equals(newJavadocsXmlContent)) { _write(javadocsXmlFile, newJavadocsXmlContent); _modifiedFileNames.add(javadocsXmlFile.getAbsolutePath()); } _detachUnnecessaryTypes(javadocsXmlRootElement); File javadocsRuntimeXmlFile = new File( StringUtil.replaceLast( javadocsXmlFile.toString(), "-all.xml", "-rt.xml")); String oldJavadocsRuntimeXmlContent = StringPool.BLANK; if (javadocsRuntimeXmlFile.exists()) { oldJavadocsRuntimeXmlContent = _read( javadocsRuntimeXmlFile); } String newJavadocsRuntimeXmlContent = _compactString( javadocsXmlDocument); if (!oldJavadocsRuntimeXmlContent.equals( newJavadocsRuntimeXmlContent)) { _write( javadocsRuntimeXmlFile, newJavadocsRuntimeXmlContent); _modifiedFileNames.add( javadocsRuntimeXmlFile.getAbsolutePath()); } } } } public Set<String> getModifiedFileNames() { return _modifiedFileNames; } private void _addClassCommentElement( Element rootElement, JavaClass javaClass) { String comment = _getCDATA(javaClass); if (comment.startsWith("Copyright (c)")) { comment = StringPool.BLANK; } if (Validator.isNull(comment)) { return; } Element commentElement = rootElement.addElement("comment"); commentElement.addCDATA(comment); } private String _addDeprecatedTag( String comment, AbstractBaseJavaEntity abstractBaseJavaEntity, String indent) throws Exception { if (comment == null) { return null; } comment = ToolsUtil.stripFullyQualifiedClassNames( comment, _imports, _packagePath); if (!comment.contains("* @deprecated ") || _hasAnnotation(abstractBaseJavaEntity, "Deprecated")) { return comment; } return comment + indent + "@Deprecated\n"; } private void _addDocletElements( Element parentElement, AbstractJavaEntity abstractJavaEntity, String name) throws Exception { DocletTag[] docletTags = abstractJavaEntity.getTagsByName(name); for (DocletTag docletTag : docletTags) { String value = docletTag.getValue(); value = ToolsUtil.stripFullyQualifiedClassNames( value, _imports, _packagePath); value = _trimMultilineText(value); value = StringUtil.replace(value, " </", "</"); Element element = parentElement.addElement(name); element.addCDATA(value); } if ((docletTags.length == 0) && name.equals("author")) { Element element = parentElement.addElement(name); element.addCDATA(_author); } } private String _addDocletTags( Element parentElement, String[] tagNames, String indent, boolean publicAccess) { List<String> allTagNames = new ArrayList<>(); List<String> customTagNames = new ArrayList<>(); List<String> requiredTagNames = new ArrayList<>(); for (String tagName : tagNames) { List<Element> elements = parentElement.elements(tagName); for (Element element : elements) { Element commentElement = element.element("comment"); String comment = null; // Get comment by comment element's text or the element's text if (commentElement != null) { comment = commentElement.getText(); } else { comment = element.getText(); } if (tagName.equals("param") || tagName.equals("return") || tagName.equals("throws")) { if (Validator.isNotNull(comment)) { requiredTagNames.add(tagName); } else if (tagName.equals("param")) { if (GetterUtil.getBoolean( element.elementText("required"))) { requiredTagNames.add(tagName); } } else if (tagName.equals("throws")) { if (GetterUtil.getBoolean( element.elementText("required"))) { requiredTagNames.add(tagName); } } } else { customTagNames.add(tagName); } allTagNames.add(tagName); } } int maxTagNameLength = 0; List<String> maxTagNameLengthTags = new ArrayList<>(); if (_initializeMissingJavadocs) { maxTagNameLengthTags.addAll(allTagNames); } else if (_updateJavadocs) { if (!requiredTagNames.isEmpty()) { maxTagNameLengthTags.addAll(allTagNames); } else { maxTagNameLengthTags.addAll(customTagNames); maxTagNameLengthTags.addAll(requiredTagNames); } } else { maxTagNameLengthTags.addAll(customTagNames); maxTagNameLengthTags.addAll(requiredTagNames); } for (String name : maxTagNameLengthTags) { if (name.length() > maxTagNameLength) { maxTagNameLength = name.length(); } } // There should be an @ sign before the tag and a space after it maxTagNameLength += 2; String tagNameIndent = _getSpacesIndent(maxTagNameLength); StringBundler sb = new StringBundler(); for (String tagName : tagNames) { List<Element> elements = parentElement.elements(tagName); for (Element element : elements) { Element commentElement = element.element("comment"); String comment = null; if (commentElement != null) { comment = commentElement.getText(); } else { comment = element.getText(); } String elementName = element.elementText("name"); if (Validator.isNotNull(comment)) { comment = _assembleTagComment( tagName, elementName, comment, indent, tagNameIndent); sb.append(comment); } else { if (_initializeMissingJavadocs && publicAccess) { // Write out all tags comment = _assembleTagComment( tagName, elementName, comment, indent, tagNameIndent); sb.append(comment); } else if (_updateJavadocs && publicAccess) { if (!tagName.equals("param") && !tagName.equals("return") && !tagName.equals("throws")) { // Write out custom tag comment = _assembleTagComment( tagName, elementName, comment, indent, tagNameIndent); sb.append(comment); } else if (!requiredTagNames.isEmpty()) { // Write out all tags comment = _assembleTagComment( tagName, elementName, comment, indent, tagNameIndent); sb.append(comment); } else { // Skip empty common tag } } else { if (!tagName.equals("param") && !tagName.equals("return") && !tagName.equals("throws")) { // Write out custom tag comment = _assembleTagComment( tagName, elementName, comment, indent, tagNameIndent); sb.append(comment); } else if (tagName.equals("param") || tagName.equals("return") || tagName.equals("throws")) { if (GetterUtil.getBoolean( element.elementText("required"))) { elementName = element.elementText("name"); comment = _assembleTagComment( tagName, elementName, comment, indent, tagNameIndent); sb.append(comment); } } else { // Skip empty common tag } } } } } return sb.toString(); } private void _addFieldElement(Element rootElement, JavaField javaField) throws Exception { Element fieldElement = rootElement.addElement("field"); Dom4jDocUtil.add(fieldElement, "name", javaField.getName()); String comment = _getCDATA(javaField); if (Validator.isNotNull(comment)) { Element commentElement = fieldElement.addElement("comment"); commentElement.addCDATA(comment); } _addDocletElements(fieldElement, javaField, "version"); _addDocletElements(fieldElement, javaField, "see"); _addDocletElements(fieldElement, javaField, "since"); _addDocletElements(fieldElement, javaField, "deprecated"); } private void _addMethodElement(Element rootElement, JavaMethod javaMethod) throws Exception { Element methodElement = rootElement.addElement("method"); Dom4jDocUtil.add(methodElement, "name", javaMethod.getName()); String comment = _getCDATA(javaMethod); if (Validator.isNotNull(comment)) { Element commentElement = methodElement.addElement("comment"); commentElement.addCDATA(_getCDATA(javaMethod)); } _addDocletElements(methodElement, javaMethod, "version"); _addParamElements(methodElement, javaMethod); _addReturnElement(methodElement, javaMethod); _addThrowsElements(methodElement, javaMethod); _addDocletElements(methodElement, javaMethod, "see"); _addDocletElements(methodElement, javaMethod, "since"); _addDocletElements(methodElement, javaMethod, "deprecated"); } private void _addParamElement( Element methodElement, JavaParameter javaParameter, DocletTag[] paramDocletTags) throws Exception { String name = javaParameter.getName(); String value = null; for (DocletTag paramDocletTag : paramDocletTags) { String curValue = paramDocletTag.getValue(); if (curValue.equals(name) || curValue.startsWith(name + " ")) { value = curValue; break; } } Element paramElement = methodElement.addElement("param"); Dom4jDocUtil.add(paramElement, "name", name); Dom4jDocUtil.add(paramElement, "type", _getTypeValue(javaParameter)); if (value != null) { value = value.substring(name.length()); Dom4jDocUtil.add(paramElement, "required", true); } value = ToolsUtil.stripFullyQualifiedClassNames( value, _imports, _packagePath); value = _trimMultilineText(value); Element commentElement = paramElement.addElement("comment"); commentElement.addCDATA(value); } private void _addParamElements(Element methodElement, JavaMethod javaMethod) throws Exception { JavaParameter[] javaParameters = javaMethod.getParameters(); DocletTag[] paramDocletTags = javaMethod.getTagsByName("param"); for (JavaParameter javaParameter : javaParameters) { _addParamElement(methodElement, javaParameter, paramDocletTags); } } private void _addReturnElement(Element methodElement, JavaMethod javaMethod) throws Exception { Type returnType = javaMethod.getReturnType(); if (returnType == null) { return; } String returnTypeValue = returnType.getValue(); if (returnTypeValue.equals("void")) { return; } Element returnElement = methodElement.addElement("return"); DocletTag[] returnDocletTags = javaMethod.getTagsByName("return"); String comment = StringPool.BLANK; if (returnDocletTags.length > 0) { DocletTag returnDocletTag = returnDocletTags[0]; comment = GetterUtil.getString(returnDocletTag.getValue()); Dom4jDocUtil.add(returnElement, "required", true); } comment = ToolsUtil.stripFullyQualifiedClassNames( comment, _imports, _packagePath); comment = _trimMultilineText(comment); Element commentElement = returnElement.addElement("comment"); commentElement.addCDATA(comment); } private void _addThrowsElement( Element methodElement, Type exceptionType, DocletTag[] throwsDocletTags) throws Exception { JavaClass javaClass = exceptionType.getJavaClass(); String name = javaClass.getName(); String value = null; for (DocletTag throwsDocletTag : throwsDocletTags) { String curValue = throwsDocletTag.getValue(); if (!curValue.startsWith(name)) { continue; } else { value = curValue; break; } } Element throwsElement = methodElement.addElement("throws"); Dom4jDocUtil.add(throwsElement, "name", name); Dom4jDocUtil.add(throwsElement, "type", exceptionType.getValue()); if (value != null) { value = value.substring(name.length()); Dom4jDocUtil.add(throwsElement, "required", true); } value = ToolsUtil.stripFullyQualifiedClassNames( value, _imports, _packagePath); value = _trimMultilineText(value); Element commentElement = throwsElement.addElement("comment"); commentElement.addCDATA(_getCDATA(value)); } private void _addThrowsElements( Element methodElement, JavaMethod javaMethod) throws Exception { Type[] exceptionTypes = javaMethod.getExceptions(); DocletTag[] throwsDocletTags = javaMethod.getTagsByName("throws"); for (Type exceptionType : exceptionTypes) { _addThrowsElement(methodElement, exceptionType, throwsDocletTags); } } private String _assembleTagComment( String tagName, String elementName, String comment, String indent, String tagNameIndent) { String indentAndTagName = indent + StringPool.AT + tagName; if (Validator.isNotNull(elementName)) { if (Validator.isNotNull(comment)) { comment = elementName + StringPool.SPACE + comment; } else { comment = elementName; } // <name indent> elementName [comment] comment = _wrapText(comment, indent + tagNameIndent); // * @name <name indent> elementName [comment] comment = indentAndTagName + comment.substring(indentAndTagName.length()); } else { if (Validator.isNotNull(comment)) { // <name indent> comment comment = _wrapText(comment, indent + tagNameIndent); // * @name <name indent> comment comment = indentAndTagName + comment.substring(indentAndTagName.length()); } else { // * @name comment = indentAndTagName + "\n"; } } return comment; } private String _compactString(Node node) throws IOException { UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream(); OutputFormat outputFormat = OutputFormat.createCompactFormat(); XMLWriter xmlWriter = new XMLWriter( unsyncByteArrayOutputStream, outputFormat); xmlWriter.write(node); return unsyncByteArrayOutputStream.toString(StringPool.UTF8); } private void _detachUnnecessaryTypes(Element rootElement) { List<Element> elements = rootElement.elements(); for (Element element : elements) { String type = element.elementText("type"); if (!type.contains(".service.") || !type.endsWith("ServiceImpl")) { element.detach(); } } } private void _format(String fileName) throws Exception { File file = new File(_inputDirName, fileName); String originalContent = _read(file); String absolutePath = _getAbsolutePath(fileName); if (absolutePath.contains("modules/third-party") || fileName.endsWith("Application.java") || fileName.endsWith("JavadocFormatter.java") || fileName.endsWith("Mojo.java") || fileName.endsWith("SourceFormatter.java") || fileName.endsWith("WebProxyPortlet.java") || _hasGeneratedTag(originalContent)) { return; } _imports = JavaImportsFormatter.getImports(originalContent); _packagePath = ToolsUtil.getPackagePath(fileName); JavaClass javaClass = _getJavaClass( fileName, new UnsyncStringReader(originalContent)); String javadocLessContent = _removeJavadocFromJava( javaClass, originalContent); Document document = _getJavadocDocument(javaClass); if (_generateXml) { _updateJavadocsXmlFile(fileName, javaClass, document); } String newContent = _getUpdateJavaFromDocument( fileName, javadocLessContent, document); if (!originalContent.equals(newContent)) { _write(file, newContent); _modifiedFileNames.add(file.getAbsolutePath()); System.out.println("Writing " + file); } } private String _formatCDATA(String cdata) { cdata = cdata.replaceAll( "(?s)\\s*<(p|[ou]l)>\\s*(.*?)\\s*</\\1>\\s*", "\n\n<$1>\n$2\n</$1>\n\n"); cdata = cdata.replaceAll( "(?s)\\s*<li>\\s*(.*?)\\s*</li>\\s*", "\n<li>\n$1\n</li>\n"); cdata = StringUtil.replace(cdata, "</li>\n\n<li>", "</li>\n<li>"); cdata = cdata.replaceAll("\n\\s+\n", "\n\n"); cdata = cdata.replaceAll(" +", " "); // Trim whitespace inside paragraph tags or in the first paragraph Matcher matcher = _paragraphTagPattern.matcher(cdata); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String trimmed = _trimMultilineText(matcher.group()); // Escape dollar signs trimmed = StringUtil.replace(trimmed, '$', "\\$"); matcher.appendReplacement(sb, trimmed); } matcher.appendTail(sb); cdata = sb.toString(); return cdata.trim(); } private String _formatInlines(String text) { // Capitalize ID text = text.replaceAll("[?@param id](?i)\\bid(s)?\\b", " ID$1"); // Wrap special constants in code tags text = text.replaceAll( "(?i)(?<!<code>|\\w)(null|false|true)(?!\\w)", "<code>$1</code>"); return text; } private String _formattedString(Node node) throws IOException { return Dom4jUtil.toString(node); } private String _getAbsolutePath(String fileName) { Path filePath = Paths.get(fileName); filePath = filePath.toAbsolutePath(); filePath = filePath.normalize(); return StringUtil.replace( filePath.toString(), CharPool.BACK_SLASH, CharPool.SLASH); } private String _getCDATA(AbstractJavaEntity abstractJavaEntity) { return _getCDATA(abstractJavaEntity.getComment()); } private String _getCDATA(String cdata) { StringBundler sb = new StringBundler(); if ((cdata == null) || cdata.isEmpty()) { return StringPool.BLANK; } int cdataBeginIndex = 0; while (!cdata.isEmpty()) { int preTagIndex = cdata.indexOf("<pre>"); int tableTagIndex = cdata.indexOf("<table>"); boolean hasPreTag = false; if (preTagIndex != -1) { hasPreTag = true; } boolean hasTableTag = false; if (tableTagIndex != -1) { hasTableTag = true; } if (!hasPreTag && !hasTableTag) { sb.append(_formatCDATA(cdata)); break; } boolean startsWithPreTag = false; if (preTagIndex == 0) { startsWithPreTag = true; } boolean startsWithTableTag = false; if (tableTagIndex == 0) { startsWithTableTag = true; } if (startsWithPreTag || startsWithTableTag) { sb.append("\n"); String tagName = null; if (preTagIndex == 0) { tagName = "pre"; } else { tagName = "table"; } String startTag = "<" + tagName + ">"; String endTag = "</" + tagName + ">"; int startTagLength = startTag.length(); int endTagLength = endTag.length(); int endTagIndex = cdata.indexOf(endTag, startTagLength - 1); sb.append(cdata.substring(0, endTagIndex + endTagLength)); sb.append("\n"); cdataBeginIndex = endTagIndex + endTagLength; } else { // Format the cdata up to the next pre or table tag int startTagIndex = 0; if (hasPreTag && hasTableTag) { if (preTagIndex < tableTagIndex) { startTagIndex = preTagIndex; } else { startTagIndex = tableTagIndex; } } else if (hasPreTag && !hasTableTag) { startTagIndex = preTagIndex; } else { // Must have table tag and no pre tag startTagIndex = tableTagIndex; } sb.append(_formatCDATA(cdata.substring(0, startTagIndex))); cdataBeginIndex = startTagIndex; } cdata = cdata.substring(cdataBeginIndex); } cdata = sb.toString(); return cdata.trim(); } private String _getClassName(String fileName) { int pos = fileName.indexOf("src/main/java/"); if (pos == -1) { pos = fileName.indexOf("src/test/java/"); } if (pos == -1) { pos = fileName.indexOf("src/testIntegration/java/"); } if (pos != -1) { pos = fileName.indexOf("java/", pos); } if (pos == -1) { pos = fileName.indexOf("src/"); } if (pos == -1) { pos = fileName.indexOf("test/integration/"); if (pos != -1) { pos = fileName.indexOf("integration/", pos); } } if (pos == -1) { pos = fileName.indexOf("test/unit/"); if (pos != -1) { pos = fileName.indexOf("unit/", pos); } } if (pos == -1) { pos = fileName.indexOf("test/"); } if (pos == -1) { pos = fileName.indexOf("service/"); } if (pos == -1) { throw new RuntimeException(fileName); } pos = fileName.indexOf("/", pos); String srcFile = fileName.substring(pos + 1, fileName.length()); return StringUtil.replace( srcFile.substring(0, srcFile.length() - 5), '/', '.'); } private String _getFieldKey(Element fieldElement) { return fieldElement.elementText("name"); } private String _getFieldKey(JavaField javaField) { return javaField.getName(); } private String _getIndent( String[] lines, AbstractBaseJavaEntity abstractBaseJavaEntity) { String line = lines[abstractBaseJavaEntity.getLineNumber() - 1]; String indent = StringPool.BLANK; for (char c : line.toCharArray()) { if (Character.isWhitespace(c)) { indent += c; } else { break; } } return indent; } private int _getIndentLength(String indent) { int indentLength = 0; for (char c : indent.toCharArray()) { if (c == '\t') { indentLength = indentLength + 4; } else { indentLength++; } } return indentLength; } private JavaClass _getJavaClass(String fileName, Reader reader) throws Exception { String className = _getClassName(fileName); if (reader != null) { _javadocBuilder.addSource(reader); } return _javadocBuilder.getClassByName(className); } private String _getJavaClassComment( Element rootElement, JavaClass javaClass) throws Exception { StringBundler sb = new StringBundler(); String indent = StringPool.BLANK; sb.append("/**\n"); String comment = rootElement.elementText("comment"); if (Validator.isNotNull(comment)) { comment = ToolsUtil.stripFullyQualifiedClassNames( comment, _imports, _packagePath); sb.append(_wrapText(comment, indent + " * ")); } String docletTags = _addDocletTags( rootElement, new String[] { "author", "version", "see", "since", "serial", "deprecated" }, indent + " * ", _hasPublicModifier(javaClass)); if (Validator.isNotNull(docletTags)) { if (_initializeMissingJavadocs || Validator.isNotNull(comment)) { sb.append(" *\n"); } sb.append(docletTags); } sb.append(" */\n"); return sb.toString(); } private int _getJavaClassLineNumber(JavaClass javaClass) { int lineNumber = javaClass.getLineNumber(); Annotation[] annotations = javaClass.getAnnotations(); if (annotations.length == 0) { return lineNumber; } for (Annotation annotation : annotations) { int annotationLineNumber = annotation.getLineNumber(); Map<String, String> propertyMap = annotation.getPropertyMap(); if (propertyMap.isEmpty()) { annotationLineNumber--; } if (annotationLineNumber < lineNumber) { lineNumber = annotationLineNumber; } } return lineNumber; } private Document _getJavadocDocument(JavaClass javaClass) throws Exception { Element rootElement = DocumentHelper.createElement("javadoc"); Document document = DocumentHelper.createDocument(rootElement); Dom4jDocUtil.add(rootElement, "name", javaClass.getName()); Dom4jDocUtil.add( rootElement, "type", javaClass.getFullyQualifiedName()); _addClassCommentElement(rootElement, javaClass); _addDocletElements(rootElement, javaClass, "author"); _addDocletElements(rootElement, javaClass, "version"); _addDocletElements(rootElement, javaClass, "see"); _addDocletElements(rootElement, javaClass, "since"); _addDocletElements(rootElement, javaClass, "serial"); _addDocletElements(rootElement, javaClass, "deprecated"); JavaMethod[] javaMethods = javaClass.getMethods(); for (JavaMethod javaMethod : javaMethods) { _addMethodElement(rootElement, javaMethod); } JavaField[] javaFields = javaClass.getFields(); for (JavaField javaField : javaFields) { _addFieldElement(rootElement, javaField); } return document; } private Tuple _getJavadocsXmlTuple(String fileName) throws Exception { File file = new File(_inputDirName + fileName); String absolutePath = file.getAbsolutePath(); absolutePath = StringUtil.replace(absolutePath, '\\', '/'); absolutePath = StringUtil.replace(absolutePath, "/./", "/"); int pos = absolutePath.indexOf("/portal-impl/src/"); String srcDirName = null; if (pos != -1) { srcDirName = absolutePath.substring(0, pos + 17); } if (srcDirName == null) { pos = absolutePath.indexOf("/portal-kernel/src/"); if (pos == -1) { pos = absolutePath.indexOf("/portal-kernel/src/"); } if (pos == -1) { pos = absolutePath.indexOf("/util-bridges/src/"); } if (pos == -1) { pos = absolutePath.indexOf("/util-java/src/"); } if (pos == -1) { pos = absolutePath.indexOf("/util-taglib/src/"); } if (pos != -1) { srcDirName = absolutePath.substring(0, pos) + "/portal-impl/src/"; } } if (srcDirName == null) { pos = absolutePath.indexOf("/WEB-INF/src/"); if (pos != -1) { srcDirName = absolutePath.substring(0, pos + 13); } } if (srcDirName == null) { pos = absolutePath.indexOf("/src/main/java/"); if (pos != -1) { srcDirName = absolutePath.substring(0, pos) + "/src/main/resources"; } } if (srcDirName == null) { return null; } Tuple tuple = _javadocxXmlTuples.get(srcDirName); if (tuple != null) { return tuple; } File metaInfDir = new File(srcDirName, "META-INF"); if (!metaInfDir.exists()) { metaInfDir.mkdirs(); } File javadocsXmlFile = new File( metaInfDir, _outputFilePrefix + "-all.xml"); String javadocsXmlContent = null; if (!javadocsXmlFile.exists()) { javadocsXmlContent = "<?xml version=\"1.0\"?>\n\n<javadocs>\n</javadocs>"; _write(javadocsXmlFile, javadocsXmlContent); _modifiedFileNames.add(javadocsXmlFile.getAbsolutePath()); } javadocsXmlContent = _read(javadocsXmlFile); SAXReader saxReader = _getSAXReader(); Document javadocsXmlDocument = saxReader.read( new XMLSafeReader(javadocsXmlContent)); tuple = new Tuple( srcDirName, javadocsXmlFile, javadocsXmlContent, javadocsXmlDocument); _javadocxXmlTuples.put(srcDirName, tuple); return tuple; } private String _getJavaFieldComment( Map<String, Element> fieldElementsMap, JavaField javaField, String indent) throws Exception { String fieldKey = _getFieldKey(javaField); Element fieldElement = fieldElementsMap.get(fieldKey); if (fieldElement == null) { return null; } StringBundler sb = new StringBundler(); sb.append(indent); sb.append("/**\n"); String comment = fieldElement.elementText("comment"); if (Validator.isNotNull(comment)) { comment = ToolsUtil.stripFullyQualifiedClassNames( comment, _imports, _packagePath); sb.append(_wrapText(comment, indent + " * ")); } String docletTags = _addDocletTags( fieldElement, new String[] {"version", "see", "since", "deprecated"}, indent + " * ", _hasPublicModifier(javaField)); if (Validator.isNotNull(docletTags)) { if (_initializeMissingJavadocs || Validator.isNotNull(comment)) { sb.append(indent); sb.append(" *\n"); } sb.append(docletTags); } sb.append(indent); sb.append(" */\n"); if (!_initializeMissingJavadocs && Validator.isNull(comment) && Validator.isNull(docletTags)) { return null; } if (!_hasPublicModifier(javaField) && Validator.isNull(comment) && Validator.isNull(docletTags)) { return null; } return sb.toString(); } private String _getJavaMethodComment( Map<String, Element> methodElementsMap, JavaMethod javaMethod, String indent) throws Exception { String methodKey = _getMethodKey(javaMethod); Element methodElement = methodElementsMap.get(methodKey); if (methodElement == null) { return null; } StringBundler sb = new StringBundler(); sb.append(indent); sb.append("/**\n"); String comment = methodElement.elementText("comment"); if (Validator.isNotNull(comment)) { comment = ToolsUtil.stripFullyQualifiedClassNames( comment, _imports, _packagePath); sb.append(_wrapText(comment, indent + " * ")); } String docletTags = _addDocletTags( methodElement, new String[] { "version", "param", "return", "throws", "see", "since", "deprecated" }, indent + " * ", _hasPublicModifier(javaMethod)); if (Validator.isNotNull(docletTags)) { if (_initializeMissingJavadocs || Validator.isNotNull(comment)) { sb.append(indent); sb.append(" *\n"); } sb.append(docletTags); } sb.append(indent); sb.append(" */\n"); if (!_initializeMissingJavadocs && Validator.isNull(comment) && Validator.isNull(docletTags)) { return null; } if (!_hasPublicModifier(javaMethod) && Validator.isNull(comment) && Validator.isNull(docletTags)) { return null; } return sb.toString(); } private String _getMethodKey(Element methodElement) { StringBundler sb = new StringBundler(); sb.append(methodElement.elementText("name")); sb.append(StringPool.OPEN_PARENTHESIS); List<Element> paramElements = methodElement.elements("param"); for (Element paramElement : paramElements) { sb.append(paramElement.elementText("name")); sb.append("|"); sb.append(paramElement.elementText("type")); sb.append(","); } sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private String _getMethodKey(JavaMethod javaMethod) { StringBundler sb = new StringBundler(); sb.append(javaMethod.getName()); sb.append(StringPool.OPEN_PARENTHESIS); JavaParameter[] javaParameters = javaMethod.getParameters(); for (JavaParameter javaParameter : javaParameters) { sb.append(javaParameter.getName()); sb.append("|"); sb.append(_getTypeValue(javaParameter)); sb.append(","); } sb.append(StringPool.CLOSE_PARENTHESIS); return sb.toString(); } private SAXReader _getSAXReader() { return SAXReaderFactory.getSAXReader(null, false, false); } private String _getSpacesIndent(int length) { String indent = StringPool.BLANK; for (int i = 0; i < length; i++) { indent += StringPool.SPACE; } return indent; } private String _getTypeValue(JavaParameter javaParameter) { Type type = javaParameter.getType(); String typeValue = type.getValue(); if (type.isArray()) { typeValue += "[]"; } return typeValue; } private String _getUpdateJavaFromDocument( String fileName, String javadocLessContent, Document document) throws Exception { String[] lines = StringUtil.splitLines(javadocLessContent); JavaClass javaClass = _getJavaClass( fileName, new UnsyncStringReader(javadocLessContent)); _updateLanguageProperties(document, javaClass.getName()); Element rootElement = document.getRootElement(); Map<Integer, String> commentsMap = new TreeMap<>(); String javaClassComment = _getJavaClassComment(rootElement, javaClass); javaClassComment = _addDeprecatedTag( javaClassComment, javaClass, StringPool.BLANK); commentsMap.put(_getJavaClassLineNumber(javaClass), javaClassComment); Map<String, Element> methodElementsMap = new HashMap<>(); List<Element> methodElements = rootElement.elements("method"); for (Element methodElement : methodElements) { String methodKey = _getMethodKey(methodElement); methodElementsMap.put(methodKey, methodElement); } JavaMethod[] javaMethods = javaClass.getMethods(); for (JavaMethod javaMethod : javaMethods) { if (commentsMap.containsKey(javaMethod.getLineNumber())) { continue; } String indent = _getIndent(lines, javaMethod); String javaMethodComment = _getJavaMethodComment( methodElementsMap, javaMethod, indent); javaMethodComment = _addDeprecatedTag( javaMethodComment, javaMethod, indent); commentsMap.put(javaMethod.getLineNumber(), javaMethodComment); } Map<String, Element> fieldElementsMap = new HashMap<>(); List<Element> fieldElements = rootElement.elements("field"); for (Element fieldElement : fieldElements) { String fieldKey = _getFieldKey(fieldElement); fieldElementsMap.put(fieldKey, fieldElement); } JavaField[] javaFields = javaClass.getFields(); for (JavaField javaField : javaFields) { if (commentsMap.containsKey(javaField.getLineNumber())) { continue; } String indent = _getIndent(lines, javaField); String javaFieldComment = _getJavaFieldComment( fieldElementsMap, javaField, indent); javaFieldComment = _addDeprecatedTag( javaFieldComment, javaField, indent); commentsMap.put(javaField.getLineNumber(), javaFieldComment); } StringBundler sb = new StringBundler(javadocLessContent.length()); for (int lineNumber = 1; lineNumber <= lines.length; lineNumber++) { String line = lines[lineNumber - 1]; String comments = commentsMap.get(lineNumber); if (comments != null) { sb.append(comments); } sb.append(line); sb.append("\n"); } String formattedContent = sb.toString(); return formattedContent.trim(); } private boolean _hasAnnotation( AbstractBaseJavaEntity abstractBaseJavaEntity, String annotationName) { Annotation[] annotations = abstractBaseJavaEntity.getAnnotations(); if (annotations == null) { return false; } for (int i = 0; i < annotations.length; i++) { Type type = annotations[i].getType(); JavaClass javaClass = type.getJavaClass(); if (annotationName.equals(javaClass.getName())) { return true; } } return false; } private boolean _hasGeneratedTag(String content) { if ((content.contains("* @generated") || content.contains("$ANTLR") || content.contains("auto-generated from WSDL")) && !content.contains("hasGeneratedTag")) { return true; } else { return false; } } private boolean _hasPublicModifier(AbstractJavaEntity abstractJavaEntity) { String[] modifiers = abstractJavaEntity.getModifiers(); if (modifiers == null) { return false; } for (String modifier : modifiers) { if (modifier.equals("public")) { return true; } } return false; } private void _populateJavadocBuilder(String[] fileNames) { _javadocBuilder = new JavaDocBuilder(); for (String fileName : fileNames) { fileName = StringUtil.replace( fileName, CharPool.BACK_SLASH, CharPool.SLASH); File file = new File(_inputDirName, fileName); try { _javadocBuilder.addSource(file); } catch (Exception e) { } } } private String _read(File file) throws IOException { String s = new String( Files.readAllBytes(file.toPath()), StringPool.UTF8); return StringUtil.replace( s, StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE); } private String _removeJavadocFromJava(JavaClass javaClass, String content) { Set<Integer> lineNumbers = new HashSet<>(); lineNumbers.add(_getJavaClassLineNumber(javaClass)); JavaMethod[] javaMethods = javaClass.getMethods(); for (JavaMethod javaMethod : javaMethods) { lineNumbers.add(javaMethod.getLineNumber()); } JavaField[] javaFields = javaClass.getFields(); for (JavaField javaField : javaFields) { lineNumbers.add(javaField.getLineNumber()); } String[] lines = StringUtil.splitLines(content); for (int lineNumber : lineNumbers) { if (lineNumber == 0) { continue; } int pos = lineNumber - 2; String line = lines[pos]; if (line == null) { continue; } int blankLines = 0; while (line.equals(StringPool.BLANK)) { line = lines[--pos]; blankLines++; } line = line.trim(); if (line.endsWith("*/")) { while (true) { lines[pos] = null; if (line.startsWith("/**") || line.startsWith("/*")) { break; } line = lines[--pos].trim(); } for (int i = 0; i < blankLines; i++) { lines[lineNumber - i - 2] = null; } } } StringBundler sb = new StringBundler(content.length()); for (String line : lines) { if (line != null) { sb.append(line); sb.append("\n"); } } content = sb.toString(); return content.trim(); } private void _sortElementsByChildElement( Element element, String elementName, String childElementName) { Map<String, Element> elementsMap = new TreeMap<>(); List<Element> elements = element.elements(); for (Element curElement : elements) { curElement.detach(); if (elementName.equals(curElement.getName())) { String childElementValue = curElement.elementText( childElementName); elementsMap.put(childElementValue, curElement); } } for (Element curElement : elements) { if (elementName.equals(curElement.getName())) { break; } element.add(curElement); } for (Map.Entry<String, Element> entry : elementsMap.entrySet()) { Element curElement = entry.getValue(); element.add(curElement); } boolean foundLastElementWithElementName = false; for (int i = 0; i < elements.size(); i++) { Element curElement = elements.get(i); if (!foundLastElementWithElementName) { if (elementName.equals(curElement.getName())) { if ((i + 1) < elements.size()) { Element nextElement = elements.get(i + 1); if (!elementName.equals(nextElement.getName())) { foundLastElementWithElementName = true; } } } } else { element.add(curElement); } } } private String _trimMultilineText(String text) { String[] lines = StringUtil.splitLines(text); StringBundler sb = new StringBundler(); for (int i = 0; i < lines.length; i++) { String line = lines[i].trim(); sb.append(line); if (!line.endsWith(StringPool.OPEN_PARENTHESIS) && (i < (lines.length - 1))) { sb.append(StringPool.SPACE); } } return sb.toString(); } private void _updateJavadocsXmlFile( String fileName, JavaClass javaClass, Document javaClassDocument) throws Exception { String javaClassFullyQualifiedName = javaClass.getFullyQualifiedName(); /*if (!javaClassFullyQualifiedName.contains(".service.") || !javaClassFullyQualifiedName.endsWith("ServiceImpl")) { return; }*/ Tuple javadocsXmlTuple = _getJavadocsXmlTuple(fileName); if (javadocsXmlTuple == null) { return; } Document javadocsXmlDocument = (Document)javadocsXmlTuple.getObject(3); Element javadocsXmlRootElement = javadocsXmlDocument.getRootElement(); List<Element> javadocElements = javadocsXmlRootElement.elements( "javadoc"); for (Element javadocElement : javadocElements) { String type = javadocElement.elementText("type"); if (type.equals(javaClassFullyQualifiedName)) { Element javaClassRootElement = javaClassDocument.getRootElement(); if (Objects.equals( _formattedString(javadocElement), _formattedString(javaClassRootElement))) { return; } javadocElement.detach(); break; } } javadocsXmlRootElement.add(javaClassDocument.getRootElement()); } private void _updateLanguageProperties(Document document, String className) throws IOException { if (_languageProperties == null) { return; } int index = className.indexOf("ServiceImpl"); if (index <= 0) { return; } StringBundler sb = new StringBundler(); sb.append(Character.toLowerCase(className.charAt(0))); for (int i = 1; i < index; i++) { char c = className.charAt(i); if (Character.isUpperCase(c)) { if (((i + 1) < index) && Character.isLowerCase(className.charAt(i + 1))) { sb.append(CharPool.DASH); } sb.append(Character.toLowerCase(c)); } else { sb.append(c); } } sb.append("-service-help"); String key = sb.toString(); String value = _languageProperties.getProperty(key); if (value == null) { return; } Element rootElement = document.getRootElement(); String comment = rootElement.elementText("comment"); if ((comment == null) || value.equals(comment)) { return; } index = comment.indexOf("\n\n"); if (index != -1) { value = comment.substring(0, index); } else { value = comment; } _updateLanguageProperties(key, value); } private void _updateLanguageProperties(String key, String value) throws IOException { StringBundler sb = new StringBundler(); try (BufferedReader bufferedReader = Files.newBufferedReader( _languagePropertiesFile.toPath(), StandardCharsets.UTF_8)) { boolean begin = false; boolean firstLine = true; String linePrefix = key + "="; String line = null; while ((line = bufferedReader.readLine()) != null) { if (line.equals(StringPool.BLANK)) { begin = !begin; } if (firstLine) { firstLine = false; } else { sb.append(StringPool.NEW_LINE); } if (line.startsWith(linePrefix)) { sb.append(linePrefix); sb.append(value); } else { sb.append(line); } } } try (Writer writer = new OutputStreamWriter( new FileOutputStream(_languagePropertiesFile, false), StandardCharsets.UTF_8)) { sb.writeTo(writer); } System.out.println( "Updating " + _languagePropertiesFile + " key " + key); } private String _wrapText(String text, int indentLength, String exclude) { StringBuffer sb = new StringBuffer(); StringBundler regexSB = new StringBundler("(?<=^|</"); regexSB.append(exclude); regexSB.append(">).+?(?=$|<"); regexSB.append(exclude); regexSB.append(">)"); Pattern pattern = Pattern.compile(regexSB.toString(), Pattern.DOTALL); Matcher matcher = pattern.matcher(text); while (matcher.find()) { String wrapped = _formatInlines(matcher.group()); wrapped = StringUtil.wrap(wrapped, 80 - indentLength, "\n"); matcher.appendReplacement(sb, wrapped); } matcher.appendTail(sb); return sb.toString(); } private String _wrapText(String text, String indent) { int indentLength = _getIndentLength(indent); if (text.contains("<pre>")) { text = _wrapText(text, indentLength, "pre"); } else if (text.contains("<table>")) { text = _wrapText(text, indentLength, "table"); } else { text = _formatInlines(text); text = StringUtil.wrap(text, 80 - indentLength, "\n"); } text = text.replaceAll("(?m)^", indent); text = text.replaceAll("(?m) +$", StringPool.BLANK); return text; } private void _write(File file, String s) throws IOException { Files.write(file.toPath(), s.getBytes(StandardCharsets.UTF_8)); } private final String _author; private final boolean _generateXml; private String _imports; private final boolean _initializeMissingJavadocs; private final String _inputDirName; private JavaDocBuilder _javadocBuilder; private final Map<String, Tuple> _javadocxXmlTuples = new HashMap<>(); private final Properties _languageProperties; private final File _languagePropertiesFile; private final Set<String> _modifiedFileNames = new HashSet<>(); private final String _outputFilePrefix; private String _packagePath; private final Pattern _paragraphTagPattern = Pattern.compile( "(^.*?(?=\n\n|$)+|(?<=<p>\n).*?(?=\n</p>))", Pattern.DOTALL); private final boolean _updateJavadocs; }