├── .idea ├── .gitignore ├── compiler.xml ├── encodings.xml ├── libraries │ ├── Maven__com_alibaba_fastjson_1_2_61.xml │ ├── Maven__javax_servlet_javax_servlet_api_4_0_1.xml │ ├── Maven__org_slf4j_slf4j_api_1_7_28.xml │ ├── Maven__org_springframework_boot_spring_boot_2_1_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_autoconfigure_2_1_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_configuration_processor_2_1_7_RELEASE.xml │ ├── Maven__org_springframework_spring_aop_5_1_9_RELEASE.xml │ ├── Maven__org_springframework_spring_beans_5_1_9_RELEASE.xml │ ├── Maven__org_springframework_spring_context_5_1_9_RELEASE.xml │ ├── Maven__org_springframework_spring_core_5_1_9_RELEASE.xml │ ├── Maven__org_springframework_spring_expression_5_1_9_RELEASE.xml │ └── Maven__org_springframework_spring_jcl_5_1_9_RELEASE.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── pom.xml ├── smalldoc-core ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── liuhuagui │ │ └── smalldoc │ │ ├── core │ │ ├── DefaultSmallDocletImpl.java │ │ ├── SmallDocContext.java │ │ ├── SmallDoclet.java │ │ ├── constant │ │ │ └── Constants.java │ │ └── storer │ │ │ ├── FieldDocStorer.java │ │ │ ├── MappingDescStorer.java │ │ │ ├── MethodParamsStorer.java │ │ │ └── ParamTagStorer.java │ │ ├── properties │ │ └── SmallDocProperties.java │ │ ├── util │ │ ├── Assert.java │ │ ├── ParamFormatUtils.java │ │ ├── TypeUtils.java │ │ └── Utils.java │ │ └── web │ │ └── SmallDocServlet.java │ └── resources │ └── smalldoc │ └── support │ └── http │ └── resources │ ├── css │ ├── 2.2fbe55bd.chunk.css │ └── main.56fc618a.chunk.css │ ├── favicon.ico │ ├── index.html │ └── js │ ├── 2.6e803592.chunk.js │ └── main.6e79cee6.chunk.js ├── smalldoc-spring-boot-starter ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── liuhuagui │ │ └── spring │ │ └── boot │ │ └── autoconfigure │ │ └── SmallDocAutoConfiguration.java │ └── resources │ └── META-INF │ └── spring.factories └── smalldoc.iml /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Default ignored files 3 | /workspace.xml -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_alibaba_fastjson_1_2_61.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_servlet_javax_servlet_api_4_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_28.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_2_1_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_autoconfigure_2_1_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_configuration_processor_2_1_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_aop_5_1_9_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_beans_5_1_9_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_context_5_1_9_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_core_5_1_9_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_expression_5_1_9_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_jcl_5_1_9_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 项目 2 | 一个基于Java标准注释的 RESTful API 文档生成及测试工具。 3 | 4 | ## 为什么要造轮子? 5 | - 强迫症患者,接受不了 **Swagger** 的各式注解对代码的入侵造成的冗杂,更渴望清洁的代码; 6 | - 注解的使用需要一定的学习成本; 7 | - 随后尝试使用 **Apidoc** ,尽管Apidoc是基于注释生成文档,但是学习成本并没有降低,你需要学习额外的注释 **Tag** ,同时你不得不使用这些特殊的 **Tag** 将你所需接口的相关信息手动写出来,感觉并没有降低书写文档的工作量; 8 | - 也有一些基于Java标准注释生成文档的项目,但是有的无法支持`实体参数`、`泛型变量`,无法支持`多模块`、`第三方依赖`及`微服务文档集成`;有的Bug太多,UI界面不够友好;有的使用方式过于复杂(需要依赖Maven插件和繁琐的配置),甚至逻辑处理上存在问题。 9 | - 如果一个API文档同时集成了类PostMan的测试功能,一锤搞定,不用在`API文档`和`PostMan或RestClient之类的测试工具`间频繁切换,难道不香吗? 10 | 11 | ## 特性[*详情可见*](https://github.com/liuhuagui/smalldoc/wiki/Features) 12 | - 提供了`smalldoc-spring-boot-starter`及规范配置,让你在`spring-boot`项目中快速集成文档工具 13 | - 基于Java源码、标准注释以及Tag生成文档,无代码入侵,保证代码清洁 14 | - 包含大量的注释检查断言,保证开发人员的注释习惯与注释规范 15 | - 强大的参数配置语法,满足开发者对参数展示的各种要求 16 | - 支持实体参数,泛型字段,集合字段,关联实体等复杂参数的自动解析 17 | - 支持忽略解析指定包或指定类型参数的数据结构 18 | - 可配置的解析类名,支持正则匹配 19 | - 支持多模块及第三方依赖 20 | - 支持微服务文档集成 21 | - 对UNIX系统做了兼容 22 | - 支持接口参数示例值推断 23 | - 集成了API测试功能 24 | - 提供了友好的默认UI,支持离线文档生成; 25 | - 提供了文档RESTEful API,支持实现自定义UI; 26 | 27 | ## 文档 28 | [documention](https://github.com/liuhuagui/smalldoc/wiki/) 29 | 30 | ## 快速使用 31 | [quick start](https://github.com/liuhuagui/smalldoc/wiki/Quick-Start) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.liuhuagui 8 | smalldoc 9 | 2.4 10 | pom 11 | 12 | 13 | smalldoc-core 14 | smalldoc-spring-boot-starter 15 | 16 | 17 | https://github.com/liuhuagui/smalldoc 18 | smalldoc 19 | Zero-intrusion Java Restful API documentation tool based on code comments. 20 | 21 | 22 | 23 | The Apache Software License, Version 2.0 24 | https://www.apache.org/licenses/LICENSE-2.0.txt 25 | repo 26 | 27 | 28 | 29 | 30 | 31 | lihuagui 32 | 799600902@qq.com 33 | 34 | Owner 35 | Founder 36 | Committer 37 | 38 | https://github.com/liuhuagui 39 | 40 | 41 | 42 | 43 | https://github.com/liuhuagui/smalldoc 44 | scm:git:git@github.com:liuhuagui/smalldoc.git 45 | scm:git:git@github.com:liuhuagui/smalldoc.git 46 | 47 | 48 | 49 | UTF-8 50 | 2.1.7.RELEASE 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-dependencies 58 | ${spring.boot.version} 59 | pom 60 | import 61 | 62 | 63 | com.github.smalldoc 64 | tools 65 | yours 66 | system 67 | ${java.home}/../lib/tools.jar 68 | 69 | 70 | com.alibaba 71 | fastjson 72 | 1.2.61 73 | 74 | 75 | org.slf4j 76 | slf4j-api 77 | 1.7.28 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | maven-compiler-plugin 86 | 3.8.1 87 | 88 | 1.8 89 | 1.8 90 | 91 | 92 | 93 | maven-source-plugin 94 | 3.0.1 95 | 96 | true 97 | 98 | 99 | 100 | attach-sources 101 | 102 | jar-no-fork 103 | 104 | 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-javadoc-plugin 110 | 3.0.0 111 | 112 | none 113 | 114 | 115 | 116 | attach-javadocs 117 | 118 | jar 119 | 120 | 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-gpg-plugin 126 | 1.6 127 | 128 | 129 | sign-artifacts 130 | verify 131 | 132 | sign 133 | 134 | 135 | 136 | --pinentry-mode 137 | loopback 138 | 139 | 140 | 141 | 142 | 143 | 144 | org.sonatype.plugins 145 | nexus-staging-maven-plugin 146 | 1.6.8 147 | true 148 | 149 | ossrh 150 | https://oss.sonatype.org/ 151 | true 152 | 153 | 154 | 155 | 156 | 157 | 158 | GitHub Issue Management 159 | https://github.com/liuhuagui/smalldoc/issues 160 | 161 | 162 | 163 | 164 | ossrh 165 | https://oss.sonatype.org/content/repositories/snapshots 166 | 167 | 168 | ossrh 169 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 170 | 171 | 172 | -------------------------------------------------------------------------------- /smalldoc-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.github.liuhuagui 9 | smalldoc 10 | 2.4 11 | 12 | 13 | smalldoc-core 14 | smalldoc-core 15 | A lightweight RestFul API doc tool developed based on comments and the JavaDoc API in tools.jar. 16 | 17 | 18 | 19 | 20 | com.github.smalldoc 21 | tools 22 | 23 | 24 | com.alibaba 25 | fastjson 26 | 27 | 28 | org.slf4j 29 | slf4j-api 30 | 31 | 32 | javax.servlet 33 | javax.servlet-api 34 | provided 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/DefaultSmallDocletImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.github.liuhuagui.smalldoc.core.constant.Constants; 6 | import com.github.liuhuagui.smalldoc.core.storer.MappingDescStorer; 7 | import com.github.liuhuagui.smalldoc.core.storer.MethodParamsStorer; 8 | import com.github.liuhuagui.smalldoc.core.storer.ParamTagStorer; 9 | import com.github.liuhuagui.smalldoc.util.Assert; 10 | import com.github.liuhuagui.smalldoc.util.ParamFormatUtils; 11 | import com.github.liuhuagui.smalldoc.util.TypeUtils; 12 | import com.github.liuhuagui.smalldoc.util.Utils; 13 | import com.sun.javadoc.*; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.stream.Stream; 18 | 19 | import static com.github.liuhuagui.smalldoc.core.constant.Constants.REQUEST_PARAM; 20 | 21 | 22 | /** 23 | * 自定义Doclet。
24 | * 注意:基于JDK1.8的Javadoc API,该API在JDK1.9被遗弃并由新的API支持,在不久的将来将被移除 25 | * 26 | * @author KaiKang 27 | */ 28 | public class DefaultSmallDocletImpl extends SmallDoclet { 29 | 30 | public DefaultSmallDocletImpl(SmallDocContext smallDocContext) { 31 | super(smallDocContext); 32 | setDocLet(this);//将子类实例挂载到父类静态变量上 33 | } 34 | 35 | /** 36 | * 从{@link RootDoc}中解析文档信息 37 | * 38 | * @param root 39 | */ 40 | @Override 41 | protected boolean process(RootDoc root) { 42 | handleClassDocs(root); 43 | return true; 44 | } 45 | 46 | /** 47 | * 处理所有类 48 | * 49 | * @param root 50 | */ 51 | private void handleClassDocs(RootDoc root) { 52 | ClassDoc[] classes = root.classes(); 53 | String nameRegex = nameRegex(); 54 | for (ClassDoc classDoc : classes) { 55 | String name = classDoc.name(); 56 | if (!name.endsWith(Constants.CONTROLLER) 57 | && (nameRegex == null || !name.matches(nameRegex)))//配置你想要处理的类,提高程序性能 58 | continue; 59 | addClassDoc(classDoc); 60 | } 61 | } 62 | 63 | @Override 64 | protected JSONObject handleClassDoc(ClassDoc classDoc) { 65 | JSONObject classJSON = new JSONObject(); 66 | classJSON.put("name", classDoc.name()); 67 | classJSON.put("comment", classDoc.commentText()); 68 | classJSON.put("authors", getAuthorsInfo(classDoc.tags("@author"))); 69 | //处理Mapping 70 | JSONObject classMappingInfo = getMappingInfo(classDoc); 71 | classJSON.put("mapping", classMappingInfo); 72 | classJSON.put("methods", getMehodDocsInfo(classDoc, classMappingInfo));//由于classJSON除方法信息外有额外信息,所以使用methods统一管理方法信息 73 | return classJSON; 74 | } 75 | 76 | /** 77 | * 从@author标签中查询作者信息 78 | * 79 | * @param authorTags 80 | * @return 81 | */ 82 | private JSONArray getAuthorsInfo(Tag[] authorTags) { 83 | JSONArray authorsJSONArray = new JSONArray(); 84 | for (Tag tag : authorTags) { 85 | authorsJSONArray.add(tag.text()); 86 | } 87 | return authorsJSONArray; 88 | } 89 | 90 | /** 91 | * 查询类中的方法信息 92 | * 93 | * @param classDoc 类文档 94 | * @param classMappingInfo 类的Mapping信息 95 | */ 96 | private JSONArray getMehodDocsInfo(ClassDoc classDoc, JSONObject classMappingInfo) { 97 | JSONArray methodsJSONArray = new JSONArray(); 98 | for (MethodDoc methodDoc : classDoc.methods()) { 99 | if (!methodDoc.isPublic()) 100 | continue; 101 | handleMethodDoc(methodDoc, methodsJSONArray, classMappingInfo); 102 | } 103 | return methodsJSONArray; 104 | } 105 | 106 | /** 107 | * 处理单个方法 108 | * 109 | * @param methodDoc 110 | * @param methodsJSONArray 111 | * @param classMappingInfo 112 | */ 113 | private void handleMethodDoc(MethodDoc methodDoc, JSONArray methodsJSONArray, JSONObject classMappingInfo) { 114 | setCurrentMethodSignature(methodDoc);//在上下文中设置当前解析的方法 115 | JSONObject methodMappingInfo = getMappingInfo(methodDoc); 116 | if (methodMappingInfo.isEmpty())//如果没有Mapping注解,则忽略此方法 117 | return; 118 | JSONObject methodJSON = new JSONObject(); 119 | methodsJSONArray.add(methodJSON); 120 | 121 | methodJSON.put("name", methodDoc.name()); 122 | methodJSON.put("comment", methodDoc.commentText()); 123 | methodJSON.put("authors", getAuthorsInfo(methodDoc.tags("@author"))); 124 | //处理Mapping 125 | methodJSON.put("mapping", handleMethodMappings(classMappingInfo, methodMappingInfo)); 126 | //处理参数 127 | methodJSON.put("params", getParamDocsInfo(methodDoc));//由于methodJSON除参数信息外有额外信息,所以使用params统一管理参数信息 128 | //处理返回值 129 | methodJSON.put("returns", getReturnInfo(methodDoc)); 130 | } 131 | 132 | /** 133 | * 根据Class的Mapping信息处理Method的Mapping 134 | * 135 | * @param classMappingInfo 136 | * @param methodMappingInfo 137 | * @return 138 | */ 139 | private JSONObject handleMethodMappings(JSONObject classMappingInfo, JSONObject methodMappingInfo) { 140 | if (!classMappingInfo.isEmpty()) { 141 | String[] keys = {"method", "consumes", "produces"}; 142 | String[] values; 143 | for (String key : keys) { 144 | if (Utils.isNotEmpty(values = (String[]) classMappingInfo.get(key))) 145 | methodMappingInfo.put(key, values); 146 | } 147 | } 148 | methodMappingInfo.put("path", handleMappingPath((String[]) methodMappingInfo.get("path"), (String[]) classMappingInfo.get("path"))); 149 | return methodMappingInfo; 150 | } 151 | 152 | /** 153 | * 根据Class的Mapping path得到Method最终的Mapping path 154 | * 155 | * @param methodMappingPaths 156 | * @param classMappingPaths 非空数组 157 | * @return 158 | */ 159 | private ArrayList handleMappingPath(String[] methodMappingPaths, String[] classMappingPaths) { 160 | ArrayList finalPaths = new ArrayList<>(); 161 | 162 | boolean methodPathsEmpty = Utils.isEmpty(methodMappingPaths); 163 | boolean classPathsEmpty = Utils.isEmpty(classMappingPaths); 164 | //如果类路径为空,直接使用方法路径(去除首部斜线) 165 | if (classPathsEmpty && !methodPathsEmpty) { 166 | for (String p1 : methodMappingPaths) { 167 | finalPaths.add(Utils.removeHeadSlashIfPresent(p1)); 168 | } 169 | } 170 | 171 | //如果方法路径为空,用类路径作为最终路径(去除首部斜线) 172 | if (!classPathsEmpty && methodPathsEmpty) { 173 | for (String p0 : classMappingPaths) { 174 | finalPaths.add(Utils.removeHeadSlashIfPresent(p0)); 175 | } 176 | } 177 | 178 | //如果类路径和方法路径都不为空 179 | if (!classPathsEmpty && !methodPathsEmpty) { 180 | for (String p0 : classMappingPaths) { 181 | //拼接类路径与方法路径作为最终路径 182 | for (String p1 : methodMappingPaths) { 183 | finalPaths.add(Utils.unitePath(p0, p1)); 184 | } 185 | } 186 | } 187 | return finalPaths; 188 | } 189 | 190 | 191 | private JSONObject getReturnInfo(MethodDoc methodDoc) { 192 | JSONObject returnJSON = new JSONObject(); 193 | Type rtype = methodDoc.returnType(); 194 | returnJSON.put("qtype", TypeUtils.inferBeanName(rtype)); 195 | returnJSON.put("type", TypeUtils.getParamTypeWithDimension(rtype));//获取带维度的返回值 196 | //先处理类型参数,后面再去处理字段(保证TypeVariable的字段被处理) 197 | returnJSON.put("typeArguments", TypeUtils.getTypeArguments(rtype, this)); 198 | 199 | //如果包含返回标签,则解析返回标签的注释 200 | Tag[] returnTags = methodDoc.tags("@return"); 201 | if (Utils.isNotEmpty((returnTags))) 202 | returnJSON.put("comment", returnTags[0].text()); 203 | 204 | //如果不是库类型,保留字段 205 | if (TypeUtils.isEntity(rtype, this)) { 206 | TypeUtils.addBean(rtype, this); 207 | } 208 | return returnJSON; 209 | } 210 | 211 | /** 212 | * 查询方法的所有参数信息
213 | * Note: 如果你的参数在方法注释中不存在对应的@param,那么你的参数将被忽略,这有时可能也成为你所期望的。 214 | * 如果你的方法注释中存在了某个@param,而方法中不存该参数,将抛出断言异常。 215 | * 216 | * @param methodDoc 217 | * @return 218 | */ 219 | private JSONArray getParamDocsInfo(MethodDoc methodDoc) { 220 | //处理参数 221 | JSONArray paramsJSONArray = new JSONArray(); 222 | MethodParamsStorer methodParamsStorer = new MethodParamsStorer(methodDoc); 223 | ParamTag[] paramTags = methodDoc.paramTags(); 224 | 225 | for (ParamTag paramTag : paramTags) { 226 | String paramName = paramTag.parameterName(); 227 | ParamFormatUtils.formatParamDoc(this, methodParamsStorer.getParam(paramName), paramTag, paramsJSONArray); 228 | } 229 | return paramsJSONArray; 230 | } 231 | 232 | 233 | /** 234 | * 查询{@link ProgramElementDoc}的*Mapping注解信息 235 | * 236 | * @param elementDoc 237 | * @return 238 | */ 239 | private JSONObject getMappingInfo(ProgramElementDoc elementDoc) { 240 | JSONObject mapping = new JSONObject(); 241 | for (AnnotationDesc annotationDesc : elementDoc.annotations()) { 242 | MappingDescStorer mappingDescStorer = new MappingDescStorer(annotationDesc); 243 | String name = mappingDescStorer.name(); 244 | if (name.endsWith("Mapping")) 245 | handleMappings(mapping, mappingDescStorer); 246 | } 247 | return mapping; 248 | } 249 | 250 | /** 251 | * 处理 @RequestMapping 信息 252 | * 253 | * @param mapping 254 | * @param mappingDescStorer 255 | */ 256 | private void handleMappings(JSONObject mapping, MappingDescStorer mappingDescStorer) { 257 | mapping.put("method", getHttpMethod(mappingDescStorer)); 258 | mapping.put("consumes", mappingDescStorer.getElementValue("consumes")); 259 | mapping.put("produces", mappingDescStorer.getElementValue("produces")); 260 | mapping.put("path", mappingDescStorer.getElementValue("value")); 261 | } 262 | 263 | /** 264 | * 获取Http Method 265 | * 266 | * @param mappingDescStorer 267 | * @return 268 | */ 269 | private String[] getHttpMethod(MappingDescStorer mappingDescStorer) { 270 | String name = mappingDescStorer.name(); 271 | if (Constants.REQUEST_MAPPING.equals(name)) { 272 | return mappingDescStorer.getElementValue("method"); 273 | } else { 274 | return new String[]{name.substring(0, name.indexOf('M')).toUpperCase()}; 275 | } 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/SmallDocContext.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.github.liuhuagui.smalldoc.core.storer.FieldDocStorer; 5 | import com.github.liuhuagui.smalldoc.properties.SmallDocProperties; 6 | import com.github.liuhuagui.smalldoc.util.Utils; 7 | import com.sun.tools.javadoc.Main; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.File; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 线程不安全,但是文档结构,只在工程启动时生成一次。
18 | * 参考链接:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDIBDDD
19 | * 注意:基于JDK1.8的Javadoc API,该API在JDK1.9被遗弃并由新的API支持,以后将被移除 20 | * 21 | * @author KaiKang 799600902@qq.com 22 | */ 23 | public class SmallDocContext { 24 | private static final Logger log = LoggerFactory.getLogger(SmallDocContext.class); 25 | /** 26 | * 默认的sourcepath 27 | */ 28 | private static final String DEFAULT_SOURCE_PATH = System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "java"; 29 | 30 | /** 31 | * 文档结构,包含工程以外的信息 32 | */ 33 | private final JSONObject docsJSON; 34 | 35 | /** 36 | * 所有包的文档结构。key是包名,value为包内所有满足处理条件的类 37 | */ 38 | private final JSONObject packagesJSON; 39 | 40 | /** 41 | * 装载bean的字段信息 42 | */ 43 | private final Map> beanFieldsMap; 44 | 45 | /** 46 | * 存储实体类型,字段名与字段信息的映射关系 47 | */ 48 | private final Map> entityAndFieldMap; 49 | 50 | /** 51 | * 源文件路径 52 | */ 53 | private List paths; 54 | 55 | /** 56 | * 扫描的包及它的子包 57 | */ 58 | private List packages; 59 | 60 | private SmallDocProperties smallDocProperties; 61 | 62 | /** 63 | * 当前正在解析的方法的签名 64 | */ 65 | private String currentMethodSignature; 66 | 67 | public SmallDocContext(SmallDocProperties smallDocProperties) { 68 | this.docsJSON = new JSONObject(); 69 | this.packagesJSON = new JSONObject(); 70 | this.beanFieldsMap = new HashMap<>(); 71 | this.entityAndFieldMap = new HashMap<>(); 72 | 73 | this.smallDocProperties = smallDocProperties; 74 | this.paths = smallDocProperties.getSourcePaths(); 75 | this.packages = smallDocProperties.getPackages(); 76 | //默认已添加当前项目源码路径——user.dir 77 | this.paths.add(DEFAULT_SOURCE_PATH); 78 | //如果没有指定扫描的包,将扫描源码路径下所有包,建议给出指定包名,提升解析速度 79 | if (packages.isEmpty()) 80 | this.packages.add("/"); 81 | 82 | this.docsJSON.put("projectName", smallDocProperties.getProjectName()); 83 | this.docsJSON.put("jdkVersion", System.getProperty("java.version")); 84 | this.docsJSON.put("osName", System.getProperty("os.name")); 85 | this.docsJSON.put("encoding", System.getProperty("file.encoding")); 86 | this.docsJSON.put("support", "https://github.com/liuhuagui/smalldoc"); 87 | this.docsJSON.put("packages", this.packagesJSON); 88 | this.docsJSON.put("beans", this.beanFieldsMap); 89 | } 90 | 91 | /** 92 | * 执行文档解析 93 | */ 94 | public void execute(SmallDoclet doclet) { 95 | //Separate multiple paths with a semicolon (;) on Windows or a colon (:) on UNIX. 96 | String sourcepath = Utils.join(paths, File.pathSeparator); 97 | docsJSON.put("sourcepath", sourcepath); 98 | log.info("-sourcepath is {}", sourcepath); 99 | 100 | //-subpackages. Arguments are separated by colons on all operating systems. 101 | String subpackages = Utils.join(packages, ":"); 102 | docsJSON.put("subpackages", subpackages); 103 | log.info("-subpackages is {}", subpackages); 104 | 105 | //执行javadoc 命令 106 | Main.execute(new String[]{ 107 | "-doclet", doclet.getClass().getName(), 108 | "-encoding", System.getProperty("file.encoding"), 109 | "-quiet", 110 | "-sourcepath", sourcepath, 111 | "-subpackages", subpackages} 112 | ); 113 | } 114 | 115 | public SmallDocProperties getSmallDocProperties() { 116 | return smallDocProperties; 117 | } 118 | 119 | public JSONObject getDocsJSON() { 120 | return docsJSON; 121 | } 122 | 123 | public JSONObject getPackagesJSON() { 124 | return packagesJSON; 125 | } 126 | 127 | public Map> getBeanFieldsMap() { 128 | return beanFieldsMap; 129 | } 130 | 131 | public Map> getEntityAndFieldMap() { 132 | return entityAndFieldMap; 133 | } 134 | 135 | public List getPaths() { 136 | return paths; 137 | } 138 | 139 | public List getPackages() { 140 | return packages; 141 | } 142 | 143 | public String getCurrentMethodSignature() { 144 | return currentMethodSignature; 145 | } 146 | 147 | public void setCurrentMethodSignature(String currentMethodSignature) { 148 | this.currentMethodSignature = currentMethodSignature; 149 | } 150 | } -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/SmallDoclet.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.github.liuhuagui.smalldoc.core.storer.FieldDocStorer; 6 | import com.github.liuhuagui.smalldoc.util.Assert; 7 | import com.sun.javadoc.*; 8 | import com.sun.tools.javac.util.StringUtils; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import static com.github.liuhuagui.smalldoc.core.constant.Constants.CLASSES; 16 | 17 | public abstract class SmallDoclet extends Doclet { 18 | private SmallDocContext smallDocContext; 19 | 20 | public SmallDoclet(SmallDocContext smallDocContext) { 21 | this.smallDocContext = smallDocContext; 22 | } 23 | 24 | protected static SmallDoclet doclet; 25 | 26 | /** 27 | * Generate documentation in here.This method is required for all doclets. 28 | * 29 | * @param root 30 | * @return 31 | */ 32 | public static boolean start(RootDoc root) { 33 | return doclet.process(root); 34 | } 35 | 36 | /** 37 | * 返回 {@link LanguageVersion#JAVA_1_5} 避免获取结构信息时泛型擦除(即使Compiler Tree API已经处理了泛型) 38 | * 39 | * @return 40 | */ 41 | public static LanguageVersion languageVersion() { 42 | return LanguageVersion.JAVA_1_5; 43 | } 44 | 45 | /** 46 | * process {@link RootDoc} root 47 | * 48 | * @param root 49 | * @return 50 | */ 51 | protected abstract boolean process(RootDoc root); 52 | 53 | protected void setDocLet(SmallDoclet doclet) { 54 | SmallDoclet.doclet = doclet; 55 | } 56 | 57 | protected abstract JSONObject handleClassDoc(ClassDoc classDoc); 58 | 59 | protected void addClassDoc(ClassDoc classDoc) { 60 | PackageDoc packageDoc = classDoc.containingPackage(); 61 | String packageName = packageDoc.toString(); 62 | 63 | JSONObject packagesJSON = smallDocContext.getPackagesJSON(); 64 | JSONObject packageJSON = packagesJSON.getJSONObject(packageName); 65 | if (packageJSON != null) { 66 | JSONArray classes = packageJSON.getJSONArray(CLASSES); 67 | classes.add(handleClassDoc(classDoc)); 68 | } else { 69 | JSONArray classes = new JSONArray(); 70 | classes.add(handleClassDoc(classDoc)); 71 | packageJSON = new JSONObject(); 72 | packageJSON.put(CLASSES, classes); 73 | 74 | putPackageComment(packageDoc, packageName, packageJSON); 75 | putPackageUrl(packageDoc, packageJSON); 76 | 77 | packagesJSON.put(packageName, packageJSON); 78 | } 79 | } 80 | 81 | private void putPackageUrl(PackageDoc packageDoc, JSONObject packageJSON) { 82 | Tag[] urls = packageDoc.tags("url"); 83 | if (urls != null && urls.length > 0) { 84 | String url = urls[0].text(); 85 | packageJSON.put("url", url.endsWith("/") ? url : url + "/"); 86 | } 87 | } 88 | 89 | private void putPackageComment(PackageDoc packageDoc, String packageName, JSONObject packageJSON) { 90 | String comment = packageDoc.commentText(); 91 | if (comment != null && comment.trim().length() != 0) 92 | packageJSON.put("comment", comment); 93 | else 94 | packageJSON.put("comment", packageName); 95 | } 96 | 97 | public Map> getBeanFieldsMap() { 98 | return smallDocContext.getBeanFieldsMap(); 99 | } 100 | 101 | public Map> getEntityAndFieldMap() { 102 | return smallDocContext.getEntityAndFieldMap(); 103 | } 104 | 105 | public Map getNameAndFieldMap(String beanName) { 106 | Map nameAndFieldMap = smallDocContext.getEntityAndFieldMap().get(beanName); 107 | Assert.notNull(nameAndFieldMap, "The fields information of %s does not exist. Check if the source configuration is correct.", beanName); 108 | return nameAndFieldMap; 109 | } 110 | 111 | public List getLibraryTypePackages() { 112 | return smallDocContext.getSmallDocProperties().getLibraryTypePackages(); 113 | } 114 | 115 | public List getLibraryTypeQualifiedNames() { 116 | return smallDocContext.getSmallDocProperties().getLibraryTypeQualifiedNames(); 117 | } 118 | 119 | public String nameRegex() { 120 | return smallDocContext.getSmallDocProperties().getNameRegex(); 121 | } 122 | 123 | public String getCurrentMethodSignature() { 124 | return smallDocContext.getCurrentMethodSignature(); 125 | } 126 | 127 | /** 128 | * Set the current handling method in the context. 129 | * 130 | * @param currentMethodDoc 131 | */ 132 | public void setCurrentMethodSignature(MethodDoc currentMethodDoc) { 133 | smallDocContext.setCurrentMethodSignature(currentMethodDoc.qualifiedName() + currentMethodDoc.flatSignature()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/constant/Constants.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core.constant; 2 | 3 | /** 4 | * 相关常量 5 | * 6 | * @author KaiKang 799600902@qq.com 7 | */ 8 | public class Constants { 9 | public static final String REQUEST_MAPPING = "RequestMapping"; 10 | 11 | public static final String MAPPING = "Mapping"; 12 | 13 | public static final String CONTROLLER = "Controller"; 14 | 15 | public static final String CLASSES = "classes"; 16 | 17 | public static final String REQUEST_PARAM = "RequestParam"; 18 | } 19 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/storer/FieldDocStorer.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core.storer; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 字段文档存储器 7 | * 8 | * @author KaiKang 799600902@qq.com 9 | */ 10 | public class FieldDocStorer { 11 | /** 12 | * 字段名 13 | */ 14 | private String name; 15 | 16 | /** 17 | * 字段类型的完全限定名 18 | */ 19 | private String qtype; 20 | 21 | private String type; 22 | 23 | private String comment; 24 | 25 | private boolean entity; 26 | 27 | private boolean collection; 28 | 29 | private boolean array; 30 | 31 | private boolean file; 32 | 33 | private boolean declared; 34 | 35 | private String eleName; 36 | 37 | private List typeArguments; 38 | 39 | public boolean isFile() { 40 | return file; 41 | } 42 | 43 | public void setFile(boolean file) { 44 | this.file = file; 45 | } 46 | 47 | public boolean isDeclared() { 48 | return declared; 49 | } 50 | 51 | public void setDeclared(boolean declared) { 52 | this.declared = declared; 53 | } 54 | 55 | public String getEleName() { 56 | return eleName; 57 | } 58 | 59 | public void setEleName(String eleName) { 60 | this.eleName = eleName; 61 | } 62 | 63 | public boolean isArray() { 64 | return array; 65 | } 66 | 67 | public void setArray(boolean array) { 68 | this.array = array; 69 | } 70 | 71 | public boolean isCollection() { 72 | return collection; 73 | } 74 | 75 | public void setCollection(boolean collection) { 76 | this.collection = collection; 77 | } 78 | 79 | public boolean isEntity() { 80 | return entity; 81 | } 82 | 83 | public void setEntity(boolean entity) { 84 | this.entity = entity; 85 | } 86 | 87 | public String getName() { 88 | return name; 89 | } 90 | 91 | public void setName(String name) { 92 | this.name = name; 93 | } 94 | 95 | public String getQtype() { 96 | return qtype; 97 | } 98 | 99 | public void setQtype(String qtype) { 100 | this.qtype = qtype; 101 | } 102 | 103 | public String getType() { 104 | return type; 105 | } 106 | 107 | public void setType(String type) { 108 | this.type = type; 109 | } 110 | 111 | public String getComment() { 112 | return comment; 113 | } 114 | 115 | public void setComment(String comment) { 116 | this.comment = comment; 117 | } 118 | 119 | public List getTypeArguments() { 120 | return typeArguments; 121 | } 122 | 123 | public void setTypeArguments(List typeArguments) { 124 | this.typeArguments = typeArguments; 125 | } 126 | 127 | public ParamTagStorer build(boolean required) { 128 | ParamTagStorer paramTagStorer0 = new ParamTagStorer(this.getName(), required); 129 | fill(this, paramTagStorer0); 130 | return paramTagStorer0; 131 | } 132 | 133 | public static void fill(FieldDocStorer fieldDocStorer, ParamTagStorer paramTagStorer0) { 134 | paramTagStorer0.setType(fieldDocStorer.getType()); 135 | paramTagStorer0.setTypeArguments(fieldDocStorer.getTypeArguments()); 136 | paramTagStorer0.setComment(fieldDocStorer.getComment()); 137 | paramTagStorer0.setFile(fieldDocStorer.isFile()); 138 | paramTagStorer0.setDimension(fieldDocStorer.isCollection() || fieldDocStorer.isArray()); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/storer/MappingDescStorer.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core.storer; 2 | 3 | import com.sun.javadoc.AnnotationDesc; 4 | import com.sun.javadoc.AnnotationValue; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Mapping存储器,方便取回关于Mapping的有效信息。 11 | * 12 | * @author KaiKang 799600902@qq.com 13 | */ 14 | public class MappingDescStorer { 15 | public static final String[] EMPTY_STRING_ARRAY = new String[0]; 16 | private String name; 17 | private Map valuesMap = new HashMap<>(); 18 | 19 | public MappingDescStorer(AnnotationDesc annotationDesc) { 20 | this.name = annotationDesc.annotationType().name(); 21 | initValues(annotationDesc); 22 | } 23 | 24 | private void initValues(AnnotationDesc annotationDesc) { 25 | for (AnnotationDesc.ElementValuePair elementValuePair : 26 | annotationDesc.elementValues()) { 27 | valuesMap.put(elementValuePair.element().name(), analyseValue(elementValuePair)); 28 | } 29 | } 30 | 31 | 32 | /** 33 | * 解析value值,去除首尾空白字符 34 | * 35 | * @param elementValuePair 36 | * @return 37 | */ 38 | private String[] analyseValue(AnnotationDesc.ElementValuePair elementValuePair) { 39 | String name = elementValuePair.element().name(); 40 | Object value = elementValuePair.value().value(); 41 | if (!value.getClass().isArray()) 42 | return new String[]{value.toString().trim()};//去除首尾空白字符 43 | AnnotationValue[] values = (AnnotationValue[]) value; 44 | String[] strings = new String[values.length]; 45 | int i = 0; 46 | for (AnnotationValue v : values) { 47 | String s = v.value().toString().trim();//去除首尾空白字符 48 | if (name.equals("method")) { 49 | strings[i++] = s.substring(s.lastIndexOf(".") + 1, s.length()); 50 | } else { 51 | strings[i++] = s; 52 | } 53 | } 54 | return strings; 55 | } 56 | 57 | public String name() { 58 | return name; 59 | } 60 | 61 | public String[] getElementValue(String name) { 62 | String[] value = valuesMap.get(name); 63 | if (value == null) { 64 | //value为空时,取path 65 | if (name.equals("value")) 66 | value = valuesMap.get("path"); 67 | //path为空时,取value 68 | if (name.equals("path")) 69 | value = valuesMap.get("value"); 70 | } 71 | return value == null ? EMPTY_STRING_ARRAY : value;//为null时返回空字符串数组 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/storer/MethodParamsStorer.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core.storer; 2 | 3 | import com.github.liuhuagui.smalldoc.util.Assert; 4 | import com.sun.javadoc.MethodDoc; 5 | import com.sun.javadoc.Parameter; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * 方法参数存储器 12 | * 13 | * @author KaiKang 799600902@qq.com 14 | */ 15 | public class MethodParamsStorer { 16 | private Map paramsMap = new HashMap<>(); 17 | private MethodDoc methodDoc; 18 | 19 | public MethodParamsStorer(MethodDoc methodDoc) { 20 | this.methodDoc = methodDoc; 21 | initParamsMap(methodDoc); 22 | } 23 | 24 | private void initParamsMap(MethodDoc methodDoc) { 25 | for (Parameter parameterDoc : methodDoc.parameters()) { 26 | paramsMap.put(parameterDoc.name(), parameterDoc); 27 | } 28 | } 29 | 30 | /** 31 | * @param paramName 32 | * @return 33 | */ 34 | public Parameter getParam(String paramName) { 35 | Parameter parameter = paramsMap.get(paramName); 36 | Assert.notNull(parameter, "Method: %s, This param %s doesn't exist.", methodDoc.qualifiedName() + methodDoc.flatSignature(), paramName); 37 | return parameter; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/core/storer/ParamTagStorer.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.core.storer; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 参数信息存储器 7 | * 8 | * @author KaiKang 799600902@qq.com 9 | */ 10 | public class ParamTagStorer { 11 | 12 | private String name; 13 | 14 | private String type; 15 | 16 | private List typeArguments; 17 | 18 | private String comment; 19 | /** 20 | * 参数是否必须 21 | */ 22 | private boolean required; 23 | 24 | private String example; 25 | 26 | /** 27 | * 是否是文件类型 28 | */ 29 | private boolean file; 30 | 31 | /** 32 | * 是否是多维类型,集合或数组 33 | */ 34 | private boolean dimension; 35 | 36 | private List fieldParamStorers; 37 | 38 | public ParamTagStorer(String name) { 39 | this.name = name; 40 | } 41 | 42 | public ParamTagStorer(String name, boolean required) { 43 | this.name = name; 44 | this.required = required; 45 | } 46 | 47 | public boolean isFile() { 48 | return file; 49 | } 50 | 51 | public void setFile(boolean file) { 52 | this.file = file; 53 | } 54 | 55 | public boolean isDimension() { 56 | return dimension; 57 | } 58 | 59 | public void setDimension(boolean dimension) { 60 | this.dimension = dimension; 61 | } 62 | 63 | public String getExample() { 64 | return example; 65 | } 66 | 67 | public void setExample(String example) { 68 | this.example = example; 69 | } 70 | 71 | public String getName() { 72 | return name; 73 | } 74 | 75 | public String getComment() { 76 | return comment; 77 | } 78 | 79 | public boolean isRequired() { 80 | return required; 81 | } 82 | 83 | public void setName(String name) { 84 | this.name = name; 85 | } 86 | 87 | public void setComment(String comment) { 88 | this.comment = comment; 89 | } 90 | 91 | public void setRequired(boolean required) { 92 | this.required = required; 93 | } 94 | 95 | public String getType() { 96 | return type; 97 | } 98 | 99 | public void setType(String type) { 100 | this.type = type; 101 | } 102 | 103 | public List getTypeArguments() { 104 | return typeArguments; 105 | } 106 | 107 | public void setTypeArguments(List typeArguments) { 108 | this.typeArguments = typeArguments; 109 | } 110 | 111 | public List getFieldParamStorers() { 112 | return fieldParamStorers; 113 | } 114 | 115 | public void setFieldParamStorers(List fieldParamStorers) { 116 | this.fieldParamStorers = fieldParamStorers; 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/properties/SmallDocProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.properties; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class SmallDocProperties { 8 | private boolean enabled; 9 | 10 | /** 11 | * A regular expression matched the name of classes which you wander to parse to RESTFul API Doc, 12 | * which will improve program performance. 13 | */ 14 | private String nameRegex; 15 | 16 | private String urlPattern; 17 | /** 18 | * project name 19 | */ 20 | private String projectName; 21 | 22 | /** 23 | * source code absolute paths 24 | * 25 | * @see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDEHCDG 26 | */ 27 | private List sourcePaths = new ArrayList<>(); 28 | 29 | /** 30 | * Generates documentation from source files in the specified packages and recursively in their subpackages. 31 | * 32 | * @see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDJEDJI 33 | */ 34 | private List packages = new ArrayList<>(); 35 | 36 | private List libraryTypePackages; 37 | 38 | private List libraryTypeQualifiedNames; 39 | 40 | public boolean isEnabled() { 41 | return enabled; 42 | } 43 | 44 | public void setEnabled(boolean enabled) { 45 | this.enabled = enabled; 46 | } 47 | 48 | public String getNameRegex() { 49 | return nameRegex; 50 | } 51 | 52 | public void setNameRegex(String nameRegex) { 53 | this.nameRegex = nameRegex; 54 | } 55 | 56 | public String getUrlPattern() { 57 | return urlPattern; 58 | } 59 | 60 | public void setUrlPattern(String urlPattern) { 61 | this.urlPattern = urlPattern; 62 | } 63 | 64 | public String getProjectName() { 65 | return projectName; 66 | } 67 | 68 | public void setProjectName(String projectName) { 69 | this.projectName = projectName; 70 | } 71 | 72 | public List getSourcePaths() { 73 | return sourcePaths; 74 | } 75 | 76 | public void setSourcePaths(List sourcePaths) { 77 | this.sourcePaths = sourcePaths; 78 | } 79 | 80 | public List getPackages() { 81 | return packages; 82 | } 83 | 84 | public void setPackages(List packages) { 85 | this.packages = packages; 86 | } 87 | 88 | public List getLibraryTypePackages() { 89 | return libraryTypePackages; 90 | } 91 | 92 | public void setLibraryTypePackages(List libraryTypePackages) { 93 | this.libraryTypePackages = libraryTypePackages; 94 | } 95 | 96 | public List getLibraryTypeQualifiedNames() { 97 | return libraryTypeQualifiedNames; 98 | } 99 | 100 | public void setLibraryTypeQualifiedNames(List libraryTypeQualifiedNames) { 101 | this.libraryTypeQualifiedNames = libraryTypeQualifiedNames; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/util/Assert.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.util; 2 | 3 | /** 4 | * @author KaiKang 799600902@qq.com 5 | */ 6 | public class Assert { 7 | /** 8 | * 如果expected不为true,抛出异常 9 | * 10 | * @param expected 11 | * @param message 12 | * @param args 13 | * @see String#format(String, Object...) 14 | */ 15 | public static void check(boolean expected, String message, Object... args) { 16 | if (!expected) 17 | throw new AssertException(message, args); 18 | } 19 | 20 | /** 21 | * 如果notExpected为true,抛出异常 22 | * 23 | * @param notExpected 24 | * @param message 25 | * @param args 26 | * @see String#format(String, Object...) 27 | */ 28 | public static void checkNot(boolean notExpected, String message, Object... args) { 29 | if (notExpected) 30 | throw new AssertException(message, args); 31 | } 32 | 33 | /** 34 | * 如果target为null,抛出异常 35 | * 36 | * @param target 37 | * @param message 38 | * @param args 39 | * @see String#format(String, Object...) 40 | */ 41 | public static void notNull(Object target, String message, Object... args) { 42 | if (target == null) 43 | throw new AssertException(message, args); 44 | } 45 | 46 | 47 | public static class AssertException extends RuntimeException { 48 | /** 49 | * @param message 50 | * @param args 51 | * @see String#format(String, Object...) 52 | */ 53 | public AssertException(String message, Object... args) { 54 | super(args.length == 0 ? message : String.format(message, args)); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/util/ParamFormatUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.util; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.github.liuhuagui.smalldoc.core.DefaultSmallDocletImpl; 5 | import com.github.liuhuagui.smalldoc.core.storer.FieldDocStorer; 6 | import com.github.liuhuagui.smalldoc.core.storer.ParamTagStorer; 7 | import com.sun.javadoc.AnnotationDesc; 8 | import com.sun.javadoc.ParamTag; 9 | import com.sun.javadoc.Parameter; 10 | import com.sun.javadoc.Type; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.time.LocalDateTime; 15 | import java.time.format.DateTimeFormatter; 16 | import java.util.*; 17 | import java.util.function.Function; 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | import java.util.stream.Collectors; 21 | import java.util.stream.Stream; 22 | 23 | import static com.github.liuhuagui.smalldoc.core.constant.Constants.REQUEST_PARAM; 24 | 25 | /** 26 | * 参数格式化工具 —— 是参数处理的核心类。 27 | * 28 | * @author KaiKang 799600902@qq.com 29 | */ 30 | public class ParamFormatUtils { 31 | private static Logger log = LoggerFactory.getLogger(ParamFormatUtils.class); 32 | 33 | public static final String GENERAL_PARAM_TOKEN = "@*"; 34 | 35 | public static final String ENTITY_PARAM_TOKEN_REGEX = "([\\s\\S]*)@\\{(.*)}"; 36 | 37 | public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 38 | 39 | /** 40 | * 处理参数
41 | * Note:如果你将‘实体数组’和‘集合类型’,直接作为API method参数,那么将抛出断言异常。因为Spring MVC并不支持这种方式的数据绑定 42 | * 例如: 43 | *
 44 |      * ArrayList<String> params0;
 45 |      * ArrayList<实体类> params1;
 46 |      * List<String> params2;
 47 |      * List<实体类> params3;
 48 |      * Set<String> params4;
 49 |      * Set<实体类> params5;
 50 |      * 实体类[] params6;
 51 |      * 
52 | * 但是如果你将这些类型的参数作为字段封装到某个实体类中,那么Spring MVC将会正确处理他们 53 | *
 54 |      * public class ComplexEntityClass{
 55 |      * private ArrayList<String> params0;
 56 |      * private ArrayList<实体类> params1;
 57 |      * private List<String> params2;
 58 |      * private List<实体类> params3;
 59 |      * private Set<String> params4;
 60 |      * private Set<实体类> params5;
 61 |      * private 实体类[] params6;
 62 |      * }
 63 |      * 
64 | *
 65 |      * \@RequestMapping("action")
 66 |      * public Result test(ComplexEntityClass complexEntity) {
 67 |      * //logic
 68 |      * }
 69 |      * 
70 | * 特殊的,如果参数是基本类型或String类型的‘集合类型’,可以使用org.springframework.web.bind.annotation.RequestParam使之生效
71 | * 例如: 72 | *
 73 |      * \@RequestParam ArrayList<String> params0;
 74 |      * \@RequestParam List<String> params2;
 75 |      * \@RequestParam Set<String> params4;
 76 |      * 
77 | * 78 | * @param parameterDoc 79 | * @param paramTag 80 | * @param paramsJSONArray 81 | */ 82 | public static void formatParamDoc(DefaultSmallDocletImpl doclet, Parameter parameterDoc, ParamTag paramTag, JSONArray paramsJSONArray) { 83 | Type ptype = parameterDoc.type(); 84 | addParamBean(doclet, ptype); 85 | //注意:如果数组类型ArrayTypeImpl中的元素类型是实体类型,那么该数组类型也是实体类型。 86 | if (TypeUtils.isEntity(ptype, doclet)) { 87 | formatEntityParamDoc(doclet, paramTag, paramsJSONArray, ptype); 88 | } else { 89 | formatNoEntityParamDoc(doclet, paramTag, paramsJSONArray, ptype, parameterDoc.annotations()); 90 | } 91 | } 92 | 93 | /** 94 | * 不管参数是不是实体类型,一定要解析它的泛型参数 95 | * 96 | * @param doclet 97 | * @param type 98 | */ 99 | private static void addParamBean(DefaultSmallDocletImpl doclet, Type type) { 100 | //先处理类型参数,后面再处理字段(保证TypeVariable的字段被处理) 101 | TypeUtils.getTypeArguments(type, doclet); 102 | if (TypeUtils.isEntity(type, doclet)) 103 | TypeUtils.addBean(type, doclet); 104 | } 105 | 106 | private static void formatNoEntityParamDoc(DefaultSmallDocletImpl doclet, ParamTag paramTag, JSONArray paramsJSONArray, Type ptype, AnnotationDesc[] annotations) { 107 | boolean file = TypeUtils.isFile(ptype); 108 | boolean dimension = TypeUtils.isArray(ptype); 109 | if (TypeUtils.isCollection(ptype)) { 110 | Type typeArgument = ptype.asParameterizedType().typeArguments()[0]; 111 | String currentMethodSignature = doclet.getCurrentMethodSignature(); 112 | String parameterName = paramTag.parameterName(); 113 | Assert.checkNot(TypeUtils.isEntity(typeArgument, doclet), "Method: %s, Param: %s, Spring MVC does not support collections parameter of entity type.", currentMethodSignature, parameterName); 114 | long count = Stream.of(annotations) 115 | .filter(annotationDesc -> annotationDesc.annotationType().simpleTypeName().equals(REQUEST_PARAM)) 116 | .count(); 117 | Assert.check(count == 1, "Method: %s, Param: %s, Spring MVC need @RequestParam annotation to support collections parameter of base or String type.", currentMethodSignature, parameterName); 118 | file = TypeUtils.isFile(typeArgument); 119 | dimension = true; 120 | } 121 | 122 | extractGeneralParamTag(doclet, paramTag, ptype, file, dimension) 123 | .map(ParamFormatUtils.deriveExample(LocalDateTime.now())) 124 | .ifPresent(paramsJSONArray::add); 125 | } 126 | 127 | private static void formatEntityParamDoc(DefaultSmallDocletImpl doclet, ParamTag paramTag, JSONArray paramsJSONArray, Type ptype) { 128 | boolean array = TypeUtils.isArray(ptype); 129 | Assert.checkNot(array, "Method: %s, Param: %s, Spring MVC does not support entity arrays parameter.", doclet.getCurrentMethodSignature(), paramTag.parameterName()); 130 | //注意:如果是数组类型ArrayTypeImpl,解析字段时默认跳过数组维度,默认得到的即是元素类型字段的映射。 131 | extractEntityParamTag(doclet, paramTag, ptype) 132 | .ifPresent(pStorer -> 133 | paramsJSONArray.addAll(pStorer.getFieldParamStorers()) 134 | ); 135 | } 136 | 137 | /** 138 | * 抽取普通类型参数ParamTag信息并存储 139 | * 140 | * @param doclet 141 | * @param paramTag 142 | * @param type 143 | * @param file 是否是文件 144 | * @param dimension 是否是数组或集合 145 | * @return 146 | */ 147 | private static Optional extractGeneralParamTag(DefaultSmallDocletImpl doclet, ParamTag paramTag, Type type, boolean file, boolean dimension) { 148 | ParamTagStorer paramTagStorer = new ParamTagStorer(paramTag.parameterName()); 149 | paramTagStorer.setType(TypeUtils.getParamTypeWithDimension(type)); 150 | paramTagStorer.setTypeArguments(TypeUtils.getTypeArguments(type, doclet)); 151 | paramTagStorer.setFile(file); 152 | paramTagStorer.setDimension(dimension); 153 | 154 | String comment = paramTag.parameterComment(); 155 | if (comment.endsWith(GENERAL_PARAM_TOKEN)) { 156 | paramTagStorer.setRequired(true); 157 | paramTagStorer.setComment(comment.substring(0, comment.lastIndexOf(GENERAL_PARAM_TOKEN))); 158 | } else { 159 | paramTagStorer.setRequired(false); 160 | paramTagStorer.setComment(comment); 161 | } 162 | return Optional.of(paramTagStorer); 163 | } 164 | 165 | /** 166 | * 抽取实体类型参数ParamTag信息并存储。
167 | * Note:如果没有合适的字段标记@{(*|**|f1[*])[,[-]f2[*]...]},将打印警告并返回null
168 | *
    169 | *
  1. 字段之间用“,”隔开。
  2. 170 | *
  3. 字段后加“*”表示必须,无“*”表示否。
  4. 171 | *
  5. “*”单独出现表示展示所有非继承非实体字段,且是不必须。
  6. 172 | *
  7. “**”单独出现表示展示所有非继承非实体字段,且必须。
  8. 173 | *
  9. 在花括号中,“*”和“**”不能同时出现,且必须第一个出现。
  10. 174 | *
  11. 在“*”和“**”后出现的字段 175 | *
      176 | *
    1. 如果以“-”开头,并且包含在非继承非实体字段内,则该字段不做展示。
    2. 177 | *
    3. 如果以“-”开头,但是不包含在非继承非实体字段内,则断言异常发生。
    4. 178 | *
    5. 如果没有以“-”开头,并且包含在非继承非实体字段内则复写它,通常用来改变必须性。
    6. 179 | *
    7. 如果没有以“-”开头,并且没有包含在非继承非实体字段内同时包含在合法字段内,则增加显示该字段。
    8. 180 | *
    9. 如果没有以“-”开头,并且没有包含在非继承非实体字段内但并不包含在合法字段内,则断言异常发生。
    10. 181 | *
    182 | *
  12. 183 | *
  13. 集合或实体数组的字段会自动增加“[]”后缀,表示要采用数组方式提交
  14. 184 | *
185 | * 186 | * @param doclet 187 | * @param paramTag 188 | * @param type 189 | * @return 190 | */ 191 | private static Optional extractEntityParamTag(DefaultSmallDocletImpl doclet, ParamTag paramTag, Type type) { 192 | Pattern compile = Pattern.compile(ENTITY_PARAM_TOKEN_REGEX); 193 | Matcher matcher = compile.matcher(paramTag.parameterComment()); 194 | if (matcher.find()) { 195 | String beanName = TypeUtils.inferBeanName(type); 196 | Map nameAndFieldMap0 = doclet.getNameAndFieldMap(beanName); 197 | Map paramTagStorerTempCache = new HashMap<>(); 198 | 199 | String formatComment = matcher.group(2); 200 | if (formatComment.equals("*")) { 201 | paramTagStorerTempCache = createParamTagStorerTempCache(nameAndFieldMap0, false); 202 | } else if (formatComment.equals("**")) { 203 | paramTagStorerTempCache = createParamTagStorerTempCache(nameAndFieldMap0, true); 204 | } else { 205 | paramTagStorerTempCache = createParamTagStorerTempCache(doclet, paramTag, beanName, nameAndFieldMap0, paramTagStorerTempCache, formatComment); 206 | } 207 | 208 | return createFinalParamTagStorer(paramTag, matcher, paramTagStorerTempCache); 209 | } 210 | log.warn("Method: {}, The param tag @{(*|**|f1[*])[,[-]f2[*]...]} is expected when parameter {} is an entity type.", doclet.getCurrentMethodSignature(), paramTag.parameterName()); 211 | return Optional.empty(); 212 | } 213 | 214 | private static Optional createFinalParamTagStorer(ParamTag paramTag, Matcher matcher, Map paramTagStorerTempCache) { 215 | if (paramTagStorerTempCache.isEmpty()) 216 | return Optional.empty(); 217 | 218 | final LocalDateTime now = LocalDateTime.now(); 219 | List fieldParamStorers = paramTagStorerTempCache.entrySet().stream().map( 220 | entry -> deriveExample(now).apply(entry.getValue()) 221 | ).collect(Collectors.toList()); 222 | 223 | ParamTagStorer paramTagStorer = new ParamTagStorer(paramTag.parameterName()); 224 | paramTagStorer.setComment(matcher.group(1)); 225 | paramTagStorer.setFieldParamStorers(fieldParamStorers); 226 | return Optional.of(paramTagStorer); 227 | } 228 | 229 | /** 230 | * 高阶函数 —— 返回函数接口的函数(类似于Python中的装饰器函数) 231 | * 232 | * @param now 233 | * @return 234 | */ 235 | public static Function deriveExample(LocalDateTime now) { 236 | return paramTagStorer -> { 237 | //默认示例值 238 | String example = "3"; 239 | //先以类型推断示例值 240 | example = deriveExampleFromParamType(paramTagStorer, example, now); 241 | //特殊的,如果是文件类型,则不进行命名推断 242 | if (example != null) 243 | //用命名推断示例值,命名推断会覆盖类型推断 244 | example = deriveExampleFromParamName(paramTagStorer.getName(), example, now); 245 | paramTagStorer.setExample(example); 246 | return paramTagStorer; 247 | }; 248 | } 249 | 250 | private static String deriveExampleFromParamType(ParamTagStorer paramTagStorer, String example, LocalDateTime now) { 251 | String type = paramTagStorer.getType(); 252 | example = deriveExampleFromType(type, example, now); 253 | if (TypeUtils.isCollection(type)) 254 | example = deriveExampleFromType(paramTagStorer.getTypeArguments().get(0).getType(), example, now); 255 | return example; 256 | } 257 | 258 | 259 | private static String deriveExampleFromType(String type, String example, LocalDateTime now) { 260 | //如果是文件类型,做特殊标记 261 | if (TypeUtils.isFile(type)) 262 | return null; 263 | String type0 = type.toLowerCase(); 264 | if (type0.contains("time")) { 265 | example = now.format(FORMATTER); 266 | } else if (type0.contains("date")) { 267 | example = now.toLocalDate().toString(); 268 | } else if (type0.equals("string")) { 269 | // example = "3"; 270 | } else if (type0.equals("int") || type0.equals("integer")) { 271 | // example = "3"; 272 | } else if (type0.equals("long")) { 273 | example = "333333333"; 274 | } else if (type0.equals("float")) { 275 | example = "3.0"; 276 | } else if (type0.equals("double")) { 277 | example = "3.00"; 278 | } else if (type0.equals("boolean")) { 279 | example = "true"; 280 | } else if (type0.equals("short")) { 281 | // example = "3"; 282 | } else if (type0.equals("byte")) { 283 | // example = "3"; 284 | } else if (type0.equals("char") || type0.equals("character")) { 285 | // example = "3"; 286 | } 287 | return example; 288 | } 289 | 290 | private static String deriveExampleFromParamName(String paramName, String example, LocalDateTime now) { 291 | String name = paramName.toLowerCase(); 292 | if (name.contains("time")) { 293 | example = now.format(FORMATTER); 294 | } else if (name.contains("date")) { 295 | example = now.toLocalDate().toString(); 296 | } else if (name.contains("phone")) { 297 | example = "13271620008"; 298 | } else if (name.contains("email")) { 299 | example = "799600902@qq.com"; 300 | } 301 | return example; 302 | } 303 | 304 | private static Map createParamTagStorerTempCache(DefaultSmallDocletImpl doclet, ParamTag paramTag, String beanName, Map nameAndFieldMap0, Map paramTagStorerTempCache, String formatComment) { 305 | String[] formatFieldNames = formatComment.split(","); 306 | boolean allDeclaredLibraryField = false; 307 | int startIndex = 0; 308 | boolean all = formatComment.startsWith("*,"); 309 | boolean allRequired = formatComment.startsWith("**,"); 310 | 311 | if (all || allRequired) { 312 | allDeclaredLibraryField = true; 313 | startIndex = 1; 314 | paramTagStorerTempCache = createParamTagStorerTempCache(nameAndFieldMap0, allRequired); 315 | } 316 | 317 | for (int i = startIndex; i < formatFieldNames.length; i++) { 318 | String formatFieldName = formatFieldNames[i]; 319 | String currentMethodSignature = doclet.getCurrentMethodSignature(); 320 | String parameterName = paramTag.parameterName(); 321 | Assert.checkNot(formatFieldName.equals("*") || formatFieldName.equals("**"), "Method: %s, Param: %s, FormatFieldName: %s, *|** can only appear as the first formatFieldName.", currentMethodSignature, parameterName, formatFieldName); 322 | boolean shouldBeEntity = formatFieldName.contains("."); 323 | boolean deleteDeclaredLibraryField = formatFieldName.startsWith("-"); 324 | 325 | //'-' and '.' can't appear at the same time. 326 | Assert.checkNot(shouldBeEntity && deleteDeclaredLibraryField, "Method: %s, Param: %s, FormatFieldName: %s, '-' can only appear before the DeclaredLibraryField, while '.' represent Entity field, Please check your formatComment.", currentMethodSignature, parameterName, formatFieldName); 327 | 328 | //if '-' appear. 329 | if (deleteDeclaredLibraryField) { 330 | ActualField actualField = extractActualField(formatFieldName, 1); 331 | Assert.check(allDeclaredLibraryField, "Method: %s, Param: %s, FormatFieldName: %s, Only when *|** appears as the first formatFieldName, the remaining formatFieldNames can start with '-'.", currentMethodSignature, parameterName, formatFieldName); 332 | String name = actualField.getName(); 333 | FieldDocStorer fieldDocStorer = nameAndFieldMap0.get(name); 334 | Assert.notNull(fieldDocStorer, "Method: %s, Param: %s, '-' can only appear before the DeclaredLibraryField, but field %s does not exist in %s.", currentMethodSignature, parameterName, name, beanName); 335 | Assert.check(fieldDocStorer.isDeclared(), "Method: %s, Param: %s, '-' can only appear before the DeclaredLibraryField, but field %s is inherited.", currentMethodSignature, parameterName, name); 336 | Assert.checkNot(fieldDocStorer.isEntity(), "Method: %s, Param: %s, '-' can only appear before the DeclaredLibraryField, but field %s is Entity.", currentMethodSignature, parameterName, name); 337 | if (paramTagStorerTempCache.get(name) != null) 338 | paramTagStorerTempCache.remove(name); 339 | } 340 | //if '.' appear. 341 | if (shouldBeEntity) { 342 | ActualField actualField = extractActualField(formatFieldName, 0); 343 | ParamTagStorer fieldParamTagStorer = extractFieldParamTagStorer(doclet, beanName, actualField.getName(), actualField.isRequired()); 344 | paramTagStorerTempCache.put(fieldParamTagStorer.getName(), fieldParamTagStorer); 345 | } 346 | 347 | //If neither '-' nor '.' appears. 348 | if (!shouldBeEntity && !deleteDeclaredLibraryField) { 349 | ActualField actualField = extractActualField(formatFieldName, 0); 350 | String name = actualField.getName(); 351 | FieldDocStorer fieldDocStorer = nameAndFieldMap0.get(name); 352 | Assert.notNull(fieldDocStorer, "Method: %s, Param: %s, This field %s does not exist in %s.", currentMethodSignature, parameterName, name, beanName); 353 | Assert.checkNot(fieldDocStorer.isEntity(), "Method: %s, Param: %s, This field %s is an Entity Type, Please check your formatComment.", currentMethodSignature, parameterName, name); 354 | boolean required = actualField.isRequired(); 355 | if (allDeclaredLibraryField && fieldDocStorer.isDeclared()) { 356 | if (required != allRequired) 357 | paramTagStorerTempCache.compute(name, (k, v) -> { 358 | v.setRequired(required); 359 | return v; 360 | }); 361 | } else { 362 | paramTagStorerTempCache.put(fieldDocStorer.getName(), fieldDocStorer.build(required)); 363 | } 364 | } 365 | } 366 | return paramTagStorerTempCache; 367 | } 368 | 369 | private static Map createParamTagStorerTempCache(Map nameAndFieldMap0, boolean allRequired) { 370 | return nameAndFieldMap0.entrySet().stream() 371 | .filter(entry -> { 372 | FieldDocStorer fieldDocStorer = entry.getValue(); 373 | return fieldDocStorer.isDeclared() && !fieldDocStorer.isEntity(); 374 | }) 375 | .map(entry -> entry.getValue().build(allRequired)) 376 | .collect(Collectors.toMap(pts -> pts.getName(), pts -> pts)); 377 | } 378 | 379 | /** 380 | * 根据formatFieldName构造 {@link ParamTagStorer} 381 | * 382 | * @param doclet {@link com.sun.javadoc.Doclet} 的实现类 383 | * @param beanName 参数类型的Bean {@link TypeUtils#inferBeanName(Type)} 384 | * @param formatFieldName 注释 @{(*|**|f1[*])[,[-]f2[*]...]} 中的f 385 | * @param required 是否必填,f后是否有* 386 | * @return 387 | */ 388 | private static ParamTagStorer extractFieldParamTagStorer(DefaultSmallDocletImpl doclet, String beanName, String formatFieldName, boolean required) { 389 | Map nameAndFieldMap = doclet.getNameAndFieldMap(beanName); 390 | ParamTagStorer paramTagStorer = new ParamTagStorer("", required); 391 | FieldDocStorer fieldDocStorer; 392 | String name; 393 | StringTokenizer tokenizer = new StringTokenizer(formatFieldName, "."); 394 | boolean nameIsEmpty = true; 395 | String currentMethodSignature = doclet.getCurrentMethodSignature(); 396 | 397 | while (tokenizer.hasMoreTokens()) { 398 | String token = tokenizer.nextToken(); 399 | name = token; 400 | fieldDocStorer = nameAndFieldMap.get(name); 401 | Assert.notNull(fieldDocStorer, "Method: %s, FormatFieldName: %s, This field %s does not exist in %s.", currentMethodSignature, formatFieldName, name, beanName); 402 | 403 | if (nameIsEmpty) { 404 | paramTagStorer.setName(name); 405 | nameIsEmpty = false; 406 | } else { 407 | paramTagStorer.setName(paramTagStorer.getName() + "." + name); 408 | } 409 | 410 | //如果没有包含下一层字段 411 | if (!tokenizer.hasMoreTokens()) { 412 | Assert.checkNot(fieldDocStorer.isEntity(), "Method: %s, FormatFieldName: %s, This field %s or its elements is an entity type. More fine-grained config is required.", currentMethodSignature, formatFieldName, name); 413 | FieldDocStorer.fill(fieldDocStorer, paramTagStorer); 414 | break; 415 | } else { 416 | Assert.check(fieldDocStorer.isEntity(), "Method: %s, FormatFieldName: %s, This field %s or its elements should be an entity type.", currentMethodSignature, formatFieldName, name); 417 | //元素类型是实体类型的集合或数组类型需要加“[]”后缀 418 | if (fieldDocStorer.isCollection() || fieldDocStorer.isArray()) 419 | paramTagStorer.setName(paramTagStorer.getName() + "[0]"); 420 | nameAndFieldMap = doclet.getNameAndFieldMap(fieldDocStorer.isCollection() ? fieldDocStorer.getEleName() : fieldDocStorer.getQtype()); 421 | } 422 | } 423 | return paramTagStorer; 424 | } 425 | 426 | private static ActualField extractActualField(String formatFieldName, int beginIndex) { 427 | int i = formatFieldName.indexOf("*"); 428 | if (i < 0) 429 | return ActualField.newField(formatFieldName.substring(beginIndex), false); 430 | return ActualField.newField(formatFieldName.substring(beginIndex, i), true); 431 | } 432 | 433 | private static class ActualField { 434 | private String name; 435 | private boolean required; 436 | 437 | public ActualField(String name, boolean required) { 438 | this.name = name; 439 | this.required = required; 440 | } 441 | 442 | public String getName() { 443 | return name; 444 | } 445 | 446 | public boolean isRequired() { 447 | return required; 448 | } 449 | 450 | public static ActualField newField(String name, boolean required) { 451 | return new ActualField(name, required); 452 | } 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/util/TypeUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.util; 2 | 3 | import com.github.liuhuagui.smalldoc.core.DefaultSmallDocletImpl; 4 | import com.github.liuhuagui.smalldoc.core.storer.FieldDocStorer; 5 | import com.sun.javadoc.*; 6 | import com.sun.tools.javac.code.TypeTag; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.lang.reflect.Field; 11 | import java.util.*; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * 类型处理工具 16 | * 17 | * @author KaiKang 799600902@qq.com 18 | */ 19 | public class TypeUtils { 20 | private static Logger log = LoggerFactory.getLogger(TypeUtils.class); 21 | 22 | /** 23 | * 判断类型是否为库类型。默认基本类型,java包,javax包,spring包为库类型。 24 | * 25 | * @param ptype 26 | * @return 27 | */ 28 | public static boolean isLibraryType(Type ptype) { 29 | String qualifiedTypeName; 30 | return ptype.isPrimitive() || 31 | (qualifiedTypeName = ptype.qualifiedTypeName()).startsWith("java.") || 32 | qualifiedTypeName.startsWith("javax.") || 33 | qualifiedTypeName.startsWith("org.springframework."); 34 | } 35 | 36 | /** 37 | * 判断类型是否为库类型。默认基本类型,java包,javax包,spring包和配置类型。 38 | * 39 | * @param ptype 40 | * @return 41 | */ 42 | public static boolean isLibraryType(Type ptype, DefaultSmallDocletImpl doclet) { 43 | String qualifiedTypeName = ptype.qualifiedTypeName(); 44 | List libraryTypePackages = doclet.getLibraryTypePackages(); 45 | if (Utils.isNotEmpty(libraryTypePackages)) { 46 | for (String libraryTypePackage : libraryTypePackages) { 47 | if (qualifiedTypeName.startsWith(libraryTypePackage)) 48 | return true; 49 | } 50 | } 51 | List libraryTypeQualifiedNames = doclet.getLibraryTypeQualifiedNames(); 52 | if (Utils.isNotEmpty(libraryTypeQualifiedNames)) { 53 | for (String libraryTypeQualifiedName : libraryTypeQualifiedNames) { 54 | if (qualifiedTypeName.equals(libraryTypeQualifiedName)) 55 | return true; 56 | } 57 | } 58 | return isLibraryType(ptype); 59 | } 60 | 61 | /** 62 | * 非库类型即实体类型 63 | * 64 | * @param type 65 | * @return 66 | * @see #isLibraryType(Type, DefaultSmallDocletImpl) 67 | */ 68 | public static boolean isEntity(Type type, DefaultSmallDocletImpl doclet) { 69 | return !isLibraryType(type, doclet); 70 | } 71 | 72 | public static boolean isCollection(Type ptype) { 73 | return isCollection(ptype.typeName()); 74 | } 75 | 76 | public static boolean isCollection(String typeName) { 77 | return typeName.equals("Set") || typeName.equals("List") || typeName.equals("ArrayList"); 78 | } 79 | 80 | public static boolean isFile(Type ptype) { 81 | return isFile(ptype.typeName()); 82 | } 83 | 84 | public static boolean isFile(String typeName) { 85 | return typeName.equals("File") || typeName.equals("MultipartFile"); 86 | } 87 | 88 | public static boolean isArray(Type ptype) { 89 | return Utils.isNotBlank(ptype.dimension()); 90 | } 91 | 92 | /** 93 | * 获取带维度的参数 94 | * 95 | * @param ptype 96 | * @return 97 | */ 98 | public static String getParamTypeWithDimension(Type ptype) { 99 | return ptype.typeName() + ptype.dimension(); 100 | } 101 | 102 | /** 103 | * 增加到beans 104 | * 105 | * @param type 106 | * @param doclet 107 | */ 108 | public static void addBean(Type type, DefaultSmallDocletImpl doclet) { 109 | String beanName = inferBeanName(type);//推断BeanName 110 | //1. 单线程工作,不用加锁。 111 | //2. 判断该bean是否存在,避免循环引用造成的死循环 112 | Map> beanFieldsMap = doclet.getBeanFieldsMap(); 113 | if (Objects.nonNull(beanFieldsMap.get(beanName))) 114 | return; 115 | 116 | //3. 如果该类型是参数化类型,去推断类型变量 117 | Map typeVariableToTypeArgumentMap = null; 118 | if (type.asParameterizedType() != null) { 119 | typeVariableToTypeArgumentMap = typeVariableToTypeArgumentMap(type.asParameterizedType()); 120 | } 121 | 122 | //4. 在解析字段之前缓存该beanName,结合步骤2,避免循环引用 123 | List fieldDocStorers = new ArrayList<>(); 124 | beanFieldsMap.put(beanName, fieldDocStorers); 125 | 126 | Map nameAndFieldMap = new HashMap<>(); 127 | Map> entityAndFieldMap = doclet.getEntityAndFieldMap(); 128 | entityAndFieldMap.put(beanName, nameAndFieldMap); 129 | 130 | addFieldsToBean(type, doclet, typeVariableToTypeArgumentMap, fieldDocStorers, nameAndFieldMap, true); 131 | } 132 | 133 | /** 134 | * 增加字段信息到Bean。如果类实现了{@link java.io.Serializable} 或 {@link java.io.Externalizable}那么返回序列化字段集合, 135 | * 否则,获取所有字段集合,不管Access Modifier
136 | * 注意:如果是数组类型ArrayTypeImpl,则跳过数组维度,默认得到的即是元素类型字段的集合。 137 | * 138 | * @param type 139 | * @param doclet 140 | * @param typeVariableToTypeArgumentMap 141 | * @param fieldDocStorers 142 | * @param nameAndFieldMap 143 | * @param declared 是否是直接声明的字段(非inherited) 144 | */ 145 | public static void addFieldsToBean(Type type, DefaultSmallDocletImpl doclet, 146 | Map typeVariableToTypeArgumentMap, 147 | List fieldDocStorers, 148 | Map nameAndFieldMap, 149 | boolean declared) { 150 | if (Objects.isNull(type) || isLibraryType(type, doclet)) 151 | return; 152 | ClassDoc classDoc = type.asClassDoc();//如果是数组类型ArrayTypeImpl,则跳过数组维度 153 | FieldDoc[] fields = classDoc.serializableFields(); 154 | if (Utils.isEmpty(fields)) 155 | fields = classDoc.fields(false);//不管Access Modifier 156 | 157 | if (!Utils.isEmpty(fields)) { 158 | for (FieldDoc fieldDoc : fields) 159 | parseFieldDoc(doclet, 160 | typeVariableToTypeArgumentMap, 161 | fieldDocStorers, 162 | nameAndFieldMap, 163 | fieldDoc, 164 | declared); 165 | } 166 | 167 | addFieldsToBean(classDoc.superclassType(), doclet, 168 | typeVariableToTypeArgumentMap, 169 | fieldDocStorers, 170 | nameAndFieldMap, 171 | false); 172 | } 173 | 174 | private static void parseFieldDoc(DefaultSmallDocletImpl doclet, 175 | Map typeVariableToTypeArgumentMap, 176 | List fieldDocStorers, 177 | Map nameAndFieldMap, 178 | FieldDoc fieldDoc, 179 | boolean declared) { 180 | Type ftype = fieldDoc.type(); 181 | FieldDocStorer fieldDocStorer = new FieldDocStorer(); 182 | //推断类型变量TypeVariable的实际类型 183 | inferTypeVariableActualType(typeVariableToTypeArgumentMap, ftype, fieldDocStorer); 184 | if (fieldDocStorer.getTypeArguments() == null) 185 | fieldDocStorer.setTypeArguments(getTypeArgumentsOnFields(ftype, typeVariableToTypeArgumentMap, doclet)); 186 | 187 | fieldDocStorer.setDeclared(declared); 188 | fieldDocStorer.setComment(fieldDoc.commentText()); 189 | fieldDocStorer.setName(fieldDoc.name()); 190 | fieldDocStorer.setCollection(TypeUtils.isCollection(ftype)); 191 | fieldDocStorer.setArray(Utils.isNotBlank(ftype.dimension())); 192 | fieldDocStorer.setFile(TypeUtils.isFile(ftype)); 193 | 194 | //如果是List或Set 195 | if (fieldDocStorer.isCollection()) { 196 | Type typeArgument = ftype.asParameterizedType().typeArguments()[0];//仅支持单typeArgument 197 | fieldDocStorer.setEleName(inferBeanName(typeArgument)); 198 | fieldDocStorer.setEntity(isEntity(typeArgument, doclet)); 199 | fieldDocStorer.setFile(TypeUtils.isFile(typeArgument)); 200 | } 201 | 202 | nameAndFieldMap.put(fieldDocStorer.getName(), fieldDocStorer); 203 | fieldDocStorers.add(fieldDocStorer); 204 | //如果不是库类型,保留字段 205 | if (isEntity(ftype, doclet)) {//数组类型是否是库类型,与其元素类型一致 206 | fieldDocStorer.setEntity(true); 207 | addBean(ftype, doclet); 208 | } 209 | } 210 | 211 | 212 | /** 213 | * 对于ParameterizedType(泛型调用),typeVariable的实际类型由传入的typeArgument决定,所以不同的typeArgments 214 | * 会产生不同的beanFields信息,为保证正确的映射关系,ParameterizedType的BeanName需要保留泛型信息,同时要注意去除 215 | * 数组维度。 216 | * 217 | * @param type 218 | * @return 219 | */ 220 | public static String inferBeanName(Type type) { 221 | //默认使用qualifiedTypeName做key 222 | String key = type.qualifiedTypeName(); 223 | //typeArguments的传入会造成typeVariable字段的实际类型不同, 224 | //为了每次都能够解析到具体字段类型,使用toString()作为key(携带泛型信息)。 225 | ParameterizedType parameterizedType = type.asParameterizedType();//取出参数化类型做后续操作,防止数组维度造成的混乱。 226 | if (parameterizedType != null) 227 | key = parameterizedType.toString(); 228 | return key; 229 | } 230 | 231 | /** 232 | * 不包含维度,但是包含完全限定和泛型参数信息 233 | * 234 | * @param type1 235 | * @return 236 | */ 237 | public static String getQualifierName(com.sun.tools.javac.code.Type type1) { 238 | return type1.hasTag(TypeTag.ARRAY) ? getQualifierName(((com.sun.tools.javac.code.Type.ArrayType) type1).elemtype) : type1.toString(); 239 | } 240 | 241 | /** 242 | * 不包含完全限定,泛型,但是包含维度。 243 | * 244 | * @param type1 245 | * @param demision 246 | * @return 247 | */ 248 | public static String getName(com.sun.tools.javac.code.Type type1, int demision) { 249 | return type1.hasTag(TypeTag.ARRAY) ? getName(((com.sun.tools.javac.code.Type.ArrayType) type1).elemtype, ++demision) : type1.tsym.name.toString() + Utils.dimension(demision); 250 | } 251 | 252 | /** 253 | * 获得typeVariables与typeArguments的映射 254 | * 255 | * @param pType 参数化类型 256 | * @return 257 | */ 258 | public static Map typeVariableToTypeArgumentMap(ParameterizedType pType) { 259 | Field f = null; 260 | try { 261 | f = pType.getClass().getSuperclass().getDeclaredField("type"); 262 | } catch (NoSuchFieldException e) { 263 | log.error("type fields not exist.", e); 264 | } 265 | com.sun.tools.javac.code.Type.ClassType o = null; 266 | try { 267 | f.setAccessible(true); 268 | o = (com.sun.tools.javac.code.Type.ClassType) f.get(pType); 269 | } catch (IllegalAccessException e) { 270 | log.error("fields can't be accessed", e); 271 | } 272 | 273 | List typeArguments = o.typarams_field; 274 | List typeVariables = ((com.sun.tools.javac.code.Type.ClassType) o.tsym.type).typarams_field; 275 | 276 | if (typeArguments.size() == 0) { 277 | log.warn("Failed to infer type, it is recommended to explicitly give the type parameter: {}.", pType.toString()); 278 | return null; 279 | } 280 | if (typeArguments.size() != typeVariables.size()) 281 | throw new IllegalArgumentException("This may be a bug,welcome to issue."); 282 | 283 | HashMap map = new HashMap<>(); 284 | for (int i = 0; i < typeVariables.size(); i++) { 285 | map.put(typeVariables.get(i).toString(), typeArguments.get(i)); 286 | } 287 | return map; 288 | } 289 | 290 | 291 | /** 292 | * 获取泛型参数,并添加泛型信息到Beans。
293 | * 使用JSONArray存储返回值。如果使用JSONObject,泛型变量的个数将无法计算。 294 | * 295 | * @param ptype 296 | * @param doclet 297 | * @return 298 | */ 299 | public static List getTypeArguments(Type ptype, DefaultSmallDocletImpl doclet) { 300 | List typeArgumentStorers = new ArrayList<>(); 301 | ParameterizedType parameterizedType; 302 | if (Objects.nonNull(parameterizedType = ptype.asParameterizedType())) { 303 | for (Type typeArgument : parameterizedType.typeArguments()) { 304 | FieldDocStorer typeArgumentStorer = new FieldDocStorer(); 305 | typeArgumentStorer.setType(getParamTypeWithDimension(typeArgument)); 306 | typeArgumentStorer.setTypeArguments(getTypeArguments(typeArgument, doclet)); 307 | typeArgumentStorer.setQtype(inferBeanName(typeArgument)); 308 | typeArgumentStorers.add(typeArgumentStorer); 309 | 310 | //如果不是库类型,保留字段 311 | if (isEntity(typeArgument, doclet)) { 312 | addBean(typeArgument, doclet); 313 | } 314 | } 315 | } 316 | return typeArgumentStorers; 317 | } 318 | 319 | /** 320 | * 获取字段Fields的泛型参数,并添加泛型信息到Beans。
321 | * 由于字段泛型参数可能是类型变量TypeVariable,所以需要单独处理。(对于方法或返回值中存在的类型变量TypeVariable,Controller接口应该去避免,所以不做解析支持。) 322 | * 使用JSONArray存储返回值。如果使用JSONObject,泛型变量的个数不方便计算。 323 | * 324 | * @param ptype 字段的类型 325 | * @param typeVariableToTypeArgumentMap 字段所属对象的类型参数TypeArgument与类型变量typeVariable的映射关系 326 | * @param doclet 327 | * @return 328 | */ 329 | public static List getTypeArgumentsOnFields(Type ptype, Map typeVariableToTypeArgumentMap, DefaultSmallDocletImpl doclet) { 330 | List typeArgumentStorers = new ArrayList<>(); 331 | ParameterizedType parameterizedType; 332 | if (Objects.nonNull(parameterizedType = ptype.asParameterizedType())) { 333 | for (Type typeArgument : parameterizedType.typeArguments()) { 334 | FieldDocStorer typeArgumentStorer = new FieldDocStorer(); 335 | //推断类型变量TypeVariable的实际类型 336 | inferTypeVariableActualType(typeVariableToTypeArgumentMap, typeArgument, typeArgumentStorer); 337 | if (typeArgumentStorer.getTypeArguments() == null) 338 | typeArgumentStorer.setTypeArguments(getTypeArgumentsOnFields(typeArgument, typeVariableToTypeArgumentMap, doclet)); 339 | typeArgumentStorers.add(typeArgumentStorer); 340 | 341 | //如果不是库类型,保留它的字段 342 | if (isEntity(typeArgument, doclet)) { 343 | addBean(typeArgument, doclet); 344 | } 345 | } 346 | } 347 | return typeArgumentStorers; 348 | } 349 | 350 | /** 351 | * 推断类型变量TypeVariable的实际类型 352 | * 353 | * @param typeVariableToTypeArgumentMap 字段所属对象的类型参数TypeArgument与类型变量typeVariable的映射关系 354 | * @param fieldOrFieldArgumentType 字段或字段参数类型 355 | * @param fieldDocStorer 字段信息存储对象 356 | */ 357 | public static void inferTypeVariableActualType(Map typeVariableToTypeArgumentMap, Type fieldOrFieldArgumentType, FieldDocStorer fieldDocStorer) { 358 | //字段是typeVariable并且typeArguments被显示声明,可推断。 359 | TypeVariable typeVariable = fieldOrFieldArgumentType.asTypeVariable();//取出类型变量做后续操作,防止数组维度造成的混乱。 360 | if (typeVariable != null && typeVariableToTypeArgumentMap != null) { 361 | com.sun.tools.javac.code.Type type1 = typeVariableToTypeArgumentMap.get(typeVariable.qualifiedTypeName()); 362 | //To change the way that we handle with typeArguments when fieldOrFieldArgumentType is TypeVariable & Parameterized. 363 | fieldDocStorer.setTypeArguments(getTypeArgumentsOnTypeVariable(type1)); 364 | fieldDocStorer.setQtype(getQualifierName(type1)); 365 | fieldDocStorer.setType(getName(type1, 0)); 366 | } else { 367 | fieldDocStorer.setQtype(inferBeanName(fieldOrFieldArgumentType)); 368 | fieldDocStorer.setType(getParamTypeWithDimension(fieldOrFieldArgumentType)); 369 | } 370 | } 371 | 372 | /** 373 | * To handle with typeArguments on TypeVariable.
374 | * 这里不用再{@link #addBean(Type, DefaultSmallDocletImpl)},因为既然是TypeVariable, 375 | * 应该明确出现在TypeArguments中,在解析 ownerType’fields 之前ownerType<TypeArguments>应该被优先处理。 376 | * 377 | * @param typeVariable 378 | */ 379 | private static List getTypeArgumentsOnTypeVariable(com.sun.tools.javac.code.Type typeVariable) { 380 | if (typeVariable.isParameterized()) { 381 | return typeVariable.getTypeArguments().stream().map(typeArgument -> { 382 | FieldDocStorer typeArgumentStorer = new FieldDocStorer(); 383 | typeArgumentStorer.setQtype(getQualifierName(typeArgument)); 384 | typeArgumentStorer.setType(getName(typeArgument, 0)); 385 | typeArgumentStorer.setTypeArguments(getTypeArgumentsOnTypeVariable(typeArgument)); 386 | return typeArgumentStorer; 387 | }).collect(Collectors.toList()); 388 | } 389 | return null; 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.util; 2 | 3 | import com.github.liuhuagui.smalldoc.core.DefaultSmallDocletImpl; 4 | import com.sun.javadoc.Type; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.*; 9 | import java.lang.reflect.Array; 10 | import java.util.Collection; 11 | import java.util.Iterator; 12 | import java.util.List; 13 | import java.util.Objects; 14 | 15 | /** 16 | * @author KaiKang 799600902@qq.com 17 | */ 18 | public class Utils { 19 | private static Logger log = LoggerFactory.getLogger(Utils.class); 20 | 21 | private static final int STRING_BUILDER_SIZE = 256; 22 | 23 | public static final String EMPTY = ""; 24 | 25 | public static final String DEFAULT_PATH_SEPARATOR = "/"; 26 | 27 | public final static int DEFAULT_BUFFER_SIZE = 1024 * 4; 28 | 29 | /** 30 | *

Checks if an array of Objects is not empty and not {@code null}. 31 | * 32 | * @param the component type of the array 33 | * @param array the array to test 34 | * @return {@code true} if the array is not empty and not {@code null} 35 | */ 36 | public static boolean isNotEmpty(final T[] array) { 37 | return !isEmpty(array); 38 | } 39 | 40 | public static boolean isNotEmpty(final Collection coll) { 41 | return !isEmpty(coll); 42 | } 43 | 44 | /** 45 | *

Shallow clones an array returning a typecast result and handling 46 | * {@code null}. 47 | *

48 | *

The objects in the array are not cloned, thus there is no special 49 | * handling for multi-dimensional arrays. 50 | *

51 | *

This method returns {@code null} for a {@code null} input array. 52 | * 53 | * @param the component type of the array 54 | * @param array the array to shallow clone, may be {@code null} 55 | * @return the cloned array, {@code null} if {@code null} input 56 | */ 57 | public static T[] clone(final T[] array) { 58 | if (array == null) { 59 | return null; 60 | } 61 | return array.clone(); 62 | } 63 | 64 | /** 65 | *

Adds all the elements of the given arrays into a new array. 66 | *

The new array contains all of the element of {@code array1} followed 67 | * by all of the elements {@code array2}. When an array is returned, it is always 68 | * a new array. 69 | *

70 | *

 71 |      * ArrayUtils.addAll(null, null)     = null
 72 |      * ArrayUtils.addAll(array1, null)   = cloned copy of array1
 73 |      * ArrayUtils.addAll(null, array2)   = cloned copy of array2
 74 |      * ArrayUtils.addAll([], [])         = []
 75 |      * ArrayUtils.addAll([null], [null]) = [null, null]
 76 |      * ArrayUtils.addAll(["a", "b", "c"], ["1", "2", "3"]) = ["a", "b", "c", "1", "2", "3"]
 77 |      * 
78 | * 79 | * @param the component type of the array 80 | * @param array1 the first array whose elements are added to the new array, may be {@code null} 81 | * @param array2 the second array whose elements are added to the new array, may be {@code null} 82 | * @return The new array, {@code null} if both arrays are {@code null}. 83 | * The type of the new array is the type of the first array, 84 | * unless the first array is null, in which case the type is the same as the second array. 85 | * @throws IllegalArgumentException if the array types are incompatible 86 | */ 87 | public static T[] addAll(final T[] array1, final T... array2) { 88 | if (array1 == null) { 89 | return clone(array2); 90 | } else if (array2 == null) { 91 | return clone(array1); 92 | } 93 | final Class type1 = array1.getClass().getComponentType(); 94 | @SuppressWarnings("unchecked") // OK, because array is of type T 95 | final T[] joinedArray = (T[]) Array.newInstance(type1, array1.length + array2.length); 96 | System.arraycopy(array1, 0, joinedArray, 0, array1.length); 97 | try { 98 | System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); 99 | } catch (final ArrayStoreException ase) { 100 | // Check if problem was due to incompatible types 101 | /* 102 | * We do this here, rather than before the copy because: 103 | * - it would be a wasted check most of the time 104 | * - safer, in case check turns out to be too strict 105 | */ 106 | final Class type2 = array2.getClass().getComponentType(); 107 | if (!type1.isAssignableFrom(type2)) { 108 | throw new IllegalArgumentException("Cannot store " + type2.getName() + " in an array of " 109 | + type1.getName(), ase); 110 | } 111 | throw ase; // No, so rethrow original 112 | } 113 | return joinedArray; 114 | } 115 | 116 | /** 117 | * Null-safe check if the specified collection is empty. 118 | *

119 | * Null returns true. 120 | * 121 | * @param coll the collection to check, may be null 122 | * @return true if empty or null 123 | */ 124 | public static boolean isEmpty(final Collection coll) { 125 | return coll == null || coll.isEmpty(); 126 | } 127 | 128 | /** 129 | *

Checks if an array of Objects is empty or {@code null}. 130 | * 131 | * @param array the array to test 132 | * @return {@code true} if the array is empty or {@code null} 133 | */ 134 | public static boolean isEmpty(final Object[] array) { 135 | return getLength(array) == 0; 136 | } 137 | 138 | /** 139 | *

Returns the length of the specified array. 140 | * This method can deal with {@code Object} arrays and with primitive arrays. 141 | *

142 | *

If the input array is {@code null}, {@code 0} is returned. 143 | *

144 | *

145 |      * ArrayUtils.getLength(null)            = 0
146 |      * ArrayUtils.getLength([])              = 0
147 |      * ArrayUtils.getLength([null])          = 1
148 |      * ArrayUtils.getLength([true, false])   = 2
149 |      * ArrayUtils.getLength([1, 2, 3])       = 3
150 |      * ArrayUtils.getLength(["a", "b", "c"]) = 3
151 |      * 
152 | * 153 | * @param array the array to retrieve the length from, may be null 154 | * @return The length of the array, or {@code 0} if the array is {@code null} 155 | * @throws IllegalArgumentException if the object argument is not an array. 156 | */ 157 | public static int getLength(final Object array) { 158 | if (array == null) { 159 | return 0; 160 | } 161 | return Array.getLength(array); 162 | } 163 | 164 | /** 165 | * 拼接路径信息 166 | * 167 | * @param p0 168 | * @param p1 169 | * @return 170 | */ 171 | public static String unitePath(String p0, String p1) { 172 | //去除首斜线 173 | if (p0.startsWith(DEFAULT_PATH_SEPARATOR)) 174 | p0 = p0.substring(1); 175 | if (p1.startsWith(DEFAULT_PATH_SEPARATOR)) 176 | p1 = p1.substring(1); 177 | 178 | //拼接路径 179 | if (p0.endsWith(DEFAULT_PATH_SEPARATOR) || isBlank(p0)) { 180 | return p0 + p1; 181 | } else { 182 | return p0 + DEFAULT_PATH_SEPARATOR + p1; 183 | } 184 | } 185 | 186 | /** 187 | *

Checks if a CharSequence is empty (""), null or whitespace only.

188 | *

189 | *

Whitespace is defined by {@link Character#isWhitespace(char)}.

190 | *

191 | *

192 |      * StringUtils.isBlank(null)      = true
193 |      * StringUtils.isBlank("")        = true
194 |      * StringUtils.isBlank(" ")       = true
195 |      * StringUtils.isBlank("bob")     = false
196 |      * StringUtils.isBlank("  bob  ") = false
197 |      * 
198 | * 199 | * @param cs the CharSequence to check, may be null 200 | * @return {@code true} if the CharSequence is null, empty or whitespace only 201 | */ 202 | public static boolean isBlank(final CharSequence cs) { 203 | int strLen; 204 | if (cs == null || (strLen = cs.length()) == 0) { 205 | return true; 206 | } 207 | for (int i = 0; i < strLen; i++) { 208 | if (!Character.isWhitespace(cs.charAt(i))) { 209 | return false; 210 | } 211 | } 212 | return true; 213 | } 214 | 215 | public static boolean isNotBlank(final CharSequence cs) { 216 | return !isBlank(cs); 217 | } 218 | 219 | /** 220 | *

Joins the elements of the provided {@code Iterable} into 221 | * a single String containing the provided elements.

222 | *

223 | *

No delimiter is added before or after the list. 224 | * A {@code null} separator is the same as an empty String ("").

225 | *

226 | *

227 |      * StringUtils.join(null, *)                = null
228 |      * StringUtils.join([], *)                  = ""
229 |      * StringUtils.join([null], *)              = ""
230 |      * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
231 |      * StringUtils.join(["a", "b", "c"], null)  = "abc"
232 |      * StringUtils.join(["a", "b", "c"], "")    = "abc"
233 |      * StringUtils.join([null, "", "a"], ',')   = ",,a"
234 |      * 
235 | * 236 | * @param iterable the {@code Iterable} providing the values to join together, may be null 237 | * @param separator the separator character to use, null treated as "" 238 | * @return the joined String, {@code null} if null iterator input 239 | */ 240 | public static String join(final Iterable iterable, final String separator) { 241 | if (iterable == null) { 242 | return null; 243 | } 244 | return join(iterable.iterator(), separator); 245 | } 246 | 247 | /** 248 | *

Joins the elements of the provided {@code Iterator} into 249 | * a single String containing the provided elements.

250 | *

251 | *

No delimiter is added before or after the list. 252 | * A {@code null} separator is the same as an empty String ("").

253 | *

254 | *

255 |      * StringUtils.join(null, *)                = null
256 |      * StringUtils.join([], *)                  = ""
257 |      * StringUtils.join([null], *)              = ""
258 |      * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
259 |      * StringUtils.join(["a", "b", "c"], null)  = "abc"
260 |      * StringUtils.join(["a", "b", "c"], "")    = "abc"
261 |      * StringUtils.join([null, "", "a"], ',')   = ",,a"
262 |      * 
263 | * 264 | * @param iterator the {@code Iterator} of values to join together, may be null 265 | * @param separator the separator character to use, null treated as "" 266 | * @return the joined String, {@code null} if null iterator input 267 | */ 268 | public static String join(final Iterator iterator, final String separator) { 269 | 270 | // handle null, zero and one elements before building a buffer 271 | if (iterator == null) { 272 | return null; 273 | } 274 | if (!iterator.hasNext()) { 275 | return EMPTY; 276 | } 277 | final Object first = iterator.next(); 278 | if (!iterator.hasNext()) { 279 | return Objects.toString(first, ""); 280 | } 281 | 282 | // two or more elements 283 | final StringBuilder buf = new StringBuilder(STRING_BUILDER_SIZE); // Java default is 16, probably too small 284 | if (first != null) { 285 | buf.append(first); 286 | } 287 | 288 | while (iterator.hasNext()) { 289 | if (separator != null) { 290 | buf.append(separator); 291 | } 292 | final Object obj = iterator.next(); 293 | if (obj != null) { 294 | buf.append(obj); 295 | } 296 | } 297 | return buf.toString(); 298 | } 299 | 300 | /** 301 | * 如果首部存在斜线移除它 302 | * 303 | * @param p0 304 | * @return 305 | */ 306 | public static String removeHeadSlashIfPresent(String p0) { 307 | //去除首斜线 308 | if (p0.startsWith(DEFAULT_PATH_SEPARATOR)) 309 | return p0.substring(1); 310 | return p0; 311 | } 312 | 313 | /** 314 | * 返回数组维度 315 | * 316 | * @param demision 数组维度 317 | * @return 318 | */ 319 | public static String dimension(int demision) { 320 | StringBuilder sb = new StringBuilder(); 321 | for (int i = 0; i < demision; i++) { 322 | sb.append("[]"); 323 | } 324 | return sb.toString(); 325 | } 326 | 327 | 328 | public static String read(InputStream in) { 329 | InputStreamReader reader; 330 | try { 331 | reader = new InputStreamReader(in, "UTF-8"); 332 | } catch (UnsupportedEncodingException e) { 333 | throw new IllegalStateException(e.getMessage(), e); 334 | } 335 | return read(reader); 336 | } 337 | 338 | public static String readFromResource(String resource) throws IOException { 339 | InputStream in = null; 340 | try { 341 | in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource); 342 | if (in == null) { 343 | in = Utils.class.getResourceAsStream(resource); 344 | } 345 | 346 | if (in == null) { 347 | return null; 348 | } 349 | 350 | String text = read(in); 351 | return text; 352 | } finally { 353 | close(in); 354 | } 355 | } 356 | 357 | public static byte[] readByteArrayFromResource(String resource) throws IOException { 358 | InputStream in = null; 359 | try { 360 | in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource); 361 | if (in == null) { 362 | return null; 363 | } 364 | 365 | return readByteArray(in); 366 | } finally { 367 | close(in); 368 | } 369 | } 370 | 371 | public static byte[] readByteArray(InputStream input) throws IOException { 372 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 373 | copy(input, output); 374 | return output.toByteArray(); 375 | } 376 | 377 | public static long copy(InputStream input, OutputStream output) throws IOException { 378 | final int EOF = -1; 379 | 380 | byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 381 | 382 | long count = 0; 383 | int n; 384 | while (EOF != (n = input.read(buffer))) { 385 | output.write(buffer, 0, n); 386 | count += n; 387 | } 388 | return count; 389 | } 390 | 391 | public static String read(Reader reader) { 392 | try { 393 | 394 | StringWriter writer = new StringWriter(); 395 | 396 | char[] buffer = new char[DEFAULT_BUFFER_SIZE]; 397 | int n; 398 | while (-1 != (n = reader.read(buffer))) { 399 | writer.write(buffer, 0, n); 400 | } 401 | 402 | return writer.toString(); 403 | } catch (IOException ex) { 404 | throw new IllegalStateException("read error", ex); 405 | } 406 | } 407 | 408 | public static void close(Closeable x) { 409 | if (x == null) { 410 | return; 411 | } 412 | 413 | try { 414 | x.close(); 415 | } catch (Exception e) { 416 | log.error("close error", e); 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/java/com/github/liuhuagui/smalldoc/web/SmallDocServlet.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.smalldoc.web; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.alibaba.fastjson.serializer.SerializerFeature; 6 | import com.github.liuhuagui.smalldoc.core.DefaultSmallDocletImpl; 7 | import com.github.liuhuagui.smalldoc.core.SmallDocContext; 8 | import com.github.liuhuagui.smalldoc.properties.SmallDocProperties; 9 | import com.github.liuhuagui.smalldoc.util.Utils; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import javax.servlet.ServletException; 14 | import javax.servlet.http.HttpServlet; 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import javax.sound.midi.Soundbank; 18 | import java.io.IOException; 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.ExecutionException; 21 | 22 | public class SmallDocServlet extends HttpServlet { 23 | public static final String TITLE = "\\$\\{title}"; 24 | public static final String DOCJSON = "\\$\\{docJSON}"; 25 | public static final String DEFAULT_SERVLET_PATH = "smalldoc"; 26 | 27 | private static Logger log = LoggerFactory.getLogger(SmallDocServlet.class); 28 | private String resourcePath = "smalldoc/support/http/resources"; 29 | 30 | private SmallDocProperties smallDocProperties; 31 | private SmallDocContext smallDocContext; 32 | private CompletableFuture docJSONFuture; 33 | 34 | public SmallDocServlet(SmallDocProperties smallDocProperties) { 35 | this.smallDocProperties = smallDocProperties; 36 | this.smallDocContext = new SmallDocContext(smallDocProperties); 37 | } 38 | 39 | @Override 40 | public void init() throws ServletException { 41 | this.docJSONFuture = CompletableFuture.supplyAsync(() -> { 42 | smallDocContext.execute(new DefaultSmallDocletImpl(smallDocContext)); 43 | return smallDocContext.getDocsJSON(); 44 | }); 45 | } 46 | 47 | @Override 48 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 49 | getURL(req);//获取baseUrl 50 | 51 | String contextPath = req.getContextPath(); 52 | String servletPath = req.getServletPath(); 53 | String requestURI = req.getRequestURI(); 54 | resp.setCharacterEncoding("utf-8"); 55 | if (contextPath == null) { // root context 56 | contextPath = ""; 57 | } 58 | String uri = contextPath + servletPath; 59 | String path = requestURI.substring(contextPath.length() + servletPath.length()); 60 | 61 | if (path.equals("/")) { 62 | resp.setContentType("text/html; charset=utf-8"); 63 | String filePath = getFilePath("/index.html"); 64 | String text = Utils.readFromResource(filePath); 65 | text = text.replaceFirst(TITLE, smallDocProperties.getProjectName() == null ? DEFAULT_SERVLET_PATH : smallDocProperties.getProjectName()); 66 | try { 67 | // 1. 禁止循环引用检测,否则会产生“$ref”,在正则时出现异常(“$”在Java正则中表示group引用) —— java.lang.IllegalArgumentException: Illegal group reference 68 | // 2. 对特殊字符使用Slash转义,防止浏览器JSON.parse失败 69 | String escapeStr = JSON.toJSONString(docJSONFuture.get().toString(SerializerFeature.DisableCircularReferenceDetect), SerializerFeature.WriteSlashAsSpecial); 70 | //去除首尾引号 71 | escapeStr = escapeStr.substring(1,escapeStr.length()-1); 72 | text = text.replaceFirst(DOCJSON, escapeStr); 73 | } catch (InterruptedException | ExecutionException e) { 74 | log.error("", e); 75 | } 76 | resp.getWriter().write(text); 77 | return; 78 | } 79 | 80 | returnResourceFile(path, uri, resp); 81 | } 82 | 83 | private String getURL(HttpServletRequest req) { 84 | StringBuilder sb = new StringBuilder(req.getScheme()); 85 | sb.append("://"); 86 | sb.append(req.getServerName()); 87 | sb.append(":"); 88 | sb.append(req.getServerPort()); 89 | 90 | String contextPath = req.getContextPath(); 91 | sb.append(contextPath); 92 | if (!contextPath.endsWith("/")) 93 | sb.append("/"); 94 | 95 | String baseUrl = sb.toString(); 96 | smallDocContext.getDocsJSON().put("url", baseUrl); 97 | return baseUrl; 98 | } 99 | 100 | @Override 101 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 102 | getURL(req);//获取baseUrl 103 | 104 | String contextPath = req.getContextPath(); 105 | String servletPath = req.getServletPath(); 106 | String requestURI = req.getRequestURI(); 107 | resp.setCharacterEncoding("utf-8"); 108 | if (contextPath == null) { // root context 109 | contextPath = ""; 110 | } 111 | String path = requestURI.substring(contextPath.length() + servletPath.length()); 112 | 113 | if (path.equals("") || path.equals("/")) { 114 | resp.setContentType("application/json;charset=UTF-8"); 115 | try { 116 | resp.getWriter().write(docJSONFuture.get().toString(SerializerFeature.DisableCircularReferenceDetect)); 117 | } catch (InterruptedException | ExecutionException e) { 118 | log.error("", e); 119 | } 120 | return; 121 | } 122 | } 123 | 124 | private String getFilePath(String fileName) { 125 | return resourcePath + fileName; 126 | } 127 | 128 | private void returnResourceFile(String fileName, String uri, HttpServletResponse response) 129 | throws ServletException, 130 | IOException { 131 | if (fileName.equals("")) { 132 | response.sendRedirect(uri + "/"); 133 | return; 134 | } 135 | String filePath = getFilePath(fileName); 136 | 137 | if (filePath.endsWith(".html")) { 138 | response.setContentType("text/html; charset=utf-8"); 139 | } 140 | if (fileName.endsWith(".jpg") || fileName.endsWith(".png") || fileName.endsWith(".ico")) { 141 | byte[] bytes = Utils.readByteArrayFromResource(filePath); 142 | if (bytes != null) { 143 | response.getOutputStream().write(bytes); 144 | } 145 | return; 146 | } 147 | 148 | String text = Utils.readFromResource(filePath); 149 | if (text == null) { 150 | response.sendRedirect(uri + "/"); 151 | return; 152 | } 153 | if (fileName.endsWith(".css")) { 154 | response.setContentType("text/css;charset=utf-8"); 155 | } else if (fileName.endsWith(".js")) { 156 | response.setContentType("text/javascript;charset=utf-8"); 157 | } 158 | response.getWriter().write(text); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /smalldoc-core/src/main/resources/smalldoc/support/http/resources/css/main.56fc618a.chunk.css: -------------------------------------------------------------------------------- 1 | .editable-cell{position:relative}.editable-cell-value-wrap{padding:5px 12px;cursor:pointer;word-break:break-all;word-wrap:break-word}.editable-row:hover .editable-cell-value-wrap{border:1px solid #d9d9d9;border-radius:4px;padding:4px 11px}.forward-visiable-button{visibility:hidden;width:47px!important;height:32px!important;margin:-5px -24px -5px 0;transition:none!important}.editable-row:hover .forward-visiable-button{visibility:visible}.back-addon-after-button{width:47px!important;height:32px!important;margin:-1px -11px}#components-back-top-smalldoc-custom .ant-back-top{right:40px;bottom:120px}#components-back-top-smalldoc-custom .ant-back-top-inner{height:40px;width:40px;line-height:40px;border-radius:4px;background-color:#1088e9;color:#fff;text-align:center;font-size:20px}#components-layout-smalldoc-custom-trigger .trigger{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#components-layout-smalldoc-custom-trigger .trigger:hover{color:#1890ff}#params-or-returns-table-div{border-radius:5px;border:1px solid #d4d4d4;background-image:linear-gradient(#fff,#e5eecc 100px)}.ant-collapse>.ant-collapse-item>.ant-collapse-header{line-height:12px!important}.header-style-green{height:100%;width:100%;padding:4px;border-radius:3px;border:1px solid #49cc90;background-color:#e8f6f0}.header-content-style-green{display:inline-block;white-space:nowrap;width:17.8vw;overflow:hidden;text-overflow:ellipsis;padding:6px 4px;border-radius:3px;background-color:#49cc90}.header-style-blue{height:100%;width:100%;padding:4px;border-radius:3px;border:1px solid #61affe;background-color:#ebf3fb}.header-content-style-blue{display:inline-block;white-space:nowrap;width:17.8vw;overflow:hidden;text-overflow:ellipsis;padding:6px 4px;border-radius:3px;background-color:#61affe}.header-style-red{height:100%;width:100%;padding:4px;border-radius:3px;border:1px solid #f93e3e;background-color:#fae7e7}.header-content-style-red{display:inline-block;white-space:nowrap;width:17.8vw;overflow:hidden;text-overflow:ellipsis;padding:6px 4px;border-radius:3px;background-color:#f93e3e}.header-style-orange{height:100%;width:100%;padding:4px;border-radius:3px;border:1px solid #fca130;background-color:#fbf1e6}.header-content-style-orange{display:inline-block;white-space:nowrap;width:17.8vw;overflow:hidden;text-overflow:ellipsis;padding:6px 4px;border-radius:3px;background-color:#fca130}#type-button .ant-btn{padding:0}.class-doc-info .ant-collapse-header{padding:0!important} 2 | /*# sourceMappingURL=main.56fc618a.chunk.css.map */ -------------------------------------------------------------------------------- /smalldoc-core/src/main/resources/smalldoc/support/http/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhuagui/smalldoc/8b9c16d8615b083cab728caa3a6355aafb116806/smalldoc-core/src/main/resources/smalldoc/support/http/resources/favicon.ico -------------------------------------------------------------------------------- /smalldoc-core/src/main/resources/smalldoc/support/http/resources/index.html: -------------------------------------------------------------------------------- 1 | ${title}
-------------------------------------------------------------------------------- /smalldoc-core/src/main/resources/smalldoc/support/http/resources/js/main.6e79cee6.chunk.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonpsmalldoc=window.webpackJsonpsmalldoc||[]).push([[0],{295:function(e,t,a){e.exports=a(590)},586:function(e,t,a){},589:function(e,t,a){},590:function(e,t,a){"use strict";a.r(t);var n=a(0),r=a.n(n),l=a(7),c=a.n(l),o=(a(300),a(29)),i=a(50),u=a(95),s=a(35),d=a(36),m=a(40),p=a(41),f=a(593),h=a(75),v=a(116),g=a(146),b=a(601),y=a(603),E=a(602),k=a(598),O=function(e){Object(p.a)(a,e);var t=Object(m.a)(a);function a(){return Object(s.a)(this,a),t.apply(this,arguments)}return Object(d.a)(a,[{key:"render",value:function(){var e=this.props,t=e.url,a=e.other;return r.a.createElement(k.a,{title:a.projectName,bordered:!0,column:1,style:{background:"#fff",padding:"10px 20px"}},r.a.createElement(k.a.Item,{label:"\u64cd\u4f5c\u7cfb\u7edf"},a.osName),r.a.createElement(k.a.Item,{label:"JDK\u7248\u672c"},a.jdkVersion),r.a.createElement(k.a.Item,{label:"\u5de5\u7a0b\u7f16\u7801"},a.encoding),r.a.createElement(k.a.Item,{label:"\u8bbf\u95ee\u8def\u5f84"},"".concat(t,"smalldoc/")),r.a.createElement(k.a.Item,{label:"\u6e90\u6587\u4ef6\u8def\u5f84\uff08\u5305\u542b\u5b50\u76ee\u5f55\uff09"},a.sourcepath&&a.sourcepath.split(";").map((function(e,t){return e&&r.a.createElement("span",{key:t},e,r.a.createElement("br",null))}))),r.a.createElement(k.a.Item,{label:"\u626b\u63cf\u7684\u5305\uff08\u5305\u542b\u5b50\u5305\uff09"},a.subpackages&&a.subpackages.split(":").map((function(e,t){return e&&r.a.createElement("span",{key:t},e,r.a.createElement("br",null))}))))}}]),a}(r.a.Component),C=a(597),j=a(592),w=a(76),S=a(591),I=a(265),x=a(605),A=Object(n.createContext)({value:null,onChange:function(){}}),R=function(e){var t=e.value,a=e.color,l=e.children,c=Object(n.useContext)(A),o=c.value,i=c.onChange,u=o===t;return u?r.a.createElement(E.a.CheckableTag,{checked:u,children:l}):r.a.createElement(E.a,{onClick:function(){return i(t)},color:a,children:l})};R.Group=function(e){var t=e.children,a=Object(i.a)(e,["children"]);return r.a.createElement(A.Provider,{value:a},r.a.createElement("span",null,t))};var P=R,N=a(594),D=a(266),L=a(599);function K(e,t){var a,n=e.qtype,r=t[n];return!r&&(a=e.typeArguments)&&1===a.length?new K(a[0],t):{fields:r,pendingType:n}}var M=function(e,t,a){return r.a.createElement("span",{id:"type-button"},a[t.qtype]?r.a.createElement(w.a,{type:"link",onClick:function(){return e=t.qtype,void L.a.warning("\u8fd9\u662f\u4e00\u4e2a\u590d\u6742\u5bf9\u8c61\uff1a"+e);var e}},e):e,function(e,t){var a=e.typeArguments;if(a&&a.length>0){var n=0;return r.a.createElement("span",null,"<",a.map((function(e,l){return r.a.createElement("span",{key:l},M(e.type,e,t),++n")})))}return null}(t,a))},T=function(e){var t=e.datas,a=e.beans,n=[{title:"\u53c2\u6570\u540d\u79f0",dataIndex:"name",width:250,fixed:"left",render:function(e,t){return t.children?r.a.createElement("del",null,e):e}},{title:"\u7c7b\u578b",dataIndex:"type",render:function(e,t){return M(e,t,a)}},{title:"\u89e3\u91ca",dataIndex:"comment",render:function(e){return r.a.createElement("div",{dangerouslySetInnerHTML:{__html:e}})}}];return t&&function e(t,a,n,r){t.forEach((function(t){t.hierarchy=a++;var l=new K(t,n),c=l.fields,o=l.pendingType;if(c&&o!==r){var i=JSON.parse(JSON.stringify(c));t.children=i,e(i,a,n,o)}}))}(t,1,a),r.a.createElement("div",{id:"params-or-returns-table-div"},r.a.createElement("div",{style:{margin:10,backgroundColor:"rgba(255,255,255,0.15)"}},r.a.createElement(N.a,{title:function(){return r.a.createElement("b",null,"\u8fd4\u56de\u7ed3\u679c:")},rowKey:function(e){return e.qtype+e.name+e.hierarchy},defaultExpandAllRows:!0,scroll:{y:"max-content",x:1300},pagination:!1,columns:n,dataSource:t})))},U=a(117),_=a(600),q=a(147),F=[["header-style-blue","header-content-style-blue"],["header-style-green","header-content-style-green"],["header-style-red","header-content-style-red"],["header-style-orange","header-content-style-orange"]],H=/\{(\w+)\}/g,B=function(e){var t=e.urlPath,a=e.selectedRows,n=e.urlMethod,r=void 0===n?"POST":n,l=e.showDrawer,c={};a.forEach((function(e){c[e.name]=e.example}));var i=[];t=t.replace(H,(function(e,t){return i.push(t),c[t]}));var u=null;if("HEAD"===r);else if("GET"===r){var s=Object.entries(c).filter((function(e){var t=Object(o.a)(e,2),a=t[0];t[1];return!i.includes(a)})).map((function(e){var t=Object(o.a)(e,2),a=t[0],n=t[1];return"".concat(a,"=").concat(n)})).join("&");s&&(t="".concat(t,"?").concat(s))}else u=function(e){var t=new FormData;return Object.keys(e).forEach((function(a){var n=e[a];if(Array.isArray(n)&&n.length>0&&n[0]instanceof File){var r,l=Object(D.a)(n);try{for(l.s();!(r=l.n()).done;){var c=r.value;t.append(a,c)}}catch(o){l.e(o)}finally{l.f()}}else t.append(a,n)})),t}(c);fetch(t,{method:r,body:u}).then((function(e){if(e.ok)return e.text();throw new Error("Request is failed, status is ".concat(e.status))})).then((function(e){l(e,t)}),(function(e){L.a.error(e.toString())}))},G=function(e){var t=Object(n.useState)(1),a=Object(o.a)(t,2),l=a[0],c=a[1];return r.a.createElement("div",{style:{display:"flex",justifyContent:"space-between"}},r.a.createElement("b",null,"\u8bf7\u6c42\u53c2\u6570:"),r.a.createElement("div",{style:{display:"inline-flex",alignItems:"center"}},r.a.createElement(w.a,{type:"primary",size:"small",onClick:function(){return B(e)},style:{marginRight:20}},"Send"),r.a.createElement(q.a.Group,{onChange:function(e){return c(e.target.value)},value:l},r.a.createElement(q.a,{value:1},"form-data"),r.a.createElement(q.a,{value:2},"x-www-form-urlencoded"))))},J=a(136),z=a(596),V=a(595),W=a(604),$=function(e){Object(p.a)(a,e);var t=Object(m.a)(a);function a(){var e;Object(s.a)(this,a);for(var n=arguments.length,r=new Array(n),l=0;l=0}},p.map((function(e,t){var a=Object(o.a)(e,2),n=a[0],l=a[1].comment;return r.a.createElement(ve,{key:t,value:n},r.a.createElement(g.a,{placement:"right",title:l},l))})))),r.a.createElement(h.a,{theme:"dark",mode:"inline",onClick:this.onClickMenuItem},k.map((function(t,a){return r.a.createElement(he,{onTitleClick:e.getClassDocInfo,key:a,title:r.a.createElement("span",null,r.a.createElement(ie.a,null),r.a.createElement("span",null,t.comment))},t.methods.map((function(e,n){var l=e.comment,c=e.name;return r.a.createElement(h.a.Item,{key:"".concat(a,"-").concat(n)},r.a.createElement("a",{id:a,"data-i":n,href:"#".concat(t.name,"-").concat(c)},l),r.a.createElement("br",null))})))})))),r.a.createElement(f.a,{style:{marginLeft:j}},r.a.createElement(b.a,{offsetTop:0},r.a.createElement(fe,{style:{background:"#fff",padding:0}},this.state.collapsed?r.a.createElement(ue.a,{className:"trigger",onClick:this.toggle}):r.a.createElement(se.a,{className:"trigger",onClick:this.toggle}),r.a.createElement(y.a,{dot:!0},r.a.createElement(de.a,{style:{fontSize:20},rotate:-10,type:"notification"})),r.a.createElement("span",{style:{marginLeft:"20px"}},"\u6587\u6863\u7531",r.a.createElement(E.a,{color:"red",style:{margin:0}},"smalldoc"),"\u63d0\u4f9b\u652f\u6301"),r.a.createElement("a",{style:{marginLeft:"20px"},href:u.support},u.support))),r.a.createElement(pe,{style:{margin:"10px 0px"}},r.a.createElement("div",{style:{padding:30,background:"#fff"}},s?r.a.createElement(oe,Object.assign({currentClassDocInfo:s,beans:l,url:w,activeKey:d},{setActiveKey:this.setActiveKey})):r.a.createElement(O,{url:c,other:u})))))}}]),a}(r.a.Component);function be(e,t){var a=e[t];return{classes:a?a.classes:[],packageUrl:a.url}}var ye=function(){return r.a.createElement("div",{className:"App"},r.a.createElement(ge,null))};Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));c.a.render(r.a.createElement(ye,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then((function(e){e.unregister()}))}},[[295,1,2]]]); 2 | //# sourceMappingURL=main.6e79cee6.chunk.js.map -------------------------------------------------------------------------------- /smalldoc-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.liuhuagui 8 | smalldoc 9 | 2.4 10 | 11 | 12 | smalldoc-spring-boot-starter 13 | smalldoc-spring-boot-starter 14 | Zero-intrusion Java Restful API documentation tool based on code comments. 15 | 16 | 17 | 18 | com.github.liuhuagui 19 | smalldoc-core 20 | ${project.version} 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-autoconfigure 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-configuration-processor 29 | true 30 | 31 | 32 | javax.servlet 33 | javax.servlet-api 34 | provided 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /smalldoc-spring-boot-starter/src/main/java/com/github/liuhuagui/spring/boot/autoconfigure/SmallDocAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.liuhuagui.spring.boot.autoconfigure; 2 | 3 | import com.github.liuhuagui.smalldoc.core.SmallDocContext; 4 | import com.github.liuhuagui.smalldoc.properties.SmallDocProperties; 5 | import com.github.liuhuagui.smalldoc.web.SmallDocServlet; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.context.annotation.Profile; 14 | 15 | @Profile("dev") 16 | @Configuration 17 | @ConditionalOnWebApplication 18 | @ConditionalOnClass(SmallDocContext.class) 19 | @ConditionalOnProperty(name = "smalldoc.enabled", havingValue = "true", matchIfMissing = true) 20 | public class SmallDocAutoConfiguration { 21 | 22 | @Bean 23 | @ConfigurationProperties("smalldoc") 24 | public SmallDocProperties smallDocProperties(){ 25 | return new SmallDocProperties(); 26 | } 27 | 28 | @Bean 29 | public ServletRegistrationBean smallDocServletRegistrationBean(SmallDocProperties smallDocProperties) { 30 | ServletRegistrationBean registrationBean = new ServletRegistrationBean(); 31 | registrationBean.setServlet(new SmallDocServlet(smallDocProperties)); 32 | registrationBean.addUrlMappings(smallDocProperties.getUrlPattern() != null ? smallDocProperties.getUrlPattern() : "/smalldoc/*"); 33 | return registrationBean; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /smalldoc-spring-boot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.github.liuhuagui.spring.boot.autoconfigure.SmallDocAutoConfiguration -------------------------------------------------------------------------------- /smalldoc.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------