/**
* Copyright 2013-2014 the original author or authors.
*
* 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.
*/
package com.iflytek.edu.cloud.frame.doc;
import static java.io.File.separator;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang.StringUtils;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
import com.iflytek.edu.cloud.frame.annotation.ServiceMethod;
import com.iflytek.edu.cloud.frame.doc.model.ClassDoc;
import com.iflytek.edu.cloud.frame.doc.model.JavaBeanDoc;
import com.iflytek.edu.cloud.frame.doc.model.JavaBeanDoc.FieldDoc;
import com.iflytek.edu.cloud.frame.doc.model.MethodDoc;
import com.iflytek.edu.cloud.frame.doc.model.ParamDoc;
import com.iflytek.edu.cloud.frame.doc.model.ReturnDoc;
import com.iflytek.edu.cloud.frame.utils.EnvUtil;
import com.iflytek.edu.cloud.frame.utils.SystemPropertyUtil;
import com.thoughtworks.qdox.JavaDocBuilder;
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;
/**
*
* @author libinsong1204@gmail.com
*
*/
public class ServiceDocBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceDocBuilder.class);
private JavaDocBuilder builder;
public ServiceDocBuilder() {
builder = new JavaDocBuilder();
builder.setEncoding("UTF-8");
addJavaSource();
}
public List<ClassDoc> buildDoc() {
List<ClassDoc> classDocs = new ArrayList<ClassDoc>();
List<JavaClass> javaClasses = findRestServices();
for(JavaClass javaClass : javaClasses) {
ClassDoc classDoc = new ClassDoc();
classDoc.setServiceDesc(javaClass.getComment());
classDoc.setServiceName(javaClass.getTagByName("serviceName").getValue());
List<JavaMethod> javaMethods = findMethods(javaClass);
for(JavaMethod method : javaMethods) {
//方法文档信息
MethodDoc methodDoc = new MethodDoc();
Annotation[] annotations = method.getAnnotations();
for(Annotation ann : annotations) {
if(ServiceMethod.class.getName().equals(ann.getType().getFullyQualifiedName())) {
String name = (String)ann.getNamedParameter("value");
String version = (String)ann.getNamedParameter("version");
methodDoc.setName(StringUtils.strip(name.trim(), "\""));
methodDoc.setVersion(StringUtils.strip(version.trim(), "\""));
break;
}
}
methodDoc.setDescription(method.getComment());
//返回值文档信息
ReturnDoc returnDoc = new ReturnDoc();
returnDoc.setDataType(method.getReturnType().getFullyQualifiedName());
if("java.util.List".equals(returnDoc.getDataType())) {
Type type = method.getReturnType().getActualTypeArguments()[0];
if(!ClassUtils.isPrimitiveOrWrapper(type.getFullyQualifiedName())) {
returnDoc.setBeanName(type.getFullyQualifiedName());
JavaBeanDoc beanDoc = getJavaBeanDoc(type.getFullyQualifiedName());
classDoc.getBeanDocs().add(beanDoc);
}
}
if(method.getTagByName("return") ==null) {
throw new RuntimeException("方法 " + method.getName() + " 没有return注释");
}
//解析返回实例数据,例如返回值文档注释格式: @return userid A2342312 $$ 用户ID
String returnDesc = method.getTagByName("return").getValue();
String[] desces = returnDesc.split("\\$\\$");
if(desces.length > 1) {
returnDoc.setExampleData(desces[0]);
returnDoc.setDescription(desces[1]);
} else {
returnDoc.setDescription(returnDesc);
}
returnDoc.setDimensions(method.getReturnType().getDimensions());
methodDoc.setReturnDoc(returnDoc);
//方法参数文档信息
JavaParameter[] parameters = method.getParameters();
DocletTag[] paramTags = method.getTagsByName("param");
for(int i=0, len=parameters.length; i<len; i++) {
JavaParameter parameter = parameters[i];
String type = parameter.getType().getFullyQualifiedName();
String paramName = parameter.getName();
ParamDoc paramDoc = new ParamDoc();
paramDoc.setName(paramName);
paramDoc.setDataType(type);
//解析参数实例数据,例如参数文档注释格式: @param userid A2342312 $$ 用户ID
String paramDesc = getParamDesc(paramName, paramTags);
desces = paramDesc.split("\\$\\$");
if(desces.length > 1) {
paramDoc.setExampleData(desces[0].trim());
paramDoc.setDescription(desces[1].trim());
} else {
paramDoc.setDescription(paramDesc);
}
methodDoc.getParamDocs().add(paramDoc);
//基本类型,和javax.servlet.*相关的类排除掉
if(!ClassUtils.isPrimitiveOrWrapper(type) && !type.startsWith("javax.servlet")) {
classDoc.getBeanDocs().add(getJavaBeanDoc(type));
}
}
classDoc.getMethodDocs().add(methodDoc);
}
boolean isNew = true;
for(ClassDoc doc : classDocs) {
if(doc.getServiceName().equals(classDoc.getServiceName())) {
doc.getBeanDocs().addAll(classDoc.getBeanDocs());
doc.getMethodDocs().addAll(classDoc.getMethodDocs());
isNew = false;
break;
}
}
if(isNew)
classDocs.add(classDoc);
}
return classDocs;
}
private JavaBeanDoc getJavaBeanDoc(String type) {
JavaClass javaClass = builder.getClassByName(type);
JavaField[] fields = javaClass.getFields();
JavaBeanDoc beanDoc = new JavaBeanDoc();
beanDoc.setName(javaClass.getFullyQualifiedName());
beanDoc.setDescription(javaClass.getComment());
for(JavaField field : fields) {
FieldDoc fieldDoc = new FieldDoc();
fieldDoc.setName(field.getName());
fieldDoc.setDataType(field.getType().getFullyQualifiedName());
fieldDoc.setDescription(field.getComment());
DocletTag tag = field.getTagByName("data");
if(tag != null)
fieldDoc.setExampleData(tag.getValue());
beanDoc.getFieldDocs().add(fieldDoc);
Annotation[] anns = field.getAnnotations();
for(Annotation ann : anns) {
String typeName = ann.getType().getFullyQualifiedName();
if(NotEmpty.class.getName().equals(typeName) ||
NotNull.class.getName().equals(typeName)) {
fieldDoc.setRequired(true);
break;
}
}
}
return beanDoc;
}
/**
* 获取参数信息;
*
* @return
*/
private String getParamDesc(String paramName, DocletTag[] paramTags) {
for(DocletTag tag : paramTags) {
String desc = tag.getValue();
if(desc != null && desc.trim().startsWith(paramName)) {
desc = desc.substring(paramName.length()).trim();
return desc;
}
}
return "";
}
public List<JavaClass> findRestServices() {
List<JavaClass> restJavaClasses = new ArrayList<JavaClass>();
JavaClass[] javaClasses = builder.getClasses();
for(JavaClass javaClass : javaClasses) {
Annotation[] annotations = javaClass.getAnnotations();
for(Annotation ann : annotations) {
if(RestController.class.getName().equals(ann.getType().getFullyQualifiedName())) {
restJavaClasses.add(javaClass);
break;
}
}
}
return restJavaClasses;
}
public List<JavaMethod> findMethods(JavaClass javaClass) {
List<JavaMethod> restMethods = new ArrayList<JavaMethod>();
JavaMethod[] methods = javaClass.getMethods();
for(JavaMethod method : methods) {
Annotation[] annotations = method.getAnnotations();
for(Annotation ann : annotations) {
if(ServiceMethod.class.getName().equals(ann.getType().getFullyQualifiedName())) {
restMethods.add(method);
break;
}
}
}
return restMethods;
}
private void addJavaSource() {
String sourcePath = null;
if(EnvUtil.isDevelopment()) {
sourcePath = EnvUtil.getProjectBaseDir() + separator +
"src" + separator + "main" + separator + "java";
File sourceDir = new File(sourcePath);
builder.addSourceTree(sourceDir);
} else {
try {
sourcePath = SystemPropertyUtil.get("BASE_HOME") + separator + "source" + separator
+ EnvUtil.getProjectName() + "-" + EnvUtil.getBuildVersion() + "-sources.jar";
doServiceJavaSource(sourcePath);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
private void doServiceJavaSource(String jarFileUrl)
throws IOException {
JarFile jarFile = new JarFile(jarFileUrl);
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
}
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
if(entryPath.endsWith(".java")) {
InputStream inputStream = jarFile.getInputStream(entry);
builder.addSource(new InputStreamReader(inputStream, "UTF-8"));
}
}
}
finally {
jarFile.close();
}
}
}