/*
* #%L restdoc-doclet %% Copyright (C) 2012 IG Group %% Licensed 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. #L%
*/
package com.iggroup.oss.restdoclet.doclet.util;
import static com.iggroup.oss.restdoclet.doclet.util.AnnotationUtils.ignore;
import static com.iggroup.oss.restdoclet.doclet.util.UrlUtils.parseMultiUri;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.sun.javadoc.AnnotationDesc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.ParameterizedType;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.Tag;
import com.sun.javadoc.Type;
/**
* This is an utility class for processing document types.
*/
public final class DocTypeUtils {
private static final Logger LOG = Logger.getLogger(DocTypeUtils.class);
private static final String DEPRECATED_TAG = "uriDeprecated";
private static final String RETURN_TAG = "return";
private static final String IS_PREFIX = "is";
private static final String GETTER_PREFIX = "get";
/**
* Private constructor to "silence" PMD.
*/
private DocTypeUtils() {
super();
}
/**
* Return a string array of deprecated URIs for this element, searching for
* DEPRECATED_TAG in the method comment
*
* @param element javadoc element
* @return a string array of deprecated URIs for this element
*/
public static String[] getDeprecatedURIs(final ProgramElementDoc element) {
String[] uris = null;
Tag[] tags = element.tags();
for (final Tag tag : tags) {
final String name = tag.name();
if (StringUtils.contains(name, DEPRECATED_TAG)) {
uris = parseMultiUri(tag.text());
LOG.debug("deprecated uris" + tag.text());
break;
}
}
return uris;
}
/**
* Generates a comment string from the given method, consisting of the
* method return comment and if the return type is an iggroup type, a list
* of attributes (separated by <br>
* )
*
* @param element method doc
* @return documentation string for the method
*/
public static String getReturnDoc(final MethodDoc element) {
LOG.info("Get return type documentation for method: "
+ element.toString());
String doc = "";
Tag[] tags = element.tags();
String typeDoc = getTypeDoc(element.returnType());
//if (typeComment.isEmpty()) { // no class doc found, revert to @return comment
for (final Tag tag : tags) {
final String name = tag.name();
if (StringUtils.contains(name, RETURN_TAG)) {
doc = tag.text();
if (!typeDoc.isEmpty()) {
doc += "<table><tr><td>" + typeDoc + "</td></tr></table>";
}
break;
}
}
//}
return doc;
}
/**
* Return true if this is not a java type
*
* @param type
* @return
*/
private static Boolean isRelevantType(final Type type) {
assert type.asClassDoc() != null;
Boolean relevant =
type != null && type.asClassDoc() != null
&& !ignore(type.asClassDoc())
&& type.asClassDoc().qualifiedTypeName().indexOf("java") != 0
|| isParameterisedType(type);
LOG.debug(type + " relevance is " + relevant);
return relevant;
}
/**
* Return true if this this a parameterised type
*
* @param type
* @return
*/
private static Boolean isParameterisedType(final Type type) {
return type.asParameterizedType() != null
&& type.asParameterizedType().typeArguments().length > 0;
}
/**
* Log the type information
*
* @param type
*/
private static void logType(final Type type) {
if (LOG.isDebugEnabled() && type.asClassDoc() != null) {
LOG.debug(type.asClassDoc().qualifiedTypeName());
LOG.debug(" - " + type.asClassDoc().commentText());
for (AnnotationDesc ad : type.asClassDoc().annotations()) {
LOG.debug(" - " + ad.toString());
}
}
}
/**
* Return the documentation for an enum type
*
* @param type
* @return
*/
private static String getEnumDoc(final Type type) {
String typeInfo = "";
if (type.asClassDoc() != null) {
FieldDoc[] enumConstants = type.asClassDoc().enumConstants();
for (FieldDoc constant : enumConstants) {
typeInfo += "<TR>";
typeInfo += "<TD>" + constant.name() + "</TD>";
typeInfo += "<TD>" + constant.commentText() + "</TD>";
typeInfo += "</TR>";
}
}
return typeInfo;
}
/**
* Return the documentation for a public constant
*
* @param type
* @return
*/
private static String getPublicConstantDoc(final Type type) {
String typeInfo = "";
FieldDoc[] fields = type.asClassDoc().fields(false);
for (FieldDoc field : fields) {
if (field.isPublic() && field.isFinal()
&& StringUtils.equals(field.name(), field.name().toUpperCase())) {
typeInfo +=
"<tr><td>" + field.type().simpleTypeName() + " " + field.name()
+ "</td><td>" + field.commentText() + "</td></tr>";
}
}
return typeInfo;
}
/**
* Return the documentation for a field
*
* @param type
* @param attributeName
* @return
*/
private static String getFieldDoc(final Type type,
final String attributeName,
final String methodComment) {
LOG.debug("getFieldDoc " + type.simpleTypeName() + " - " + attributeName
+ " - " + methodComment);
String fieldComment = "";
if (type.asClassDoc() != null) {
for (FieldDoc field : type.asClassDoc().fields(false)) {
if (field.name().equalsIgnoreCase(attributeName)) {
fieldComment = field.commentText();
if (methodComment.length() > fieldComment.length()) {
fieldComment = methodComment;
}
// see if there are any validation
// constraints
AnnotationDesc[] annotations = field.annotations();
for (AnnotationDesc annotation : annotations) {
if (annotation.toString().contains(
"@javax.validation.constraints.")) {
String constraint =
annotation.toString().replace(
"@javax.validation.constraints.", "");
fieldComment += "<br>[Rule: " + constraint + "]";
}
}
break;
}
}
}
return fieldComment;
}
/**
* Return a list of getter names
*
* @param type
* @return
*/
private static Collection<String> getGetterNames(final Type type) {
FieldDoc[] fields = type.asClassDoc().fields(false);
ArrayList<String> getterNames = new ArrayList<String>();
for (FieldDoc field : fields) {
if (!(field.isPublic() && field.isFinal() && StringUtils.equals(
field.name(), field.name().toUpperCase()))) {
getterNames.add(GETTER_PREFIX
+ field.name().substring(0, 1).toUpperCase()
+ field.name().substring(1));
getterNames.add(IS_PREFIX
+ field.name().substring(0, 1).toUpperCase()
+ field.name().substring(1));
}
}
return getterNames;
}
/**
* Returns as a string a list of attributes plus comments for the given
* iggroup complex type (or empty string if not iggroup), formatted in an
* HTML table. This method will recurse if attributes are iggroup complex
* types
*
* @param type
* @param processedTypes
* @param leafType
* @return
*/
private static String getTypeDoc(final Type type,
HashMap<String, String> processedTypes,
Boolean leafType) {
LOG.info("getTypeDoc " + type + " leafType=" + leafType);
String typeInfo = "";
if (isRelevantType(type)) {
ClassDoc typeDoc = type.asClassDoc();
typeInfo = processedTypes.get(type.toString());
if (typeInfo != null) {
LOG.debug("Found cached typedoc for " + type.typeName() + " - "
+ typeInfo);
}
if (typeInfo == null && typeDoc != null) {
typeInfo = "";
// if this is a generic type then recurse with the first type argument
if (isParameterisedType(type)) {
LOG.debug("Parameterised type");
if (type.asClassDoc() != null) {
typeInfo =
getTypeDoc(
type.asParameterizedType().typeArguments()[type
.asParameterizedType().typeArguments().length - 1],
processedTypes, true);
}
} else {
logType(type);
// put placeholder to stop recursion for self-referential types
LOG.debug("Starting to cache: " + type.typeName());
processedTypes.put(type.toString(), "");
LOG.debug(typeDoc.commentText() + " " + leafType);
if (leafType && !typeDoc.commentText().isEmpty()) {
typeInfo +=
"<tr><span class=\"javadoc-header\">"
+ typeDoc.commentText() + "</span></tr>";
LOG.debug(typeInfo);
}
if (typeDoc.isEnum()) {
LOG.debug("Enum type");
typeInfo += getEnumDoc(type);
} else { // class
LOG.debug("Class");
// first do base class
if (typeDoc.superclass() != null) {
LOG.debug("base type = "
+ typeDoc.superclass().qualifiedName());
String baseTypeDoc =
getTypeDoc(type.asClassDoc().superclassType(),
processedTypes, false);
if (!baseTypeDoc.isEmpty()) {
LOG.debug("base type DOC = " + baseTypeDoc);
typeInfo += baseTypeDoc;
}
}
typeInfo += getPublicConstantDoc(type);
Collection<String> getterNames = getGetterNames(type);
for (MethodDoc method : typeDoc.methods()) {
if (method.isPublic()
&& getterNames.contains(method.name())
&& !ignore(method)) {
String attributeInfo = "";
String attributeType =
method.returnType().simpleTypeName();
// check if is this a parameterised type
ParameterizedType pt =
method.returnType().asParameterizedType();
if (pt != null && pt.typeArguments().length > 0) {
attributeType += "[";
for (int i = 0; i < pt.typeArguments().length; i++) {
attributeType +=
pt.typeArguments()[i].simpleTypeName();
if (i < pt.typeArguments().length - 1) {
attributeType += ", ";
}
}
attributeType += "]";
}
// Check if this is an array
attributeType += method.returnType().dimension();
final String attributeName =
getAttributeNameFromMethod(method.name());
attributeInfo +=
"<td>" + attributeType + " " + attributeName
+ "</td>";
// If type or parameterised type then recurse
LOG.debug("Generating attribute doc for "
+ method.returnType());
String attributeTypeDoc =
getTypeDoc(method.returnType(), processedTypes,
true);
if (!attributeTypeDoc.isEmpty()) {
LOG.debug("Found attribute doc for "
+ method.returnType());
attributeInfo +=
"<td>" + attributeTypeDoc + "</td>";
} else { // no useful type information, so use whatever's on the attribute doc
LOG.debug("Found no attribute doc for "
+ method.returnType());
String fieldDoc =
getFieldDoc(type, attributeName,
method.commentText());
attributeInfo += "<td>" + fieldDoc + "</td>";
}
if (!attributeInfo.isEmpty()) {
typeInfo += "<tr>" + attributeInfo + "</tr>";
}
}
}
}
// Wrap in a table tag if this is concrete type
if (leafType && !typeInfo.isEmpty()) {
typeInfo = "<table>" + typeInfo + "</table>";
}
}
}
LOG.debug("Caching: " + type);
processedTypes.put(type.toString(), typeInfo);
}
if (typeInfo == null) {
typeInfo = "";
}
LOG.debug("XXX " + type.typeName() + " XXX " + typeInfo);
return typeInfo;
}
/**
* Returns as a string a list of attributes plus comments for the given
* type, separated by <br>
* This method will recurse if attributes are non java types
*
* @param type type info
* @return attribute data for the given type
*/
public static String getTypeDoc(final Type type) {
HashMap<String, String> documentedTypes = new HashMap<String, String>();
String typeDoc = getTypeDoc(type, documentedTypes, true);
if (typeDoc != null && !typeDoc.trim().isEmpty()) {
LOG.info("Got documentation for type " + type + " : " + typeDoc);
}
return typeDoc;
}
/**
* Return the simple type name of the passed in type, and include the simple
* name of the template type if a generic
*
* @param type type info
* @return simple type name (including template type name in [] if
* applicable)
*/
public static String getTypeName(final Type type) {
String typeName = "";
typeName = type.simpleTypeName();
ParameterizedType pt = type.asParameterizedType();
if (pt != null && pt.typeArguments() != null
&& pt.typeArguments().length > 0) {
typeName += "[";
for (int i = 0; i < pt.typeArguments().length; i++) {
typeName += pt.typeArguments()[i].simpleTypeName();
if (i < pt.typeArguments().length - 1) {
typeName += ", ";
}
}
typeName += "]";
}
return typeName;
}
/**
* Derive an attribute name from a getter/setter
*
* @param methodName
* @return the methodName without the leading is/get/set
*/
private static String getAttributeNameFromMethod(String methodName) {
String attributeName;
if (methodName.startsWith(IS_PREFIX)) {
attributeName = methodName.substring(IS_PREFIX.length());
} else {
attributeName = methodName.substring(GETTER_PREFIX.length());
}
if (attributeName != null && !attributeName.isEmpty()) {
attributeName =
attributeName.substring(0, 1).toLowerCase()
+ attributeName.substring(1);
}
return attributeName;
}
}