/**
* 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.portal.tools;
import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.xml.Document;
import com.liferay.portal.kernel.xml.Element;
import com.liferay.portal.kernel.xml.SAXReader;
import com.liferay.portal.util.FileImpl;
import com.liferay.portal.xml.SAXReaderImpl;
import com.liferay.util.xml.DocUtil;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.AbstractJavaEntity;
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 jargs.gnu.CmdLineParser;
import java.io.File;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.tools.ant.DirectoryScanner;
/**
* @author Brian Wing Shun Chan
*/
public class JavadocBuilder {
public static void main(String[] args) {
try {
new JavadocBuilder(args);
}
catch (Exception e) {
e.printStackTrace();
}
}
public JavadocBuilder(String[] args) throws Exception {
CmdLineParser cmdLineParser = new CmdLineParser();
cmdLineParser.parse(args);
CmdLineParser.Option commandOption = cmdLineParser.addStringOption(
"command");
String command = (String)cmdLineParser.getOptionValue(commandOption);
CmdLineParser.Option limitOption = cmdLineParser.addStringOption(
"limit");
String limit = (String)cmdLineParser.getOptionValue(limitOption);
CmdLineParser.Option ignoreAutogeneratedOption =
cmdLineParser.addBooleanOption("ignoreAutogenerated");
Boolean ignoreAutogenerated = (Boolean)cmdLineParser.getOptionValue(
ignoreAutogeneratedOption);
_process(command, limit, ignoreAutogenerated);
}
private void _addClassCommentElement(
Element rootElement, JavaClass javaClass) {
Element commentElement = rootElement.addElement("comment");
String comment = _getCDATA(javaClass);
if (comment.startsWith("Copyright (c) 2000-present Liferay, Inc.")) {
comment = StringPool.BLANK;
}
if (comment.startsWith(
"<a href=\"" + javaClass.getName() + ".java.html\">")) {
int pos = comment.indexOf("</a>");
comment = comment.substring(pos + 4).trim();
}
commentElement.addCDATA(comment);
}
private void _addDocletElements(
Element parentElement, AbstractJavaEntity abstractJavaEntity,
String name) {
DocletTag[] docletTags = abstractJavaEntity.getTagsByName(name);
for (DocletTag docletTag : docletTags) {
String value = docletTag.getValue();
if (name.equals("author") || name.equals("see") ||
name.equals("since") || name.equals("version")) {
/*if (value.startsWith("Raymond Aug")) {
value = "Raymond Aug\u00c3\u00a9";
}*/
DocUtil.add(parentElement, name, value);
}
else {
Element element = parentElement.addElement(name);
element.addCDATA(value);
}
}
}
private void _addDocletTags(
Element parentElement, String name, String indent, StringBuilder sb) {
List<Element> elements = parentElement.elements(name);
for (Element element : elements) {
sb.append(indent);
sb.append(" * @");
sb.append(name);
sb.append(" ");
Element commentElement = element.element("comment");
if (commentElement != null) {
sb.append(element.elementText("name"));
sb.append(" ");
sb.append(_getCDATA(element.elementText("comment")));
}
else {
sb.append(_getCDATA(element.getText()));
}
sb.append("\n");
}
}
private void _addFieldElement(Element rootElement, JavaField javaField) {
Element fieldElement = rootElement.addElement("field");
DocUtil.add(fieldElement, "name", javaField.getName());
Element commentElement = fieldElement.addElement("comment");
commentElement.addCDATA(_getCDATA(javaField));
_addDocletElements(fieldElement, javaField, "deprecated");
_addDocletElements(fieldElement, javaField, "see");
_addDocletElements(fieldElement, javaField, "since");
_addDocletElements(fieldElement, javaField, "version");
}
private void _addMethodElement(Element rootElement, JavaMethod javaMethod) {
Element methodElement = rootElement.addElement("method");
DocUtil.add(methodElement, "name", javaMethod.getName());
Element commentElement = methodElement.addElement("comment");
commentElement.addCDATA(_getCDATA(javaMethod));
_addDocletElements(methodElement, javaMethod, "deprecated");
_addParamElements(methodElement, javaMethod);
_addReturnElement(methodElement, javaMethod);
_addDocletElements(methodElement, javaMethod, "see");
_addDocletElements(methodElement, javaMethod, "since");
_addThrowsElements(methodElement, javaMethod);
_addDocletElements(methodElement, javaMethod, "version");
}
private void _addParamElement(
Element methodElement, JavaParameter javaParameter,
DocletTag[] paramDocletTags) {
String name = javaParameter.getName();
String type = javaParameter.getType().getValue();
String value = null;
for (DocletTag paramDocletTag : paramDocletTags) {
String curValue = paramDocletTag.getValue();
if (!curValue.startsWith(name)) {
continue;
}
else {
curValue = value;
break;
}
}
Element paramElement = methodElement.addElement("param");
DocUtil.add(paramElement, "name", name);
DocUtil.add(paramElement, "type", type);
if (value != null) {
value = value.substring(name.length());
}
Element commentElement = paramElement.addElement("comment");
commentElement.addCDATA(_getCDATA(value));
}
private void _addParamElements(
Element methodElement, JavaMethod javaMethod) {
JavaParameter[] javaParameters = javaMethod.getParameters();
DocletTag[] paramDocletTags = javaMethod.getTagsByName("param");
for (JavaParameter javaParameter : javaParameters) {
_addParamElement(methodElement, javaParameter, paramDocletTags);
}
}
private void _addReturnElement(
Element methodElement, JavaMethod javaMethod) {
Type returnType = javaMethod.getReturnType();
if ((returnType == null) || returnType.getValue().equals("void")) {
return;
}
_addDocletElements(methodElement, javaMethod, "return");
}
private void _addThrowsElement(
Element methodElement, Type exception, DocletTag[] throwsDocletTags) {
String name = exception.getJavaClass().getName();
String value = null;
for (DocletTag throwsDocletTag : throwsDocletTags) {
String curValue = throwsDocletTag.getValue();
if (!curValue.startsWith(name)) {
continue;
}
else {
curValue = value;
break;
}
}
Element throwsElement = methodElement.addElement("throws");
DocUtil.add(throwsElement, "name", name);
DocUtil.add(throwsElement, "type", exception.getValue());
if (value != null) {
value = value.substring(name.length());
}
Element commentElement = throwsElement.addElement("comment");
commentElement.addCDATA(_getCDATA(value));
}
private void _addThrowsElements(
Element methodElement, JavaMethod javaMethod) {
Type[] exceptions = javaMethod.getExceptions();
DocletTag[] throwsDocletTags = javaMethod.getTagsByName("throws");
for (Type exception : exceptions) {
_addThrowsElement(methodElement, exception, throwsDocletTags);
}
}
private String _getCDATA(AbstractJavaEntity abstractJavaEntity) {
return _getCDATA(abstractJavaEntity.getComment());
}
private String _getCDATA(String cdata) {
if (cdata == null) {
return StringPool.BLANK;
}
cdata = StringUtil.replace(
cdata, new String[] {"\n", "<p>", "</p>"},
new String[] {" ", " <p> ", " </p> "});
while (cdata.contains(" ")) {
cdata = StringUtil.replace(cdata, " ", " ");
}
return cdata.trim();
}
private String _getFieldKey(Element fieldElement) {
return fieldElement.elementText("name");
}
private String _getFieldKey(JavaField javaField) {
return javaField.getName();
}
private JavaClass _getJavaClass(String fileName) throws Exception {
return _getJavaClass(fileName, null);
}
private JavaClass _getJavaClass(String fileName, Reader reader)
throws Exception {
int pos = fileName.indexOf("src/");
if (pos == -1) {
pos = fileName.indexOf("test/");
}
if (pos == -1) {
throw new RuntimeException(fileName);
}
pos = fileName.indexOf("/", pos);
String srcFile = fileName.substring(pos + 1);
String className = StringUtil.replace(
srcFile.substring(0, srcFile.length() - 5), '/', '.');
JavaDocBuilder builder = new JavaDocBuilder();
if (reader == null) {
File file = new File(fileName);
if (!file.exists()) {
return null;
}
builder.addSource(file);
}
else {
builder.addSource(reader);
}
return builder.getClassByName(className);
}
private String _getJavaClassComment(Element rootElement) {
StringBuilder sb = new StringBuilder();
sb.append("/**\n");
sb.append(" * ");
sb.append(_getCDATA(rootElement.elementText("comment")));
sb.append("\n");
sb.append(" *\n");
String indent = StringPool.BLANK;
_addDocletTags(rootElement, "author", indent, sb);
_addDocletTags(rootElement, "deprecated", indent, sb);
_addDocletTags(rootElement, "see", indent, sb);
_addDocletTags(rootElement, "serial", indent, sb);
_addDocletTags(rootElement, "since", indent, sb);
_addDocletTags(rootElement, "version", indent, sb);
sb.append(" */\n");
return sb.toString();
}
private String _getJavadocXml(JavaClass javaClass) throws Exception {
Element rootElement = _saxReader.createElement("javadoc");
Document document = _saxReader.createDocument(rootElement);
DocUtil.add(rootElement, "name", javaClass.getName());
DocUtil.add(rootElement, "type", javaClass.getFullyQualifiedName());
_addClassCommentElement(rootElement, javaClass);
_addDocletElements(rootElement, javaClass, "author");
_addDocletElements(rootElement, javaClass, "deprecated");
_addDocletElements(rootElement, javaClass, "see");
_addDocletElements(rootElement, javaClass, "serial");
_addDocletElements(rootElement, javaClass, "since");
_addDocletElements(rootElement, javaClass, "version");
JavaMethod[] javaMethods = javaClass.getMethods();
for (JavaMethod javaMethod : javaMethods) {
_addMethodElement(rootElement, javaMethod);
}
JavaField[] javaFields = javaClass.getFields();
for (JavaField javaField : javaFields) {
_addFieldElement(rootElement, javaField);
}
return document.formattedString();
}
private String _getJavaFieldComment(
String[] lines, Map<String, Element> fieldElementsMap,
JavaField javaField) {
String fieldKey = _getFieldKey(javaField);
Element fieldElement = fieldElementsMap.get(fieldKey);
if (fieldElement == null) {
return null;
}
String line = lines[javaField.getLineNumber() - 1];
String indent = StringPool.BLANK;
for (char c : line.toCharArray()) {
if (Character.isWhitespace(c)) {
indent += c;
}
else {
break;
}
}
StringBuilder sb = new StringBuilder();
sb.append(indent);
sb.append("/**\n");
sb.append(indent);
sb.append(" * ");
sb.append(fieldElement.elementText("comment"));
sb.append("\n");
sb.append(indent);
sb.append(" *\n");
_addDocletTags(fieldElement, "deprecated", indent, sb);
_addDocletTags(fieldElement, "see", indent, sb);
_addDocletTags(fieldElement, "since", indent, sb);
_addDocletTags(fieldElement, "version", indent, sb);
sb.append(indent);
sb.append(" */\n");
return sb.toString();
}
private String _getJavaMethodComment(
String[] lines, Map<String, Element> methodElementsMap,
JavaMethod javaMethod) {
String methodKey = _getMethodKey(javaMethod);
Element methodElement = methodElementsMap.get(methodKey);
if (methodElement == null) {
return null;
}
String line = lines[javaMethod.getLineNumber() - 1];
String indent = StringPool.BLANK;
for (char c : line.toCharArray()) {
if (Character.isWhitespace(c)) {
indent += c;
}
else {
break;
}
}
StringBuilder sb = new StringBuilder();
sb.append(indent);
sb.append("/**\n");
sb.append(indent);
sb.append(" * ");
sb.append(methodElement.elementText("comment"));
sb.append("\n");
sb.append(indent);
sb.append(" *\n");
_addDocletTags(methodElement, "deprecated", indent, sb);
_addDocletTags(methodElement, "param", indent, sb);
_addDocletTags(methodElement, "return", indent, sb);
_addDocletTags(methodElement, "see", indent, sb);
_addDocletTags(methodElement, "since", indent, sb);
_addDocletTags(methodElement, "throws", indent, sb);
_addDocletTags(methodElement, "version", indent, sb);
sb.append(indent);
sb.append(" */\n");
return sb.toString();
}
private String _getMethodKey(Element methodElement) {
StringBuilder sb = new StringBuilder();
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) {
StringBuilder sb = new StringBuilder();
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(javaParameter.getType().getValue());
sb.append(",");
}
sb.append(StringPool.CLOSE_PARENTHESIS);
return sb.toString();
}
private boolean _isGenerated(String content) {
if (content.contains("<javadoc autogenerated=\"true\">")) {
return true;
}
else {
return false;
}
}
private void _process(
String command, String limit, Boolean ignoreAutogenerated)
throws Exception {
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(_BASEDIR);
ds.setExcludes(
new String[] {
"**\\classes\\**", "**\\portal-client\\**", "**\\portal-web\\**"
});
List<String> includes = new ArrayList<>();
if (Validator.isNotNull(limit) && !limit.startsWith("$")) {
String[] limitArray = StringUtil.split(limit, '/');
for (String curLimit : limitArray) {
includes.add(
"**\\" + StringUtil.replace(curLimit, '.', '\\') +
"\\**\\*.java");
includes.add("**\\" + curLimit + ".java");
}
}
else {
includes.add("**\\*.java");
}
ds.setIncludes(includes.toArray(new String[includes.size()]));
ds.scan();
String[] fileNames = ds.getIncludedFiles();
for (String fileName : fileNames) {
fileName = StringUtil.replace(fileName, '\\', '/');
/*if (!fileName.endsWith("Isolation.java")) {
continue;
}*/
if ((ignoreAutogenerated != null) &&
ignoreAutogenerated.booleanValue()) {
File file = new File(_BASEDIR + fileName);
if (file.exists()) {
String oldContent = _fileUtil.read(
_BASEDIR + fileName + "doc");
if (_isGenerated(oldContent)) {
continue;
}
}
}
if (command.equals("cleanup")) {
_processGet(fileName);
_processSave(fileName);
_processDelete(fileName);
}
else if (command.equals("commit")) {
_processSave(fileName);
_processDelete(fileName);
}
else if (command.equals("delete")) {
_processDelete(fileName);
}
else if (command.equals("get")) {
_processGet(fileName);
}
else if (command.equals("save")) {
_processSave(fileName);
}
}
}
private void _processDelete(String fileName) throws Exception {
_removeJavadocFromJava(fileName, true);
}
private void _processGet(String fileName) throws Exception {
File javadocFile = new File(_BASEDIR + fileName + "doc");
if (!javadocFile.exists()) {
_updateJavadocFromJava(fileName);
}
String javaWithoutJavadoc = _removeJavadocFromJava(fileName, false);
_updateJavaFromJavadoc(fileName, javaWithoutJavadoc);
}
private void _processSave(String fileName) throws Exception {
_updateJavadocFromJava(fileName);
}
private String _removeJavadocFromJava(String fileName, boolean log)
throws Exception {
File file = new File(_BASEDIR + fileName);
String oldContent = _fileUtil.read(file);
String[] lines = StringUtil.splitLines(oldContent);
JavaClass javaClass = _getJavaClass(
fileName, new UnsyncStringReader(oldContent));
Set<Integer> lineNumbers = new HashSet<>();
lineNumbers.add(javaClass.getLineNumber());
JavaMethod[] javaMethods = javaClass.getMethods();
for (JavaMethod javaMethod : javaMethods) {
lineNumbers.add(javaMethod.getLineNumber());
}
JavaField[] javaFields = javaClass.getFields();
for (JavaField javaField : javaFields) {
lineNumbers.add(javaField.getLineNumber());
}
for (int lineNumber : lineNumbers) {
int pos = lineNumber - 2;
String line = lines[pos].trim();
if (line.endsWith("*/")) {
while (true) {
lines[pos] = null;
if (line.startsWith("/**")) {
break;
}
line = lines[--pos].trim();
}
}
}
StringBuilder sb = new StringBuilder(oldContent.length());
for (String line : lines) {
if (line != null) {
sb.append(line);
sb.append("\n");
}
}
String newContent = sb.toString().trim();
if ((oldContent == null) || !oldContent.equals(newContent)) {
_fileUtil.write(file, newContent);
if (log) {
System.out.println("Writing " + file);
}
}
return newContent;
}
private void _updateJavadocFromJava(String fileName) throws Exception {
File file = new File(_BASEDIR + fileName + "doc");
String oldContent = null;
if (file.exists()) {
oldContent = _fileUtil.read(file);
if (_isGenerated(oldContent)) {
return;
}
}
JavaClass javaClass = _getJavaClass(fileName);
String newContent = _getJavadocXml(javaClass);
if ((oldContent == null) || !oldContent.equals(newContent)) {
_fileUtil.write(file, newContent.getBytes());
System.out.println("Writing " + file);
}
}
private void _updateJavaFromJavadoc(String fileName, String oldContent)
throws Exception {
File javadocFile = new File(_BASEDIR + fileName + "doc");
if (!javadocFile.exists()) {
return;
}
File file = new File(_BASEDIR + fileName);
if (oldContent == null) {
oldContent = _fileUtil.read(file);
}
String[] lines = StringUtil.splitLines(oldContent);
JavaClass javaClass = _getJavaClass(
fileName, new UnsyncStringReader(oldContent));
Document document = _saxReader.read(javadocFile);
Element rootElement = document.getRootElement();
Map<Integer, String> commentsMap = new TreeMap<>();
commentsMap.put(
javaClass.getLineNumber(), _getJavaClassComment(rootElement));
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;
}
commentsMap.put(
javaMethod.getLineNumber(),
_getJavaMethodComment(lines, methodElementsMap, javaMethod));
}
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;
}
commentsMap.put(
javaField.getLineNumber(),
_getJavaFieldComment(lines, fieldElementsMap, javaField));
}
StringBuilder sb = new StringBuilder(oldContent.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 newContent = sb.toString().trim();
if ((oldContent == null) || !oldContent.equals(newContent)) {
_fileUtil.write(file, newContent);
System.out.println("Writing " + file);
}
}
private static final String _BASEDIR = "./";
private static final FileImpl _fileUtil = FileImpl.getInstance();
private static final SAXReader _saxReader = new SAXReaderImpl();
}