cn.mapway.document.parser.SpringParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mapway-doc-ui Show documentation
Show all versions of mapway-doc-ui Show documentation
auto gen doc from api with ui
package cn.mapway.document.parser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import cn.mapway.document.helper.Markdowns;
import cn.mapway.document.helper.Scans;
import cn.mapway.document.module.ApiDoc;
import cn.mapway.document.module.Entry;
import cn.mapway.document.module.Group;
import cn.mapway.document.module.ObjectInfo;
import org.nutz.castor.Castors;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Files;
import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;
import org.nutz.lang.Strings;
import org.nutz.log.Logs;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import cn.mapway.document.annotation.ApiField;
import cn.mapway.document.annotation.ApiStyle;
import cn.mapway.document.annotation.Code;
import cn.mapway.document.annotation.Codes;
import cn.mapway.document.annotation.DevelopmentState;
import cn.mapway.document.annotation.Doc;
import cn.mapway.document.annotation.RuntimeType;
import cn.mapway.document.module.FieldCode;
/**
* 解析Spring 注解.
*
* @author zhangjianshe @gmail.com
*/
public class SpringParser {
/**
* The Constant log.
*/
private final static org.nutz.log.Log log = Logs.getLog(SpringParser.class);
/**
* The m context.
*/
GenContext mContext;
/**
* 解析包中的类.
*
* @param context the context
* @param packageNames 包名
* @return the api doc
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
public ApiDoc parse(GenContext context, String... packageNames)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
mContext = context;
if (packageNames.length == 0) {
return new ApiDoc();
}
ArrayList> clzs = new ArrayList>();
for (String pk : packageNames) {
Set> clz = scanPackage(pk);
clzs.addAll(clz);
}
ApiDoc doc = new ApiDoc();
doc.author = context.getAuthor();
doc.basePath = context.getBasepath();
doc.title = context.getDocTitle();
doc.wordUrl = context.getWordURL();
doc.summary = context.getSubtitle();
doc.domain = context.getDomain();
doc.copyright = context.getCopyright();
doc.apiVersion = context.getApiVersion();
doc.logo = context.getLogobase64();
doc.homeUrl = context.getHomeUrl();
doc.cssStyle = context.getCssStyle();
if (Strings.isEmpty(doc.apiVersion)) {
doc.apiVersion = "1.0";
}
doc.copyright = doc.copyright + "-" + CompileVersion.VERSION;
for (Class> clz : clzs) {
if (clz.getAnnotation(Controller.class) != null || clz.getAnnotation(RestController.class) != null) {
parseClass(doc, clz);
}
}
doc.root.name = doc.title;
doc.root.fullName = "/" + doc.title;
doc.root.summary = "";
doc.sort();
return doc;
}
private Set> scanPackage(String pk) {
Set> clzs = Scans.getClasses(pk);
return clzs;
}
/**
* 解析某个类.
*
* @param document the document
* @param clz the clz
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private void parseClass(ApiDoc document, Class> clz)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
Doc doc = clz.getAnnotation(Doc.class);
if (doc == null) {
return;
}
populateGroup(document, clz);
}
/**
* 填充Group信息.
*
* @param document the document
* @param c the c
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private void populateGroup(ApiDoc document, Class> c)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
// 类Doc
Doc doc = c.getAnnotation(Doc.class);
String group_base_path = doc.group();
Group apigroup = document.findGroup(group_base_path);
apigroup.summary += doc.desc().length() > 0 ? doc.desc() : "";
apigroup.summary += parseRef(c, doc.refs());
apigroup.order = doc.order();
String basepath = "";
RequestMapping rm = c.getAnnotation(RequestMapping.class);
if (rm != null) {
String[] paths = rm.value();
if (paths == null || paths.length == 0) {
} else {
basepath = paths[0];
}
}
Method[] methods = c.getDeclaredMethods();
List list = new ArrayList();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
RequestMapping rm1 = m.getAnnotation(RequestMapping.class);
if (rm1 != null) {
list.add(m);
}
}
for (int i = 0; i < list.size(); i++) {
Method m = list.get(i);
Entry entry = handleMethod(c, document, group_base_path, m);
entry.parentClassName = c.getName();
if (entry != null) {
entry.relativePath = basepath + entry.relativePath;
entry.url = mContext.getBasepath() + entry.relativePath;
}
}
}
/**
* 解析方法,生成APIentry.
*
* @param document the document
* @param group_base_path the group base path
* @param m the m
* @return the entry
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private Entry handleMethod(Class> clsType, ApiDoc document, String group_base_path, Method m)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
Entry e = new Entry();
RequestMapping rm = m.getAnnotation(RequestMapping.class);
if (rm != null) {
// TODO 增加对多路径的支持
String[] paths = rm.value();
if (paths == null || paths.length == 0) {
} else {
e.relativePath = paths[0];
}
// 处理请求方法 支持对多种调用方式 POST GET PUT DELETE 等
RequestMethod[] ms = rm.method();
if (ms != null) {
for (int i = 0; i < ms.length; i++) {
RequestMethod rm0 = ms[i];
e.invokeMethods.add(rm0.name());
}
}
}
if (e.invokeMethods.size() == 0) {
e.invokeMethods.add("GET");
}
if (e.relativePath.length() == 0) {
return null;
}
e.methodName = m.getName();
Doc summary = m.getAnnotation(Doc.class);
if (summary != null) {
e.title = summary.value();
e.summary = summary.desc() == null ? "" : summary.desc();
e.summary += parseRef(clsType, summary.refs());
e.order = summary.order();
e.author = summary.author();
e.state = transState(summary.state());
e.apiStyle = transApiStyle(summary.style());
// 处理方法的标签.
if (summary.tags() != null) {
for (String tag : summary.tags()) {
e.tags.add(tag);
}
}
}
Class>[] ps = m.getParameterTypes();
Class> out = (Class>) m.getReturnType();
Type returnType = m.getGenericReturnType();
System.out.println(returnType);
int i = 0;
for (Class> clz : ps) {
String name = clz.getSimpleName();
String pname = "";
if (clz.getPackage() != null) {
pname = clz.getPackage().getName();
}
if (name.startsWith("Http") || pname.startsWith("org.")) {
continue;
} else {
Annotation[][] parameterAnnotations = m.getParameterAnnotations();
Annotation[] ass = parameterAnnotations[i];
// 处理每一个参数
PathVariable pathVariable = null;
RequestParam queryVariable = null;
RequestBody isRequestBody = null;
ApiField paraDoc = null;
Size stringConstrain = null;
NotNull nullConstrain = null;
Min minConstrain = null;
Max maxConstrain = null;
for (Annotation a : ass) {
if (a instanceof PathVariable) {
pathVariable = (PathVariable) a;
} else if (a instanceof RequestParam) {
queryVariable = (RequestParam) a;
} else if (a instanceof ApiField) {
paraDoc = (ApiField) a;
} else if (a instanceof RequestBody) {
isRequestBody = (RequestBody) a;
} else if (a instanceof Size) {
stringConstrain = (Size) a;
} else if (a instanceof NotNull) {
nullConstrain = (NotNull) a;
} else if (a instanceof Min) {
minConstrain = (Min) a;
} else if (a instanceof Max) {
maxConstrain = (Max) a;
}
}
ObjectInfo p = handleParameter(clz, name);
if (p == null) {
continue;
}
if (paraDoc != null) {
p.example = paraDoc.example();
p.manditary = paraDoc.mandidate();
p.title = paraDoc.value();
// 处理注释,从ref中获取
p.summary = parseRef(clsType, paraDoc.refs());
}
// 长度约束
if (stringConstrain != null) {
p.minLength = stringConstrain.min();
p.maxLength = stringConstrain.max();
}
if (minConstrain != null) {
p.min = minConstrain.value();
}
if (maxConstrain != null) {
p.max = maxConstrain.value();
}
if (nullConstrain != null) {
p.manditary = true;
}
if (pathVariable != null) {
// 路径参数
if (Strings.isBlank(pathVariable.value())) {
p.name = "未知";
} else {
p.name = pathVariable.value();
}
e.pathParas.add(p);
} else if (queryVariable != null) {
p.name = queryVariable.value();
e.queryParas.add(p);
} else if (isRequestBody != null) {
e.input.add(p);
}
i++;
}
}
// 处理返回值注解
ApiField returnDoc = m.getAnnotation(ApiField.class);
e.output = handleParameter(out, "out");
if (returnDoc != null) {
// 类型 解释 例子
if (returnDoc.example() != null && returnDoc.example().length() > 0) {
e.output.example = returnDoc.example();
}
}
String group_path = group_base_path + summary.group();
Group apiGroup = document.findGroup(group_path);
apiGroup.entries.add(e);
return e;
}
/**
* 解析ref html.
*
* @param refs
* @return
*/
private String parseRef(Class> type, String[] refs) {
if (refs == null || refs.length == 0) {
// 如果没有引用描述页面,则不处理
return "";
}
StringBuilder sb = new StringBuilder();
for (String ref : refs) {
if (Strings.isBlank(ref)) {
//移除空字符串
continue;
}
boolean isMarkdown = Files.getSuffixName(ref).equalsIgnoreCase("md");
String desc = Scans.readResource(type.getPackage().getName(), ref);
if (isMarkdown) {
//转换markdown to html
desc = translateMarkdown(desc);
sb.append("").append(desc).append("");
}
else {
sb.append(desc);
}
}
return sb.toString();
}
/**
* 转换Markdown文件格式.
*
* @param desc
* @return
*/
private String translateMarkdown(String desc) {
if (Strings.isBlank(desc)) {
return "";
}
return Markdowns.get().translate(desc);
}
/**
* 转换API样式
*
* @param style
* @return
*/
private String transApiStyle(ApiStyle style) {
return style.name();
}
/**
* Trans state.
*
* @param state the state
* @return the string
*/
private String transState(DevelopmentState state) {
if (state == DevelopmentState.FINISH) {
return "已完成";
} else if (state == DevelopmentState.OBSOLETED) {
return "已废弃";
} else if (state == DevelopmentState.PROCESS) {
return "开发中";
} else if (state == DevelopmentState.UNSTART) {
return "未开发";
}
return "";
}
/**
* 处理参数.
*
* @param clz the clz
* @param name the name
* @return the object info
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private ObjectInfo handleParameter(Class> clz, String name)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
ObjectInfo p = new ObjectInfo();
Doc doc = clz.getAnnotation(Doc.class);
p.name = name == null ? clz.getSimpleName() : name;
p.title = doc == null ? "" : doc.value();
if (isPrimitive(clz)) {
p.type = clz.getSimpleName();
} else {
p.type = clz.getName();
}
String sum = doc == null ? "" : (Strings.isBlank(doc.desc()) ? doc.value() : doc.desc());
// 循环处理父类中的解释
Class> superclazz = clz.getSuperclass();
while (superclazz != null) {
Doc summary1 = superclazz.getAnnotation(Doc.class);
if (sum.length() > 0) {
sum += "
";
}
sum += summary1 == null ? "" : summary1.desc();
superclazz = superclazz.getSuperclass();
}
if (doc != null) {
p.summary = sum + parseRef(clz, doc.refs());
}
deeps = new Deeps();
deeps.push(clz.getName(), deeps.getLevel());
Object instance = null;
instance = newInstance(clz);
for (Field f : clz.getFields()) {
ObjectInfo fld = handleField(instance, f);
if (fld != null) {
p.fields.add(fld);
}
}
if (instance != null)
p.json = Json.toJson(instance, JsonFormat.full());
else {
p.json = "{}";
}
return p;
}
/**
* 类深度信息.
*/
Deeps deeps;
/**
* 处理字段.
*
* @param instance the instance
* @param f the f
* @return the object info
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private ObjectInfo handleField(Object instance, Field f)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
deeps.incLevel();
ApiField wf = f.getAnnotation(ApiField.class);
Annotation[] ass = f.getAnnotations();
if (wf != null) {
ObjectInfo fi = new ObjectInfo();
fi.manditary = wf.mandidate();
fi.title = wf.value();
fi.example = wf.example();
fi.name = f.getName();
fi.type = f.getType().getName();
// 处理返回代码
Codes codes = f.getAnnotation(Codes.class);
if (codes != null) {
for (Code code : codes.value()) {
FieldCode fc = new FieldCode(code.value(), code.desc());
fi.codes.add(fc);
}
}
// 处理字段约束
Size stringConstrain = null;
NotNull nullConstrain = null;
Min minConstrain = null;
Max maxConstrain = null;
for (Annotation a : ass) {
if (a instanceof Size) {
stringConstrain = (Size) a;
} else if (a instanceof NotNull) {
nullConstrain = (NotNull) a;
} else if (a instanceof Min) {
minConstrain = (Min) a;
} else if (a instanceof Max) {
maxConstrain = (Max) a;
}
}
// 长度约束
if (stringConstrain != null) {
fi.minLength = stringConstrain.min();
fi.maxLength = stringConstrain.max();
}
if (minConstrain != null) {
fi.min = minConstrain.value();
}
if (maxConstrain != null) {
fi.max = maxConstrain.value();
}
if (nullConstrain != null) {
fi.manditary = true;
}
// 记录类型的循环次数
deeps.push(f.getType().getName(), deeps.getLevel());
// 处理字段
if (isPrimitive(f.getType())) {
tacklePrimitive(instance, f, wf, fi);
} else if (isList(f)) {
ArrayList list = new ArrayList();
Type type = getGenericType(f);
Class> c = null;
if (type instanceof ParameterizedType) {
ParameterizedType t = (ParameterizedType) type;
c = (Class>) t.getRawType();
} else if (type instanceof sun.reflect.generics.reflectiveObjects.TypeVariableImpl) {
c = Object.class;
} else {
c = (Class>) type;
}
f.set(instance, list);
fi.type = "List<" + type.getTypeName() + ">";
if (isMap(type.getClass())) {
tackleMap(fi, list);
} else if (isGeneric(c)) {
tackleListGnericType(list, f, fi);
} else {
tackleListObject(fi, list, c);
}
} else if (isGeneric(f.getType())) {
tackleGnericType(instance, f, fi);
} else {
// 该字段是一个对象类,循环处理此类
int count = deeps.getPreLevelCount(f.getType().getName(), deeps.getLevel());
if (count > 2) {
// 不处理了,油循环引用
deeps.decLevel();
return null;
}
tackleObject(instance, f, fi);
}
deeps.decLevel();
return fi;
}
deeps.decLevel();
return null;
}
/**
* @param instance
* @param f
* @param wf
* @param fi
*/
private void tacklePrimitive(Object instance, Field f, ApiField wf, ObjectInfo fi) {
// 原始数据类型 无需解析子类
fi.type = f.getType().getSimpleName();
if (instance != null) {
// 处理例子
if (!Strings.isBlank(wf.example())) {
Object obj = Castors.me().castTo(wf.example(), f.getType());
Mirror.me(instance).setValue(instance, f, obj);
}
}
}
/**
* @param instance
* @param f
* @param fi
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void tackleObject(Object instance, Field f, ObjectInfo fi)
throws IllegalAccessException, InstantiationException {
Object cinstance = null;
cinstance = newInstance(f.getType());
f.set(instance, cinstance);
// 读取List数组中对象的Doc注解
Doc fdoc = cinstance.getClass().getAnnotation(Doc.class);
if (fdoc != null) {
fi.summary = fdoc.desc();
}
for (Field f1 : f.getType().getFields()) {
ObjectInfo o = handleField(cinstance, f1);
if (o != null) {
fi.fields.add(o);
}
}
}
/**
* @param instance
* @param f
* @param fi
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void tackleGnericType(Object instance, Field f, ObjectInfo fi)
throws IllegalAccessException, InstantiationException {
// 是一个泛型类 处理特殊的注解
fi.type = "Object";
RuntimeType rt = f.getAnnotation(RuntimeType.class);
if (rt == null) {
// 程序员没有添加注解,需要补充完整
fi.summary = "请程序员添加注解 RuntimeType";
} else {
// 处理程序员添加的注解
for (int i = 0; i < rt.value().length; i++) {
Class> cls = rt.value()[i];
fi.refs.add(handleParameter(cls, ""));
}
}
f.set(instance, new Object());
}
/**
* @param instance
* @param f
* @param fi
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void tackleListGnericType(List instance, Field f, ObjectInfo fi)
throws IllegalAccessException, InstantiationException {
// 是一个泛型类 处理特殊的注解
fi.type = "List