├── .gitignore
├── README.md
├── assets
└── image.png
├── pom.xml
└── src
└── main
├── java
└── com
│ └── diff
│ └── core
│ ├── Common
│ ├── Code.java
│ └── Config.java
│ ├── Recorders
│ ├── AbstractRecord.java
│ ├── ApiRecord.java
│ ├── ControllerRecord.java
│ ├── DubboRecord.java
│ └── InterfaceRecord.java
│ ├── Utils
│ ├── ChainUtils.java
│ ├── FileTreeUtil.java
│ ├── FilterUtils.java
│ ├── FullMethodNameUtil.java
│ ├── ParseUtil.java
│ ├── ProjectChainUtils.java
│ └── XmlDiffUtils.java
│ ├── Visitors
│ ├── AsmAnnotationVisitor.java
│ ├── AsmClassVisitor.java
│ ├── AsmMethodAdapter.java
│ ├── AsmSearchFilterClass.java
│ ├── AsmSearchFilterMethod.java
│ ├── MethodVisitorAdapter.java
│ └── VisitorAdapter.java
│ └── main.java
└── resources
└── config.json
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 这是什么:
2 |
3 | 用于静态分析Java工程的调用链,对比新老分支代码的差异,并给出受到影响的接口作为建议,通俗一点地说:能够通过一段修改过的代码,推断出测试要回归哪些接口用例
4 |
5 | ### 设计思想:
6 |
7 | * 名词解释:
8 | * 调用关系:在A方法中调用了B方法,则形成一条调用关系 A->B
9 | * 调用链:从方法A开始,一层层追溯其所有调用关系,如:A->B->C->D,这样一条完整的调用关系链,在此称之为调用链
10 | * 抽象语法树:是源代码**语法**结构的一种**抽象**表示。 它以**树**状的形式表现编程语言的**语法**结构,**树**上的每个节点都表示源代码中的一种结构。
11 | * 核心原理:
12 | * 通过字节码工具,检索工程项目内的所有编译好的字节码文件(.class),获取项目内所有方法的「调用关系」;
13 | * 定义项目内的Controller类的方法为入口,由「调用关系」递归或循环,得到每一个api的「调用链」
14 | * 通过语法树工具,将项目内的源码文件(.java)解析为语法树,可以很容易地对比两个分支的语法树,并得到新分支内被修改过,或者新增的方法;
15 | * 遍历所有「调用链」,检查每一条调用链内是否存在被修改或更新的方法,若存在则认为本次代码更新影响到了此条调用链;
16 | * 收集所有被影响到的「调用链」,我们有理由认为,这些调用链的入口,也就是对应的Controller类的api在本次需求迭代中有更新,需要参与测试或回归测试
17 |
18 | ### 如何使用:
19 |
20 | (注:仅用于分析springboot的项目)
21 |
22 | 1.首先把你需要分析的项目代码clone两份,一份checkout到master,另一份可以checkout到dev(或者其他分支),这样表示对比这两个分支的代码
23 |
24 | 2.打开 src/main/resources/config.json
25 |
26 | ```
27 | {
28 | "oldProjectPath": "/Users/xiaoandi/github/qqbot/qqbot",
29 | "newProjectPath": "/Users/xiaoandi/github/qqbot/diff_test/qqbot",
30 | "source": "src/main/java",
31 | "target": "target/classes",
32 | "resources": "src/main/resources"
33 | }
34 | ```
35 |
36 | 设置好以上路径,比如:
37 |
38 | * oldProjectPath填master分支的路径(会自动检索项目下的所有子模块);
39 | * newProjectPath填dev分支的路径(同上)
40 | * source:代码路径,不用改
41 | * target:maven编译出来的路径,不用改
42 | * resources:资源路径,不用改
43 | * 编译newProjectPath项目,得到target(需要把所有子模块全部编译,oldProject不需要编译)
44 | * 运行 main 即可得到结果
45 | * 如果要打包,执行mvn命令:[mvn assembly:assembly] 得到 `static-chain-analysis-1.0-SNAPSHOT-jar-with-dependencies.jar`
46 | * 这样表示: 对于dev分支新增、修改过的源码将会被分析调用链关系。注意,相较于oldProject,newProject里删除的源码文件将不会加入分析,因为那没多大意义。
47 | * oldProjectPath和newProjectPath路径最后不需要加上"/"
48 | * 可能存在某些代码结构未考虑到,如果报错了可以沟通交流解决
49 |
50 | ### 更新计划
51 |
52 | * [X] 支持检测mybatis的xml配置变动
53 | * [X] 支持dubbo接口的调用链(从注解)
54 | * [ ] 支持dubbo接口的调用链(从xml)
55 | * [ ] 支持SpringCloud的rpc接口
56 | * [X] 为这个垃圾工具增加了一个web页面,可手动选择分支以及精确到具体的commitId来分析[static-chain-analysis-web](https://github.com/F-JH/static-chain-analysis-web)
57 |
58 | ##### 效果图如下:
59 |
60 | 
61 |
--------------------------------------------------------------------------------
/assets/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F-JH/static-chain-analysis/208382857bcae1591245ed50cbebd18eaddaff57/assets/image.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.shopline
8 | static-chain-analysis
9 | 1.0-SNAPSHOT
10 |
11 |
12 | 11
13 | 11
14 |
15 |
16 |
17 |
18 |
19 | org.ow2.asm
20 | asm
21 | 7.3.1
22 |
23 |
24 | org.ow2.asm
25 | asm-commons
26 | 7.3.1
27 |
28 |
29 | org.ow2.asm
30 | asm-util
31 | 7.2-beta
32 |
33 |
34 | asm-analysis
35 | org.ow2.asm
36 |
37 |
38 | asm
39 | org.ow2.asm
40 |
41 |
42 | asm-tree
43 | org.ow2.asm
44 |
45 |
46 |
47 |
48 | com.github.javaparser
49 | javaparser-symbol-solver-core
50 | 3.24.2
51 |
52 |
53 | com.alibaba
54 | fastjson
55 | 1.2.79
56 |
57 |
58 |
59 | commons-io
60 | commons-io
61 | 2.11.0
62 |
63 |
64 |
65 | dom4j
66 | dom4j
67 | 1.6.1
68 |
69 |
70 | jaxen
71 | jaxen
72 | 1.1.6
73 |
74 |
75 |
76 |
77 |
78 |
79 | org.apache.maven.plugins
80 | maven-assembly-plugin
81 | 2.2-beta-5
82 |
83 |
84 |
85 |
86 | true
87 | com.diff.core.main
88 |
89 |
90 |
91 | jar-with-dependencies
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Common/Code.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Common;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class Code {
7 | // 各种分隔符
8 | public final static String METHOD_SPLIT = ".";
9 | public final static String METHOD_SIGNATURE_SPLIT = "|";
10 | public final static String URL_SPLIT;
11 | public final static String SUB_CLASS_SPLIT = "$";
12 | public final static String PACKAGE_SPLIT = "/";
13 | public final static String POM = "pom.xml";
14 | public final static String DUBBO = "[dubbo]";
15 | public final static String HTTP = "[http]";
16 |
17 |
18 | public final static Map descriptorMap = new HashMap<>();
19 | static {
20 | // java基本类型与标识符对照表
21 | descriptorMap.put("B", "byte");
22 | descriptorMap.put("C", "char");
23 | descriptorMap.put("D", "double");
24 | descriptorMap.put("F", "float");
25 | descriptorMap.put("I", "int");
26 | descriptorMap.put("J", "long");
27 | descriptorMap.put("S", "short");
28 | descriptorMap.put("Z", "boolean");
29 | descriptorMap.put("V", "void");
30 | descriptorMap.put("L", ""); // 表示单个类型
31 | descriptorMap.put("[", "[]"); // 表示数组类型
32 |
33 | if(System.getProperty("os.name").toLowerCase().startsWith("win")){
34 | URL_SPLIT = "\\";
35 | }else{
36 | URL_SPLIT = "/";
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Common/Config.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Common;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import com.alibaba.fastjson.JSONObject;
5 |
6 | import java.io.*;
7 | import java.nio.charset.Charset;
8 |
9 | /**
10 | * 读取项目配置
11 | */
12 | public class Config {
13 | private final static Config instance = new Config();
14 | private JSONObject configJson;
15 | private Config(){
16 | try{
17 | // 读取jar包内的config.json
18 | InputStream is = this.getClass().getResourceAsStream("/config.json");
19 | BufferedReader in = new BufferedReader((new InputStreamReader(is, Charset.forName("UTF-8"))));
20 | StringBuffer buffer = new StringBuffer();
21 | String line;
22 | while ((line = in.readLine()) != null){
23 | buffer.append(line);
24 | }
25 | configJson = JSONObject.parseObject(buffer.toString());
26 | }catch (Exception e){
27 | e.printStackTrace();
28 | }
29 | }
30 |
31 | public static Config getInstance(){
32 | return instance;
33 | }
34 |
35 | public void setValue(String key, String value) throws NoSuchFieldException {
36 | configJson.put(key, value);
37 | }
38 | public String getString(String key){
39 | return configJson.getString(key);
40 | }
41 | public JSONObject getJSONObject(String key){
42 | return configJson.getJSONObject(key);
43 | }
44 | public JSONArray getJSONArray(String key){
45 | return configJson.getJSONArray(key);
46 | }
47 | public Integer getInteger(String key){
48 | return configJson.getInteger(key);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Recorders/AbstractRecord.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Recorders;
2 |
3 | import java.util.*;
4 |
5 | /**
6 | * 保存所有抽象类,暂时用不到?
7 | */
8 | public class AbstractRecord {
9 | private Map> record = new HashMap<>();
10 | private final static AbstractRecord instance = new AbstractRecord();
11 | private final Map> abstractMethodList = new HashMap<>();
12 |
13 | private AbstractRecord(){}
14 | public static AbstractRecord getInstance(){
15 | return instance;
16 | }
17 | public void putAbstractClass(String className){
18 | record.computeIfAbsent(className, k -> new ArrayList<>());
19 | abstractMethodList.computeIfAbsent(className, k -> new HashMap<>());
20 | }
21 | public void putAbstractEntry(String abstractClassName, String entryClassName){
22 | record.computeIfAbsent(abstractClassName, k -> new ArrayList<>());
23 | abstractMethodList.computeIfAbsent(abstractClassName, k -> new HashMap<>());
24 | record.get(abstractClassName).add(entryClassName);
25 | }
26 | public void putMethod(String abstractClassName, String methodName, boolean isAbstract){
27 | abstractMethodList.get(abstractClassName).put(methodName, isAbstract);
28 | }
29 | public boolean containAbstract(String abstractClassName){
30 | return record.containsKey(abstractClassName);
31 | }
32 | public List getEntries(String className){
33 | return record.get(className);
34 | }
35 | public Map getMethod(String abstractClassName){
36 | return abstractMethodList.get(abstractClassName);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Recorders/ApiRecord.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Recorders;
2 |
3 | import java.util.*;
4 |
5 | /**
6 | * 保存所有Controller类方法与Api路径的对应关系
7 | */
8 | public class ApiRecord {
9 | private Map> record = new HashMap<>();
10 | private final static ApiRecord instance = new ApiRecord();
11 |
12 | private ApiRecord(){}
13 |
14 | public static ApiRecord getInstance() {
15 | return instance;
16 | }
17 |
18 | public void putApi(String fullMethodName, Set api){
19 | // record.computeIfAbsent(fullMethodName, k -> new HashSet<>());
20 | record.put(fullMethodName, api);
21 | }
22 |
23 | public Set getApis(String fullMethodName){
24 | return record.get(fullMethodName);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Recorders/ControllerRecord.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Recorders;
2 |
3 | import java.util.*;
4 |
5 | /**
6 | * 保存所有Controller方法,在调用链入口可使用
7 | */
8 | public class ControllerRecord {
9 | private Map> controllers = new HashMap<>();
10 | private static final ControllerRecord instance = new ControllerRecord();
11 |
12 | private ControllerRecord(){}
13 | public static ControllerRecord getInstance(){
14 | return instance;
15 | }
16 |
17 | public void putControlClass(String ControlClassName){
18 | controllers.computeIfAbsent(ControlClassName, k -> new ArrayList<>());
19 | }
20 |
21 | public void putControlMethod(String ControlClassName, String ControlMethodName){
22 | controllers.computeIfAbsent(ControlClassName, k -> new ArrayList<>());
23 | controllers.get(ControlClassName).add(ControlMethodName);
24 | }
25 |
26 | public List getApiFromControlClassName(String ControlClassName){
27 | return controllers.get(ControlClassName);
28 | }
29 |
30 | public Set getControllers(){
31 | return controllers.keySet();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Recorders/DubboRecord.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Recorders;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | /**
9 | * 保存所有dubbo的类
10 | */
11 | public class DubboRecord {
12 | private static final List dubboMethods = new ArrayList<>();
13 |
14 |
15 | public static void putDubboMethod(String fullMethodName){
16 | dubboMethods.add(fullMethodName);
17 | }
18 |
19 | public static boolean contains(String fullMethodName){
20 | return dubboMethods.contains(fullMethodName);
21 | }
22 |
23 | public static List getList(){
24 | return dubboMethods;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Recorders/InterfaceRecord.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Recorders;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | /**
9 | * 保存所有Interface与实体类的映射
10 | */
11 | public class InterfaceRecord {
12 | private final Map> interfaceList = new HashMap<>();
13 | private final Map> interfaceMethodList = new HashMap<>();
14 | private static final InterfaceRecord instance = new InterfaceRecord();
15 |
16 |
17 | private InterfaceRecord(){}
18 | public static InterfaceRecord getInstance(){
19 | return instance;
20 | }
21 |
22 | public void putInterfaceClass(String interfaceClassName){
23 | interfaceList.computeIfAbsent(interfaceClassName, k -> new ArrayList<>());
24 | interfaceMethodList.computeIfAbsent(interfaceClassName, k -> new HashMap<>());
25 | }
26 | public void putInterfaceEntry(String interfaceClassName, String entryClassName){
27 | interfaceList.computeIfAbsent(interfaceClassName, k -> new ArrayList<>());
28 | interfaceMethodList.computeIfAbsent(interfaceClassName, k -> new HashMap<>());
29 | interfaceList.get(interfaceClassName).add(entryClassName);
30 | }
31 |
32 | public void putMethod(String interfaceClassName, String methodName, boolean isAbstract){
33 | interfaceMethodList.get(interfaceClassName).put(methodName, isAbstract);
34 | }
35 |
36 | public boolean containInterface(String interfaceClassName){
37 | return interfaceList.containsKey(interfaceClassName);
38 | }
39 |
40 | public List getEntries(String interfaceClassName){
41 | return interfaceList.get(interfaceClassName);
42 | }
43 |
44 | public Map getMethod(String interfaceClassName){
45 | return interfaceMethodList.get(interfaceClassName);
46 | }
47 |
48 | public Map> getInterfaceList() {
49 | return interfaceList;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Utils/ChainUtils.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Utils;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.diff.core.Recorders.AbstractRecord;
5 | import com.diff.core.Recorders.InterfaceRecord;
6 | import com.diff.core.Visitors.AsmClassVisitor;
7 | import com.diff.core.Visitors.AsmSearchFilterClass;
8 | import org.objectweb.asm.ClassReader;
9 | import org.objectweb.asm.ClassVisitor;
10 | import org.objectweb.asm.ClassWriter;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.Stack;
16 |
17 | import static com.diff.core.Common.Code.*;
18 |
19 | public class ChainUtils {
20 | /**
21 | * {
22 | * "botApi|(Lcom/alibaba/fastjson/JSONObject;)Ljava/lang/String;":[
23 | * "com/bot/server/qqBot/envGet:getByEnv|(Ljava/lang/String;)Ljava/lang/String;",
24 | * "com/bot/server/qqBot/mapper/postMethod:run|(Lcom/alibaba/fastjson/JSONObject;Lcom/alibaba/fastjson/JSONObject;Ljava/lang/Integer;)Ljava/lang/String;"
25 | * ],
26 | * "saveToMysql|()Z":[
27 | * "com/bot/server/qqBot/server/msgManage:saveToMysqlSignal|()V"
28 | * ],
29 | * "test|(Ljava/lang/String;)Ljava/lang/String;":[
30 | *
31 | * ],
32 | * "|()V":[
33 | *
34 | * ],
35 | * "showList|()Ljava/lang/String;":[
36 | * "com/bot/server/qqBot/server/msgManage:getMsgList|()Ljava/util/Map;"
37 | * ],
38 | * "showEnvs|()Ljava/util/Map;":[
39 | * "com/bot/server/qqBot/envGet:getByEnv|(Ljava/lang/String;)Ljava/lang/String;"
40 | * ],
41 | * "checkThread|(Ljava/lang/String;)Ljava/util/Map;":[
42 | * "com/bot/server/qqBot/server/msgManage:getAllThreadStatus|()Ljava/util/Map;",
43 | * "com/bot/server/qqBot/server/msgManage:getGroupThreadStatus|(Ljava/lang/String;)Ljava/util/Map;"
44 | * ]
45 | * }
46 | * @param classfileBuffer
47 | * @return
48 | */
49 | public static Map> getRelationShipFromClassBuffer(byte[] classfileBuffer){
50 | ClassReader cr = new ClassReader(classfileBuffer);
51 | ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
52 | AsmClassVisitor cv = new AsmClassVisitor(cw);
53 | cr.accept(cv, ClassReader.SKIP_FRAMES);
54 |
55 | return cv.getMethodRelations();
56 | }
57 |
58 |
59 | /**
60 | * 先visit一遍,把项目的所有className存到FilterUtils中
61 | * @param classfileBuffer
62 | */
63 | public static void scanForClassName(byte[] classfileBuffer){
64 | ClassReader cr = new ClassReader(classfileBuffer);
65 | ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
66 | ClassVisitor cv = new AsmSearchFilterClass(cw);
67 | cr.accept(cv, ClassReader.SKIP_FRAMES);
68 | }
69 |
70 | /**
71 | * {
72 | * "com/bot/server/qqBot/envGet:getByEnv|(Ljava/lang/String;)Ljava/lang/String;":{
73 | * "com/bot/server/qqBot/mapper/postMethod:run|(Lcom/alibaba/fastjson/JSONObject;Lcom/alibaba/fastjson/JSONObject;Ljava/lang/Integer;)Ljava/lang/String":{
74 | * ...
75 | * }
76 | * },
77 | * "com/bot/server/qqBot/envGet:test|(Ljava/lang/String;)Ljava/lang/String;":{
78 | * ...
79 | * }
80 | * }
81 | * @param relationShips
82 | * @param startFullMethodName
83 | * @return
84 | */
85 | public static JSONObject getJSONChainFromRelationShip(Map>> relationShips, String startFullMethodName){
86 | InterfaceRecord interfaceRecord = InterfaceRecord.getInstance();
87 | AbstractRecord abstractRecord = AbstractRecord.getInstance();
88 |
89 | String className = startFullMethodName.substring(0, startFullMethodName.indexOf(METHOD_SPLIT));
90 | String methodName = startFullMethodName.substring(startFullMethodName.indexOf(METHOD_SPLIT)+1);
91 | Stack stack = new Stack<>();
92 | JSONObject relation = new JSONObject();
93 | List startChain = new ArrayList<>();
94 | startChain.add(startFullMethodName);
95 | StackJSONNode initNode = new StackJSONNode(className, methodName, startChain, relation);
96 | stack.push(initNode);
97 | while(!stack.empty()){
98 | StackJSONNode currentNode = stack.pop();
99 | String currentClassName = currentNode.getClassName();
100 | // if(currentClassName.equals("com/hiido/controllers/center/UserCenterController"))
101 | // System.out.println("haha");
102 | String currentMethodName = currentNode.getMethodName();
103 | List currentChain = currentNode.getChain();
104 | List methodRelationShip = relationShips.get(currentClassName).get(currentMethodName);
105 | JSONObject currentRelation = currentNode.getChainNode();
106 | // 处理接口或抽象类
107 | if(interfaceRecord.containInterface(currentClassName) || abstractRecord.containAbstract(currentClassName)){
108 | List entries = interfaceRecord.getEntries(currentClassName);
109 | Map abstractMethod = interfaceRecord.getMethod(currentClassName);
110 | if(entries==null){
111 | entries = abstractRecord.getEntries(currentClassName);
112 | abstractMethod = abstractRecord.getMethod(currentClassName);
113 | }
114 | for(String entryClassName:entries){
115 | // 存在default方法,需要判断是否被实体类复写
116 | if(!abstractMethod.get(currentMethodName)){
117 | // default或者抽象类中有实体的方法
118 | if(relationShips.get(entryClassName).containsKey(currentMethodName)){
119 | // 实体类有复写这个方法,但是静态分析下无法判断实际跑的是哪一个方法,所以都要加进去
120 | methodRelationShip.add(entryClassName + METHOD_SPLIT + currentMethodName);
121 | }
122 | }else{
123 | methodRelationShip.add(entryClassName + METHOD_SPLIT + currentMethodName);
124 | }
125 | }
126 | }
127 | if(methodRelationShip.size()>0){
128 | for(String fullMethodName:methodRelationShip){
129 | // 解开methodRelationShip,加到stack中,并与往json里添加
130 | JSONObject tmpRelation = new JSONObject();
131 | currentRelation.put(fullMethodName, tmpRelation);
132 | if(!currentChain.contains(fullMethodName)){
133 | String tmpClassName = fullMethodName.substring(0, fullMethodName.indexOf(METHOD_SPLIT));
134 | String tmpMethodName = fullMethodName.substring(fullMethodName.indexOf(METHOD_SPLIT)+1);
135 | List tmpChain = new ArrayList(currentChain);
136 | tmpChain.add(fullMethodName);
137 | stack.add(new StackJSONNode(tmpClassName, tmpMethodName, tmpChain, tmpRelation));
138 | }
139 | }
140 | }
141 | }
142 | return relation;
143 | }
144 |
145 | private static class StackJSONNode{
146 | private String className;
147 | private String methodName;
148 | private List chain;
149 | private JSONObject chainNode;
150 | public StackJSONNode(String className, String methodName, List chain, JSONObject chainNode){
151 | this.className = className;
152 | this.methodName = methodName;
153 | this.chain = chain;
154 | this.chainNode = chainNode;
155 | }
156 |
157 | public List getChain() {
158 | return chain;
159 | }
160 |
161 | public String getClassName() {
162 | return className;
163 | }
164 |
165 | public String getMethodName() {
166 | return methodName;
167 | }
168 |
169 | public JSONObject getChainNode() {
170 | return chainNode;
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Utils/FileTreeUtil.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Utils;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.diff.core.Common.Config;
5 | import org.apache.commons.io.FileUtils;
6 | import org.dom4j.DocumentException;
7 | import org.w3c.dom.Document;
8 | import org.w3c.dom.NodeList;
9 | import org.xml.sax.SAXException;
10 |
11 | import javax.xml.parsers.DocumentBuilder;
12 | import javax.xml.parsers.DocumentBuilderFactory;
13 | import javax.xml.parsers.ParserConfigurationException;
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.nio.file.NotDirectoryException;
17 | import java.security.MessageDigest;
18 | import java.security.NoSuchAlgorithmException;
19 | import java.util.*;
20 |
21 | import static com.diff.core.Common.Code.*;
22 |
23 | /**
24 | * 文件结构树工具
25 | */
26 | public class FileTreeUtil {
27 | /**
28 | * 对比两个项目的所有pom.xml,得到新增的子模块和非新增的子模块,不关注删除的模块
29 | * @param oldProject
30 | * @param newProject
31 | * @return
32 | * {
33 | * "newModules": [
34 | * // 新的子模块
35 | * "/Users/xiaoandi/github/qqbot/diff_test/qqbot",
36 | * ...
37 | * ],
38 | * "normalModules": [
39 | * // 不是新的子模块
40 | * "/Users/xiaoandi/github/qqbot/diff_test/qqbot",
41 | * ...
42 | * ]
43 | * }
44 | * @throws Exception
45 | */
46 | public static Map> compireToPomTree(String oldProject, String newProject) throws Exception{
47 | File oldRoot = new File(oldProject);
48 | File newRoot = new File(newProject);
49 | if(!oldRoot.exists() || !newRoot.exists() || !oldRoot.isDirectory() || !newRoot.isDirectory())
50 | throw new NotDirectoryException("需要传入完整路径");
51 | Config config = Config.getInstance();
52 | File newRootPom = new File(newProject + URL_SPLIT + POM);
53 | String oldRootPomPath = oldProject + URL_SPLIT + POM;
54 | String newRootPomPath = newProject + URL_SPLIT + POM;
55 |
56 | List newModules = new ArrayList<>(); // 新增的模块,不需要走parser对比,直接检索并加入 new Method 列表
57 | List normalModules = new ArrayList<>(); // 非新增模块,需要走正常流程
58 | File newRootSource = new File(newProject + URL_SPLIT + config.getString("source"));
59 | File oldRootSource = new File(oldProject + URL_SPLIT + config.getString("source"));
60 |
61 | Stack start = new Stack<>();
62 | if(newRootSource.exists() && newRootSource.listFiles().length > 0){
63 | // 根目录有源码
64 | if(oldRootSource.exists() && oldRootSource.listFiles().length > 0){
65 | // 旧项目根目录也TM有源码
66 | normalModules.add(newProject);
67 | }else{
68 | newModules.add(newProject);
69 | }
70 | }
71 | for(String module:listModules(newRootPom)){
72 | // 初始化start
73 | start.push(new FileNode(
74 | new StringBuilder(""),
75 | new File(newProject + URL_SPLIT + module + URL_SPLIT + POM)
76 | ));
77 | }
78 | while(!start.empty()){
79 | FileNode topItem = start.pop();
80 | String currentModuleName = topItem.fileNode.getAbsolutePath();
81 | currentModuleName = currentModuleName.substring(0, currentModuleName.lastIndexOf(URL_SPLIT));
82 | currentModuleName = currentModuleName.substring(currentModuleName.lastIndexOf(URL_SPLIT)+1);
83 | File currentModuleSource = new File(newProject + topItem.relativePath.toString() + URL_SPLIT + currentModuleName + URL_SPLIT + config.getString("source"));
84 | File oldModuleSource = new File(oldProject + topItem.relativePath.toString() + URL_SPLIT + currentModuleName + URL_SPLIT + config.getString("source"));
85 | // 判断当前module是否有源码
86 | if(currentModuleSource.exists() && currentModuleSource.listFiles().length > 0){
87 | if(oldModuleSource.exists() && oldModuleSource.listFiles().length > 0){
88 | normalModules.add(newProject + topItem.relativePath.toString() + URL_SPLIT + currentModuleName);
89 | }else{
90 | newModules.add(newProject + topItem.relativePath.toString() + URL_SPLIT + currentModuleName);
91 | }
92 | }
93 | List list = listModules(topItem.fileNode);
94 |
95 | for(String subModule:list){
96 | if(!URL_SPLIT.equals(PACKAGE_SPLIT)){
97 | subModule = subModule.replace(PACKAGE_SPLIT, URL_SPLIT);
98 | }
99 | String currentPath = topItem.relativePath.append(URL_SPLIT).append(currentModuleName).toString();
100 | start.push(new FileNode(
101 | new StringBuilder(currentPath),
102 | new File(newProject + currentPath + URL_SPLIT + subModule + URL_SPLIT + POM)
103 | ));
104 | }
105 | }
106 | Map> result = new HashMap<>();
107 | result.put("newModules", newModules);
108 | result.put("normalModules", normalModules);
109 | return result;
110 | }
111 |
112 | /**
113 | * 获取目录下所有文件的完整路径
114 | * @param dirPath
115 | * @return
116 | * @throws NotDirectoryException
117 | */
118 | public static List scanForDirectory(String dirPath) throws NotDirectoryException {
119 | List result = new ArrayList<>();
120 | File dirFile = new File(dirPath);
121 | if (!dirFile.exists() || !dirFile.isDirectory())
122 | throw new NotDirectoryException("「" + dirPath + "」不是文件夹");
123 | File[] files = dirFile.listFiles();
124 | if(files==null)
125 | return null;
126 | Stack stack = new Stack<>();
127 | for(File file:files)
128 | stack.push(new FileNode(new StringBuilder(""), file));
129 | while(!stack.empty()){
130 | FileNode topItem = stack.pop();
131 | if(topItem.fileNode.isFile()){
132 | String fileName = topItem.fileNode.getName();
133 | if(fileName.substring(fileName.lastIndexOf('.')).equals(".java") || fileName.substring(fileName.lastIndexOf('.')).equals(".class")){
134 | String topItemPath = topItem.relativePath.append(URL_SPLIT).append(fileName).toString();
135 | result.add(dirPath + topItemPath);
136 | }
137 | }else{
138 | String dirName = topItem.fileNode.getName();
139 | String topItemPath = topItem.relativePath.append(URL_SPLIT).append(dirName).toString();
140 | File[] fs = topItem.fileNode.listFiles();
141 | for(File f:fs)
142 | stack.push(new FileNode(new StringBuilder(topItemPath), f));
143 | }
144 | }
145 | return result;
146 | }
147 |
148 | /**
149 | * 比较两个文件夹的目录结构,得到所有被修改、新增的文件和文件夹
150 | * @param oldPath 旧路径,需要传入完整路径
151 | * @param newPath 新路径,需要传入完整路径
152 | * @return 只会记录新建的java文件、修改的java文件以及新建的文件夹,不会返回删除的内容
153 | * @throws NotDirectoryException
154 | */
155 | public static Map> compireToPath(String oldPath, String newPath) throws NotDirectoryException {
156 | File oldRoot = new File(oldPath);
157 | File newRoot = new File(newPath);
158 | if(!oldRoot.exists() || !newRoot.exists() || !oldRoot.isDirectory() || !newRoot.isDirectory())
159 | throw new NotDirectoryException("需要传入完整路径");
160 |
161 | Map> result = new HashMap<>();
162 | List newFiles = new ArrayList<>();
163 | List modifyFiles = new ArrayList<>();
164 | List newDirectorys = new ArrayList<>();
165 |
166 | Stack newStack = new Stack<>();
167 | // 初始化Stack
168 | File[] newRootFiles = newRoot.listFiles();
169 | if(newRootFiles==null)
170 | return null;
171 | for(File f:newRootFiles)
172 | newStack.push(new FileNode(new StringBuilder(""), f));
173 | while(!newStack.empty()){
174 | FileNode topItem = newStack.pop();
175 | if(topItem.fileNode.isFile()){
176 | // 文件类型只处理.java文件,其他的不管
177 | // 能来到这说明至少在oldPath中有同个文件夹
178 | String fileName = topItem.fileNode.getName();
179 | if(fileName.substring(fileName.lastIndexOf('.')).equals(".java")){
180 | // 检查 oldPath 中是否有此java文件,有的话再对比byte是否有修改
181 | String topItemPath = topItem.relativePath.append(URL_SPLIT).append(fileName).toString();
182 | File oldFile = new File(oldPath + topItemPath);
183 | if(oldFile.exists()){
184 | if(oldFile.length() != topItem.fileNode.length() || !Arrays.equals(getFileBytes(oldFile), getFileBytes(topItem.fileNode)))
185 | modifyFiles.add(topItemPath);
186 | }else{
187 | newFiles.add(topItemPath);
188 | }
189 | }
190 | }else{
191 | // 如果是文件夹,先判断oldPath里是否存在,不存在就是新增的文件夹,直接添加到newDirectory中,不用遍历文件
192 | // 如果不是新增的,则把文件夹内的元素解开,添加到Stack中
193 | String dirName = topItem.fileNode.getName();
194 | String topItemPath = topItem.relativePath.append(URL_SPLIT).append(dirName).toString();
195 | File oldDir = new File(oldPath + topItemPath);
196 | if(oldDir.exists()){
197 | File[] files = topItem.fileNode.listFiles();
198 | for(File file:files)
199 | newStack.push(new FileNode(new StringBuilder(topItemPath), file));
200 | }else{
201 | newDirectorys.add(topItemPath);
202 | }
203 | }
204 | }
205 |
206 | result.put("newFiles", newFiles);
207 | result.put("modifyFiles", modifyFiles);
208 | result.put("newDirectorys", newDirectorys);
209 | return result;
210 | }
211 |
212 | public static Map scanMybatisXml(String path) throws NotDirectoryException, DocumentException {
213 | Map result = new HashMap<>();
214 | File dirFile = new File(path);
215 | if (!dirFile.exists() || !dirFile.isDirectory())
216 | throw new NotDirectoryException("「" + path + "」不是文件夹");
217 | File[] files = dirFile.listFiles();
218 | if(files==null)
219 | return null;
220 | Stack stack = new Stack<>();
221 | for(File file:files)
222 | stack.push(new FileNode(new StringBuilder(""), file));
223 | while(!stack.empty()){
224 | FileNode topItem = stack.pop();
225 | if(topItem.fileNode.isFile()){
226 | String fileName = topItem.fileNode.getName();
227 | if(fileName.substring(fileName.lastIndexOf('.')).equals(".xml")){
228 | XmlDiffUtils xmlDiffUtils = new XmlDiffUtils(topItem.fileNode);
229 | if(xmlDiffUtils.isMapper()){
230 | String topItemPath = topItem.relativePath.append(URL_SPLIT).append(fileName).toString();
231 | result.put(xmlDiffUtils.getMapperNameSpace(), path + topItemPath);
232 | }
233 | }
234 | }else{
235 | String dirName = topItem.fileNode.getName();
236 | String topItemPath = topItem.relativePath.append(URL_SPLIT).append(dirName).toString();
237 | File[] fs = topItem.fileNode.listFiles();
238 | for(File f:fs)
239 | stack.push(new FileNode(new StringBuilder(topItemPath), f));
240 | }
241 | }
242 | return result;
243 | }
244 |
245 | private static List listModules(File pom) throws ParserConfigurationException, IOException, SAXException {
246 | List result = new ArrayList<>();
247 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
248 | Document document;
249 | DocumentBuilder db = dbf.newDocumentBuilder();
250 | document = db.parse(pom);
251 | NodeList modules = document.getElementsByTagName("module");
252 | if(modules.getLength() == 0){
253 | return result;
254 | }else{
255 | for(int i=0; i projectPackage = new HashSet<>();
8 | private static final Set projectMehtods = new HashSet<>();
9 | private static final Set requestAnnotation = new HashSet<>();
10 | private static final Set controllerAnnotation = new HashSet<>();
11 | private static final Set dubboAnnotation = new HashSet<>();
12 | private static final Set mybatisTagName = new HashSet<>();
13 | static {
14 | // Request的注解
15 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/RequestMapping;");
16 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/GetMapping;");
17 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/PostMapping;");
18 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/DeleteMapping;");
19 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/PatchMapping;");
20 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/PutMapping;");
21 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/MessageMapping;");
22 | requestAnnotation.add("Lorg/springframework/web/bind/annotation/SubscribeMapping;");
23 |
24 | // Controller 的注解
25 | controllerAnnotation.add("Lorg/springframework/web/bind/annotation/RestController;");
26 | controllerAnnotation.add("Lorg/springframework/stereotype/Controller;");
27 |
28 | // DubboService 的注解
29 | dubboAnnotation.add("Lorg/apache/dubbo/config/annotation/DubboService;");
30 | dubboAnnotation.add("Lcom/alibaba/dubbo/config/annotation/Service;");
31 | dubboAnnotation.add("Lorg/apache/dubbo/config/annotation/Service;");
32 |
33 | // mybatis的xml配置的tagName
34 | mybatisTagName.add("insert");
35 | mybatisTagName.add("delete");
36 | mybatisTagName.add("update");
37 | mybatisTagName.add("select");
38 | }
39 |
40 | public static boolean isNeedInject(String className){
41 | if(null == className)
42 | return false;
43 | for(String prefix : projectPackage){
44 | if(className.equals(prefix))
45 | return true;
46 | }
47 | return false;
48 | }
49 |
50 | public static boolean isNeedInjectMethod(String methodName){
51 | if(null == methodName)
52 | return false;
53 | return projectMehtods.contains(methodName);
54 | }
55 |
56 | public static boolean isControllerAnnotation(String annoDesc){
57 | if(null == annoDesc)
58 | return false;
59 | return controllerAnnotation.contains(annoDesc);
60 | }
61 |
62 | public static boolean isRequestAnnotation(String annoDesc){
63 | if(null == annoDesc)
64 | return false;
65 | return requestAnnotation.contains(annoDesc);
66 | }
67 |
68 | public static boolean isDubboAnnotation(String annoDesc){
69 | if(null == annoDesc)
70 | return false;
71 | return dubboAnnotation.contains(annoDesc);
72 | }
73 |
74 | public static boolean isCURD(String tagName){
75 | if(null == tagName)
76 | return false;
77 | return mybatisTagName.contains(tagName);
78 | }
79 |
80 | public static void addProjectPackage(String className){
81 | projectPackage.add(className);
82 | }
83 | public static void addProjectMethod(String fullMethodName){
84 | projectMehtods.add(fullMethodName);
85 | }
86 |
87 | public static Set getProjectPackage() {
88 | return projectPackage;
89 | }
90 |
91 | public static Set getProjectMehtods() {
92 | return projectMehtods;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Utils/FullMethodNameUtil.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Utils;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Stack;
6 | import java.util.regex.Matcher;
7 | import java.util.regex.Pattern;
8 |
9 | import static com.diff.core.Common.Code.PACKAGE_SPLIT;
10 | import static com.diff.core.Common.Code.descriptorMap;
11 |
12 | public class FullMethodNameUtil {
13 | public static void main(String[] args){
14 | String test = "Ljava/lang/String;Ljava/lang/String;";
15 | String test1 = "Ljava/lang/String;Ljava/lang/String;[Ljava/util/Map;";
16 | String test2 = "Ljava/lang/String;[Ljava/lang/String;[Ljava/util/Map;>;";
17 | System.out.println(genericsParameter(test2));
18 | }
19 |
20 | public static String getMethodSignatureName(String name, String descriptor){
21 | StringBuilder signa = new StringBuilder();
22 | signa.append(name);
23 | signa.append("(");
24 | String patter = "(L.*?;|\\[{0,2}L.*?;|[ZCBSIFJDV]|\\[{0,2}[ZCBSIFJDV]{1})";
25 | Matcher parameterMatcher = Pattern.compile(patter).matcher(descriptor.substring(0, descriptor.lastIndexOf(')') + 1));
26 | while(parameterMatcher.find()){
27 | String param = parameterMatcher.group(1);
28 | if(param.length()==1){
29 | // V
30 | signa.append(descriptorMap.get(param)).append(", ");
31 | }else{
32 | String type = param.substring(0,1);
33 | if(descriptorMap.get(param.substring(1)) != null){
34 | // [V
35 | signa.append(descriptorMap.get(param.substring(1))).append(", ");
36 | }else{
37 | // Ljava/lang/Object; [Ljava/lang/Object;
38 | String typeName = param.substring(param.lastIndexOf(PACKAGE_SPLIT) + 1, param.length()-1);
39 | signa.append(typeName).append(descriptorMap.get(type)).append(", ");
40 | }
41 | }
42 | }
43 | if(!descriptor.startsWith("()"))
44 | signa.delete(signa.length()-2, signa.length());
45 | signa.append(")");
46 | String returnType = descriptor.substring(descriptor.lastIndexOf(')') + 1);
47 | if(returnType.length()==1){
48 | signa.append(descriptorMap.get(returnType));
49 | }else{
50 | String type = returnType.substring(0,1);
51 | if(descriptorMap.get(returnType.substring(1)) != null){
52 | signa.append(descriptorMap.get(returnType.substring(1))).append("[]");
53 | }else{
54 | String typeName = returnType.substring(returnType.lastIndexOf(PACKAGE_SPLIT) + 1, returnType.length()-1);
55 | signa.append(typeName).append(descriptorMap.get(type));
56 | }
57 | }
58 |
59 | return signa.toString();
60 | }
61 |
62 | public static String getMethodSignatureName(String name, String descriptor, String signature){
63 | StringBuilder fullName = new StringBuilder("");
64 | fullName.append(name).append("(");
65 | String pattern = "(L[^;]+<.*?(>;)+|\\[{0,2}L[^;]+<.*?(>;)+|L.*?;|\\[{0,2}L.*?;|[ZCBSIFJDV]|\\[{0,2}[ZCBSIFJDV]{1})";
66 | Matcher matcher;
67 | if(signature != null){
68 | matcher = Pattern.compile(pattern).matcher(signature);
69 | }else{
70 | matcher = Pattern.compile(pattern).matcher(descriptor);
71 | }
72 | while(matcher.find()){
73 | String parameter = matcher.group(1);
74 | if(parameter.length()==1){
75 | // V
76 | fullName.append(descriptorMap.get(parameter)).append(", ");
77 | }else{
78 | String type = parameter.substring(0,1);
79 | if(descriptorMap.get(parameter.substring(1)) != null){
80 | // [V
81 | fullName.append(descriptorMap.get(parameter.substring(1))).append(", ");
82 | }else{
83 | // Ljava/lang/Object; [Ljava/lang/Object; [Ljava/util/Map;
84 | if(parameter.contains("<")){
85 | // [Ljava/util/Map;
86 | String tmp = parameter.substring(0, parameter.indexOf("<"));
87 | String typeName = tmp.substring(tmp.lastIndexOf(PACKAGE_SPLIT) + 1)
88 | + FullMethodNameUtil.genericsParameter(parameter.substring(
89 | parameter.indexOf("<")+1, parameter.length()-2
90 | ));
91 |
92 | }
93 | }
94 | }
95 | }
96 |
97 | return fullName.toString();
98 | }
99 |
100 | /**
101 | * 栈+循环处理泛型的入参
102 | * @param signature "Ljava/lang/String;Ljava/lang/String;"
103 | * @return "" ">"
104 | */
105 | public static String genericsParameter(String signature){
106 | // <*> > <>
107 | if(signature.length() < 2)
108 | return signature;
109 | StringBuilder result = new StringBuilder("<");
110 | List parameterList = new ArrayList<>();
111 | Stack mainStack = new Stack<>();
112 | Stack subStack = new Stack<>();
113 | Pattern pattern = Pattern.compile("(L[^;]+<.*?(>;)+|\\[{0,2}L[^;]+<.*?(>;)+|L.*?;|\\[{0,2}L.*?;|[ZCBSIFJDV]|\\[{0,2}[ZCBSIFJDV]{1})");
114 | Matcher matcher = pattern.matcher(signature);
115 | // 初始化stack
116 | while(matcher.find())
117 | mainStack.push(matcher.group(1));
118 | while(!mainStack.empty()){
119 | // 遍历stack处理泛型的入参
120 | String parameter = mainStack.pop();
121 | if(parameter.contains("<")){
122 | // 又是带泛型的参数,解包并加入mainStack,subStack相应地加入本节点的className Node
123 | Matcher subMatcher = pattern.matcher(parameter.substring(parameter.indexOf('<')+1, parameter.length()-2));
124 | String className = parameter.substring(0, parameter.indexOf("<"));
125 | int count = 0;
126 | while(subMatcher.find()){
127 | count++;
128 | String find = subMatcher.group(1);
129 | mainStack.push(find);
130 | }
131 | subStack.push(new Node(className, count));
132 | }else{
133 | // 普通参数
134 | String name = parameter.substring(parameter.lastIndexOf(PACKAGE_SPLIT)+1, parameter.length()-1);
135 | if(descriptorMap.get(parameter.substring(0,1)).equals("[]"))
136 | name += "[]";
137 | if(!subStack.empty()){
138 | Node top = subStack.peek();
139 | top.push(name);
140 | if(top.isEnd()){
141 | subStack.pop();
142 | if(!subStack.empty())
143 | subStack.peek().push(top.getFullName());
144 | else
145 | parameterList.add(top.getFullName());
146 | }
147 | }else{
148 | parameterList.add(name);
149 | }
150 | }
151 | }
152 |
153 | for(int i=parameterList.size()-1;i>=0;i--){
154 | if(result.toString().equals("<"))
155 | result.append(parameterList.get(i));
156 | else
157 | result.append(", ").append(parameterList.get(i));
158 | }
159 | result.append(">");
160 | return result.toString();
161 | }
162 |
163 | private static class Node{
164 | private final String className;
165 | private int count = 0 ;
166 | private List subParameter = new ArrayList<>();
167 | public Node(String className, int count){
168 | this.className = className;
169 | this.count = count;
170 | }
171 |
172 | public String getClassName() {
173 | return className;
174 | }
175 |
176 | public int getCount() {
177 | return count;
178 | }
179 |
180 | public void push(String parameter){
181 | subParameter.add(parameter);
182 | count--;
183 | }
184 |
185 | public String getFullName() {
186 | StringBuilder stringBuilder = new StringBuilder("<");
187 | for(int i=subParameter.size()-1;i>=0;i--){
188 | if(stringBuilder.toString().equals("<"))
189 | stringBuilder.append(subParameter.get(i));
190 | else
191 | stringBuilder.append(", ").append(subParameter.get(i));
192 | }
193 | stringBuilder.append(">");
194 |
195 | if(descriptorMap.get(className.substring(0,1)).equals("[]")){
196 | stringBuilder.append("[]");
197 | }
198 | return className.substring(className.lastIndexOf(PACKAGE_SPLIT)+1) + stringBuilder;
199 | }
200 |
201 | public boolean isEnd(){
202 | return count == 0;
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Utils/ParseUtil.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Utils;
2 |
3 | import com.diff.core.Common.Config;
4 | import com.diff.core.Visitors.MethodVisitorAdapter;
5 | import com.github.javaparser.StaticJavaParser;
6 | import com.github.javaparser.ast.CompilationUnit;
7 | import com.github.javaparser.ast.Node;
8 | import com.github.javaparser.ast.body.MethodDeclaration;
9 | import com.github.javaparser.printer.YamlPrinter;
10 |
11 | import java.io.File;
12 | import java.io.FileNotFoundException;
13 | import java.util.ArrayList;
14 | import java.util.HashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | import static com.diff.core.Common.Code.*;
19 |
20 | public class ParseUtil {
21 |
22 | /**
23 | * test
24 | * @param args
25 | * @throws FileNotFoundException
26 | */
27 | public static void main(String[] args) throws FileNotFoundException {
28 | CompilationUnit cu1 = StaticJavaParser.parse(new File(Config.getInstance().getString("oldProjectPath") + "/src/main/java/com/bot/server/qqBot/server/msgManage.java"));
29 | YamlPrinter printer = new YamlPrinter(true);
30 | System.out.println(printer.output(cu1));
31 | }
32 |
33 | /**
34 | * 对比两个方法之间是否存在差异,弃用
35 | * @param oldFile
36 | * @param newFile
37 | * @return
38 | * @throws FileNotFoundException
39 | */
40 | public static Map compireToMethod(File oldFile, File newFile) throws FileNotFoundException {
41 | Config config = Config.getInstance();
42 | CompilationUnit oldCompilationUnit = StaticJavaParser.parse(oldFile);
43 | CompilationUnit newCompilationUnit = StaticJavaParser.parse(newFile);
44 |
45 | MethodVisitorAdapter oldMethodVisitor = new MethodVisitorAdapter();
46 | MethodVisitorAdapter newMethodVisitor = new MethodVisitorAdapter();
47 | // 清除注释
48 | oldCompilationUnit.getAllContainedComments().forEach(Node::remove);
49 | newCompilationUnit.getAllContainedComments().forEach(Node::remove);
50 |
51 | String oldArg = oldFile.getAbsolutePath().substring(
52 | (config.getString("oldProjectPath") + config.getString("source")).length() + 2,
53 | oldFile.getAbsolutePath().lastIndexOf(URL_SPLIT) + 1
54 | );
55 | String newArg = newFile.getAbsolutePath().substring(
56 | (config.getString("newProjectPath") + config.getString("source")).length() + 2,
57 | newFile.getAbsolutePath().lastIndexOf(URL_SPLIT) + 1
58 | );
59 | if(!URL_SPLIT.equals(PACKAGE_SPLIT)){
60 | // 兼容windows的路径,arg需要输入包名
61 | oldArg = oldArg.replace(URL_SPLIT, PACKAGE_SPLIT);
62 | newArg = newArg.replace(URL_SPLIT, PACKAGE_SPLIT);
63 | }
64 |
65 | oldMethodVisitor.visit(oldCompilationUnit, oldArg);
66 | newMethodVisitor.visit(newCompilationUnit, newArg);
67 |
68 | Map compireResult = new HashMap<>();
69 | for(String name:newMethodVisitor.getMds().keySet()){
70 | MethodDeclaration methodDeclaration = oldMethodVisitor.getMds().get(name);
71 | if(methodDeclaration != null)
72 | compireResult.put(name, methodDeclaration.equals(newMethodVisitor.getMds().get(name)));
73 | else
74 | compireResult.put(name, Boolean.FALSE);
75 | }
76 |
77 | return compireResult;
78 | }
79 |
80 | /**
81 | * 对比两个方法之间是否存在差异
82 | * @param oldFile
83 | * @param newFile
84 | * @return
85 | * @throws FileNotFoundException
86 | */
87 | public static Map compireToMethod(File oldFile, File newFile, String moduleName) throws FileNotFoundException {
88 | Config config = Config.getInstance();
89 | CompilationUnit oldCompilationUnit = StaticJavaParser.parse(oldFile);
90 | CompilationUnit newCompilationUnit = StaticJavaParser.parse(newFile);
91 |
92 | MethodVisitorAdapter oldMethodVisitor = new MethodVisitorAdapter();
93 | MethodVisitorAdapter newMethodVisitor = new MethodVisitorAdapter();
94 | // 清除注释
95 | oldCompilationUnit.getAllContainedComments().forEach(Node::remove);
96 | newCompilationUnit.getAllContainedComments().forEach(Node::remove);
97 |
98 | int offset = moduleName.equals("") ? 2:3;
99 | String oldArg = oldFile.getAbsolutePath().substring(
100 | (config.getString("oldProjectPath") + moduleName + config.getString("source")).length() + offset,
101 | oldFile.getAbsolutePath().lastIndexOf(URL_SPLIT) + 1
102 | );
103 | String newArg = newFile.getAbsolutePath().substring(
104 | (config.getString("newProjectPath") + moduleName + config.getString("source")).length() + offset,
105 | newFile.getAbsolutePath().lastIndexOf(URL_SPLIT) + 1
106 | );
107 | if(!URL_SPLIT.equals(PACKAGE_SPLIT)){
108 | // 兼容windows的路径,arg需要输入包名
109 | oldArg = oldArg.replace(URL_SPLIT, PACKAGE_SPLIT);
110 | newArg = newArg.replace(URL_SPLIT, PACKAGE_SPLIT);
111 | }
112 |
113 | oldMethodVisitor.visit(oldCompilationUnit, oldArg);
114 | newMethodVisitor.visit(newCompilationUnit, newArg);
115 |
116 | Map compireResult = new HashMap<>();
117 | for(String name:newMethodVisitor.getMds().keySet()){
118 | MethodDeclaration methodDeclaration = oldMethodVisitor.getMds().get(name);
119 | if(methodDeclaration != null)
120 | compireResult.put(name, methodDeclaration.equals(newMethodVisitor.getMds().get(name)));
121 | else
122 | compireResult.put(name, Boolean.FALSE);
123 | }
124 |
125 | return compireResult;
126 | }
127 |
128 | /**
129 | * 用于检索新项目中的java方法
130 | * @param classFilePath
131 | * @return
132 | * @throws FileNotFoundException
133 | */
134 | public static List scanMethods(String classFilePath)throws FileNotFoundException{
135 | File file = new File(classFilePath);
136 | Config config = Config.getInstance();
137 | CompilationUnit compilationUnit = StaticJavaParser.parse(file);
138 | MethodVisitorAdapter methodVisitor = new MethodVisitorAdapter();
139 | String arg = classFilePath.substring(
140 | classFilePath.indexOf(config.getString("source")) + config.getString("source").length() + 1,
141 | classFilePath.lastIndexOf(URL_SPLIT) + 1
142 | );
143 | if(!URL_SPLIT.equals(PACKAGE_SPLIT))
144 | arg = arg.replace(URL_SPLIT, PACKAGE_SPLIT);
145 | methodVisitor.visit(compilationUnit, arg);
146 | return new ArrayList<>(methodVisitor.getMds().keySet());
147 | }
148 | }
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Utils/ProjectChainUtils.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Utils;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.diff.core.Common.Config;
5 | import com.diff.core.Recorders.ControllerRecord;
6 | import com.diff.core.Recorders.DubboRecord;
7 | import org.apache.commons.io.FileUtils;
8 | import org.dom4j.DocumentException;
9 |
10 | import java.io.File;
11 | import java.io.FileNotFoundException;
12 | import java.io.IOException;
13 | import java.net.URL;
14 | import java.nio.file.NotDirectoryException;
15 | import java.util.*;
16 |
17 | import static com.diff.core.Common.Code.*;
18 |
19 | public class ProjectChainUtils {
20 | public static Map getProjectChainFromPath() throws IOException {
21 | Config config = Config.getInstance();
22 | String rootDir = config.getString("newProjectPath") + URL_SPLIT + config.getString("target");
23 | List filePaths = FileTreeUtil.scanForDirectory(rootDir);
24 | Map>> relationShips = new HashMap<>();
25 |
26 | for(String filePath:filePaths){
27 | // 扫描项目全部class
28 | ChainUtils.scanForClassName(FileUtils.readFileToByteArray(new File(filePath)));
29 | }
30 |
31 | for(String filePath:filePaths){
32 | // 扫描方法之间的调用关系
33 | String className = filePath.substring(rootDir.length()+1,filePath.lastIndexOf('.'));
34 | if(!URL_SPLIT.equals(PACKAGE_SPLIT))
35 | className = className.replace(URL_SPLIT, PACKAGE_SPLIT);
36 | relationShips.put(className, ChainUtils.getRelationShipFromClassBuffer(FileUtils.readFileToByteArray(new File(filePath))));
37 | }
38 | Map result = new HashMap<>();
39 | ControllerRecord controllerRecord = ControllerRecord.getInstance();
40 | for(String controllerName:controllerRecord.getControllers()){
41 | for(String methodName:controllerRecord.getApiFromControlClassName(controllerName)){
42 | String fullMethodName = controllerName + METHOD_SPLIT + methodName;
43 | result.put(fullMethodName, ChainUtils.getJSONChainFromRelationShip(relationShips, fullMethodName));
44 | }
45 | }
46 | return result;
47 | }
48 |
49 | public static List getProjectUpdateMethod(String oldProject, String newProject) throws NotDirectoryException, FileNotFoundException {
50 | List result = new ArrayList<>();
51 | Config config = Config.getInstance();
52 | Map> classDiff = FileTreeUtil.compireToPath(
53 | oldProject + URL_SPLIT + config.getString("source"),
54 | newProject + URL_SPLIT + config.getString("source")
55 | );
56 | List scanFiles = new ArrayList<>();
57 |
58 | List newDirectorys = classDiff.get("newDirectorys");
59 | List newFiles = classDiff.get("newFiles");
60 | List modifyFiles = classDiff.get("modifyFiles");
61 |
62 | for(String directory:newDirectorys){
63 | List files = FileTreeUtil.scanForDirectory(newProject + URL_SPLIT + config.getString("source") + directory);
64 | if(files != null)
65 | scanFiles.addAll(files);
66 | }
67 | // 对比 modifyFiles 获取有修改的method
68 | for(String file:modifyFiles){
69 | Map modifyMethods = ParseUtil.compireToMethod(
70 | new File(oldProject + URL_SPLIT + config.getString("source") + file),
71 | new File(newProject + URL_SPLIT + config.getString("source") + file)
72 | );
73 | for(String method:modifyMethods.keySet()){
74 | if(!modifyMethods.get(method)){
75 | result.add(method);
76 | }
77 | }
78 | }
79 | // 新目录下的所有method
80 | for(String file:scanFiles){
81 | List scanMethods = ParseUtil.scanMethods(file);
82 | result.addAll(scanMethods);
83 | }
84 | // 新java文件的所有method
85 | for(String file:newFiles){
86 | List newMethods = ParseUtil.scanMethods(
87 | newProject + URL_SPLIT + config.getString("source") + file
88 | );
89 | result.addAll(newMethods);
90 | }
91 | return result;
92 | }
93 |
94 | public static Map getProjectChainFromPath(List modules) throws Exception {
95 | Config config = Config.getInstance();
96 | Map result = new HashMap<>();
97 | Map>> relationShips = new HashMap<>();
98 |
99 | for(String module:modules){
100 | String rootDir = module + URL_SPLIT + config.getString("target");
101 | List filePaths = FileTreeUtil.scanForDirectory(rootDir);
102 | for(String filePath:filePaths){
103 | // 扫描当前模块全部class
104 | ChainUtils.scanForClassName(FileUtils.readFileToByteArray(new File(filePath)));
105 | }
106 | }
107 |
108 | for(String module:modules){
109 | String rootDir = module + URL_SPLIT + config.getString("target");
110 | List filePaths = FileTreeUtil.scanForDirectory(rootDir);
111 | for(String filePath:filePaths){
112 | // 扫描方法之间的调用关系
113 | String className = filePath.substring(rootDir.length()+1,filePath.lastIndexOf('.'));
114 | if(!URL_SPLIT.equals(PACKAGE_SPLIT))
115 | className = className.replace(URL_SPLIT, PACKAGE_SPLIT);
116 | relationShips.put(className, ChainUtils.getRelationShipFromClassBuffer(FileUtils.readFileToByteArray(new File(filePath))));
117 | }
118 | }
119 | ControllerRecord controllerRecord = ControllerRecord.getInstance();
120 | for(String controllerName:controllerRecord.getControllers()){
121 | for(String methodName:controllerRecord.getApiFromControlClassName(controllerName)){
122 | String fullMethodName = controllerName + METHOD_SPLIT + methodName;
123 | result.put(fullMethodName, ChainUtils.getJSONChainFromRelationShip(relationShips, fullMethodName));
124 | }
125 | }
126 | // 扫描dubbo调用链
127 | for(String dubboMethodName: DubboRecord.getList()){
128 | result.put(dubboMethodName, ChainUtils.getJSONChainFromRelationShip(relationShips, dubboMethodName));
129 | }
130 | return result;
131 | }
132 |
133 | public static List getProjectUpdateMethod(List modules) throws NotDirectoryException, FileNotFoundException, DocumentException {
134 | Config config = Config.getInstance();
135 | List result = new ArrayList<>();
136 | String newProject = config.getString("newProjectPath");
137 | String oldProject = config.getString("oldProjectPath");
138 | for(String module:modules){
139 | String oldModule = oldProject + module.substring(newProject.length());
140 | Map> classDiff = FileTreeUtil.compireToPath(
141 | oldModule + URL_SPLIT + config.getString("source"),
142 | module + URL_SPLIT + config.getString("source")
143 | );
144 | List newDirectorys = classDiff.get("newDirectorys");
145 | List newFiles = classDiff.get("newFiles");
146 | List modifyFiles = classDiff.get("modifyFiles");
147 | List scanFiles = new ArrayList<>();
148 |
149 | for(String directory:newDirectorys){
150 | List files = FileTreeUtil.scanForDirectory(module + URL_SPLIT + config.getString("source") + directory);
151 | if(files != null)
152 | scanFiles.addAll(files);
153 | }
154 | // 对比 modifyFiles 获取有修改的method
155 | for(String file:modifyFiles){
156 | String moduleName = module.equals(newProject) ? "" : module.substring(newProject.length()+1);
157 | Map modifyMethods = ParseUtil.compireToMethod(
158 | new File(oldModule + URL_SPLIT + config.getString("source") + file),
159 | new File(module + URL_SPLIT + config.getString("source") + file),
160 | moduleName
161 | );
162 | for(String method:modifyMethods.keySet()){
163 | if(!modifyMethods.get(method)){
164 | result.add(method);
165 | }
166 | }
167 | }
168 | // 新目录下的所有method
169 | for(String file:scanFiles){
170 | List scanMethods = ParseUtil.scanMethods(file);
171 | result.addAll(scanMethods);
172 | }
173 | // 新java文件的所有method
174 | for(String file:newFiles){
175 | List newMethods = ParseUtil.scanMethods(
176 | module + URL_SPLIT + config.getString("source") + file
177 | );
178 | result.addAll(newMethods);
179 | }
180 | // 检索所有mybatis xml配置
181 | String oldResource = oldModule + URL_SPLIT + config.getString("resources");
182 | String newResource = module + URL_SPLIT + config.getString("resources");
183 |
184 | if(new File(oldResource).exists() && new File(newResource).exists()){
185 | Map oldMybatisXml = FileTreeUtil.scanMybatisXml(oldResource);
186 | Map newMybatisXml = FileTreeUtil.scanMybatisXml(newResource);
187 |
188 | for(String xml:newMybatisXml.keySet()){
189 | if(oldMybatisXml.containsKey(xml)){
190 | List xmlDiff = XmlDiffUtils.compireXml(oldMybatisXml.get(xml), newMybatisXml.get(xml));
191 | for(String methodName:xmlDiff){
192 | List methods = ParseUtil.scanMethods(
193 | module + URL_SPLIT + config.getString("source") + URL_SPLIT + methodName.substring(0, methodName.lastIndexOf(METHOD_SPLIT)) + ".java"
194 | );
195 | for(String fullMethodName:methods){
196 | if(fullMethodName.startsWith(methodName)){
197 | result.add(fullMethodName);
198 | }
199 | }
200 | }
201 | }
202 | }
203 | }
204 | }
205 | return result;
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Utils/XmlDiffUtils.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Utils;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import com.alibaba.fastjson.JSONObject;
5 | import org.dom4j.Attribute;
6 | import org.dom4j.DocumentException;
7 | import org.dom4j.Element;
8 | import org.dom4j.io.SAXReader;
9 | import org.dom4j.Document;
10 |
11 | import java.io.File;
12 | import java.util.*;
13 |
14 | import static com.diff.core.Common.Code.*;
15 |
16 | public class XmlDiffUtils {
17 | Element root;
18 |
19 | public XmlDiffUtils(File file) throws DocumentException {
20 | SAXReader reader = new SAXReader();
21 | Document document = reader.read(file);
22 | root = document.getRootElement();
23 | }
24 |
25 | /**
26 | * 只记录有修改的,新增、删除的不记录,因为这种在代码层面已经会被记录
27 | * @param oldFile
28 | * @param newFile
29 | * @throws DocumentException
30 | */
31 | public static List compireXml(String oldFile, String newFile) throws DocumentException {
32 | SAXReader reader = new SAXReader();
33 | Document oldDocument = reader.read(new File(oldFile));
34 | Document newDocument = reader.read(new File(newFile));
35 | Element oldRoot = oldDocument.getRootElement();
36 | Element newRoot = newDocument.getRootElement();
37 | String className = newRoot.attribute("namespace").getStringValue();
38 | className = className.replace(".", PACKAGE_SPLIT);
39 | List result = new ArrayList<>();
40 |
41 | Iterator oldIterator = oldRoot.elementIterator();
42 | Iterator newIterator = newRoot.elementIterator();
43 |
44 | Map oldCURD = new HashMap<>();
45 | Map newCURD = new HashMap<>();
46 |
47 | while(oldIterator.hasNext()){
48 | Element child = (Element) oldIterator.next();
49 | if(FilterUtils.isCURD(child.getName())){
50 | JSONObject info = getElementInfo(child);
51 | oldCURD.put(info.getString("id"), info);
52 | }
53 | }
54 | while(newIterator.hasNext()){
55 | Element child = (Element) newIterator.next();
56 | if(FilterUtils.isCURD(child.getName())){
57 | JSONObject info = getElementInfo(child);
58 | newCURD.put(info.getString("id"), info);
59 | }
60 | }
61 | for(String id:newCURD.keySet()){
62 | if(
63 | oldCURD.keySet().contains(id)
64 | && !oldCURD.get(id).toJSONString().equals(newCURD.get(id).toJSONString())
65 | ){
66 | result.add(className + METHOD_SPLIT + id);
67 | }
68 | }
69 | return result;
70 | }
71 |
72 | public static JSONObject getElementInfo(Element element){
73 | JSONObject result = new JSONObject();
74 | result.put("tagName", element.getName());
75 | result.put("innerText", element.getStringValue());
76 | List attributeList = element.attributes();
77 | for(Attribute attribute:attributeList){
78 | result.put(attribute.getName(), attribute.getValue());
79 | }
80 |
81 | JSONArray children = new JSONArray();
82 |
83 | Stack recursion = new Stack<>();
84 | Iterator iterator = element.elementIterator();
85 | while(iterator.hasNext()){
86 | recursion.push(new ElementNode((Element) iterator.next(), children));
87 | }
88 |
89 | while(!recursion.empty()){
90 | ElementNode topItem = recursion.pop();
91 | JSONObject descriptor = new JSONObject();
92 | descriptor.put("tagName", topItem.element.getName());
93 | List tmpAttributeList = topItem.element.attributes();
94 | for(Attribute attribute:tmpAttributeList){
95 | descriptor.put(attribute.getName(), attribute.getValue());
96 | }
97 | Iterator tmpIterator = topItem.element.elementIterator();
98 | JSONArray tmpArray = new JSONArray();
99 | while(tmpIterator.hasNext()){
100 | recursion.push(new ElementNode((Element) tmpIterator.next(), tmpArray));
101 | }
102 | descriptor.put("children", tmpArray);
103 | topItem.json.add(descriptor);
104 | }
105 | result.put("children", children);
106 | return result;
107 | }
108 |
109 | public boolean isMapper(){
110 | return root.getName().equals("mapper");
111 | }
112 | public String getMapperNameSpace(){
113 | return root.attribute("namespace").getValue();
114 | }
115 |
116 | private static class ElementNode{
117 | private Element element;
118 | private JSONArray json;
119 | private ElementNode(Element element, JSONArray json){
120 | this.element = element;
121 | this.json = json;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Visitors/AsmAnnotationVisitor.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Visitors;
2 |
3 | import org.objectweb.asm.AnnotationVisitor;
4 |
5 | import java.util.Set;
6 |
7 | import static org.objectweb.asm.Opcodes.ASM7;
8 | import static com.diff.core.Common.Code.*;
9 |
10 | public class AsmAnnotationVisitor extends AnnotationVisitor {
11 | // 记录自己的requestMappingValue
12 | private Set paths;
13 | // 上一级的requestMappingValue(一般是类的RequestMapping)
14 | private Set parentPaths;
15 | // 判断是否有value
16 | private boolean hasValue = false;
17 |
18 | public AsmAnnotationVisitor(AnnotationVisitor av, Set paths, Set parentPaths){
19 | super(ASM7, av);
20 | this.paths = paths;
21 | this.parentPaths = parentPaths;
22 | }
23 |
24 | @Override
25 | public AnnotationVisitor visitArray(String name){
26 | // 主要提供给 annotationVisitor0 访问
27 | if(name.equals("value")){
28 | hasValue = true;
29 | return new AsmAnnotationVisitor(super.visitArray(name), paths, parentPaths);
30 | }
31 | return super.visitArray(name);
32 | }
33 |
34 | @Override
35 | public void visit(String name, Object value){
36 | // 主要提供给 annotationVisitor1 访问
37 | if(parentPaths.size() > 0){
38 | for(String parentPath:parentPaths)
39 | paths.add(parentPath + (String) value);
40 | }else{
41 | paths.add((String) value);
42 | }
43 | super.visit(name, value);
44 | }
45 |
46 | @Override
47 | public void visitEnd(){
48 | if(!hasValue && paths.size() == 0){
49 | paths.addAll(parentPaths);
50 | }
51 | super.visitEnd();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Visitors/AsmClassVisitor.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Visitors;
2 |
3 | import com.diff.core.Recorders.AbstractRecord;
4 | import com.diff.core.Recorders.ControllerRecord;
5 | import com.diff.core.Recorders.InterfaceRecord;
6 | import com.diff.core.Utils.FilterUtils;
7 | import com.diff.core.Utils.FullMethodNameUtil;
8 | import org.objectweb.asm.AnnotationVisitor;
9 | import org.objectweb.asm.ClassVisitor;
10 | import org.objectweb.asm.MethodVisitor;
11 |
12 | import java.util.*;
13 |
14 | import static org.objectweb.asm.Opcodes.*;
15 |
16 | public class AsmClassVisitor extends ClassVisitor {
17 | private String className;
18 |
19 | private Map> methodRelations = new HashMap<>();
20 | private InterfaceRecord interfaceRecord = InterfaceRecord.getInstance();
21 | private AbstractRecord abstractRecord = AbstractRecord.getInstance();
22 | private boolean isController = false;
23 | private boolean hasRequestMapping = false;
24 | private boolean isInterface;
25 | // 记录自己的 requestMapping::Value
26 | private Set requestMappingValue;
27 | // 记录全部子方法的api入口
28 | private Map> recordMapping = new HashMap<>();
29 |
30 | public AsmClassVisitor(ClassVisitor cv){
31 | super(ASM7, cv);
32 | }
33 |
34 | @Override
35 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces){
36 | // if(name.contains("testInterface") || name.contains("testAbstract"))
37 | // System.out.println("haha");
38 | className = name;
39 | // 如果是接口的实体类,则加入interfaceRecord
40 | if(interfaces.length > 0){
41 | for(String interfaceClassName:interfaces) {
42 | if (FilterUtils.isNeedInject(interfaceClassName)) {
43 | interfaceRecord.putInterfaceEntry(interfaceClassName, name);
44 | }
45 | }
46 | }
47 | // 如果是abstract类的实体类...
48 | if(abstractRecord.containAbstract(superName)){
49 | abstractRecord.putAbstractEntry(superName, name);
50 | }
51 | super.visit(version, access, name, signature, superName, interfaces);
52 | }
53 |
54 | @Override
55 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){
56 | List methodRelation = new ArrayList<>();
57 | // 处理一下方法名,适配javaparser
58 | String signa = FullMethodNameUtil.getMethodSignatureName(name, descriptor);
59 | methodRelations.put(signa, methodRelation);
60 | if(hasRequestMapping){
61 | return new AsmMethodAdapter(access, name, descriptor, super.visitMethod(access,name,descriptor,signature,exceptions), methodRelation, className, requestMappingValue, recordMapping);
62 | }else{
63 | // 类没有 RequestMapping 则parentPath为空的Set
64 | return new AsmMethodAdapter(access, name, descriptor, super.visitMethod(access,name,descriptor,signature,exceptions), methodRelation, className, new HashSet<>(), recordMapping);
65 | }
66 | }
67 |
68 | @Override
69 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visiable){
70 | if(FilterUtils.isControllerAnnotation(descriptor)){
71 | // controller类
72 | ControllerRecord.getInstance().putControlClass(className);
73 | isController = true;
74 | }
75 | if(FilterUtils.isRequestAnnotation(descriptor)){
76 | hasRequestMapping = true;
77 | requestMappingValue = new HashSet<>();
78 | Set parentPath = new HashSet<>();
79 | // 这里去获取类的 requestMappingValue
80 | return new AsmAnnotationVisitor(super.visitAnnotation(descriptor, visiable), requestMappingValue, parentPath);
81 | }
82 | return super.visitAnnotation(descriptor, visiable);
83 | }
84 |
85 | @Override
86 | public void visitEnd(){
87 | super.visitEnd();
88 | }
89 |
90 | public Map> getMethodRelations() {
91 | return methodRelations;
92 | }
93 |
94 | public Map> getRecordMapping() {
95 | return recordMapping;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Visitors/AsmMethodAdapter.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Visitors;
2 |
3 | import com.diff.core.Recorders.ApiRecord;
4 | import com.diff.core.Recorders.ControllerRecord;
5 | import com.diff.core.Utils.FilterUtils;
6 | import com.diff.core.Utils.FullMethodNameUtil;
7 | import org.objectweb.asm.AnnotationVisitor;
8 | import org.objectweb.asm.MethodVisitor;
9 | import org.objectweb.asm.commons.AdviceAdapter;
10 |
11 | import java.util.*;
12 |
13 | import static com.diff.core.Common.Code.*;
14 |
15 | public class AsmMethodAdapter extends AdviceAdapter {
16 | private String className;
17 | private String methodName;
18 | private String desc;
19 | private List relationShip;
20 | private Set parentPath;
21 | private Set requestMappingValue;
22 | private Map> recordMapping;
23 |
24 | public AsmMethodAdapter(int access, String methodName, String desc, MethodVisitor mv, List relationShip, String className){
25 | super(ASM7, mv, access, methodName, desc);
26 | this.methodName = methodName;
27 | this.relationShip = relationShip;
28 | this.desc = desc;
29 | this.className = className;
30 | }
31 |
32 | public AsmMethodAdapter(int access, String methodName, String desc, MethodVisitor mv, List relationShip, String className, Set parentPath, Map> recordMapping){
33 | super(ASM7, mv, access, methodName, desc);
34 | this.methodName = methodName;
35 | this.relationShip = relationShip;
36 | this.desc = desc;
37 | this.className = className;
38 | this.parentPath = parentPath;
39 | this.recordMapping = recordMapping;
40 | }
41 |
42 | @Override
43 | public void visitMethodInsn(int opcode, String owner, String name, String descript, boolean isInterface){
44 | String fullMethodName = owner + METHOD_SPLIT + FullMethodNameUtil.getMethodSignatureName(name, descript);
45 | if(FilterUtils.isNeedInject(owner) && FilterUtils.isNeedInjectMethod(fullMethodName) && !relationShip.contains(fullMethodName))
46 | relationShip.add(fullMethodName);
47 | super.visitMethodInsn(opcode, owner, name, descript, isInterface);
48 | }
49 |
50 | @Override
51 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visiable){
52 | if(FilterUtils.isRequestAnnotation(descriptor)){
53 | // request的方法
54 | ControllerRecord.getInstance().putControlMethod(className, FullMethodNameUtil.getMethodSignatureName(methodName, desc));
55 | requestMappingValue = new HashSet<>();
56 | // 这里去获取方法的 requestMappingValue
57 | // 故而:requestMappingValue记录自己的,parentPath上一级(比如类)传过来的 requestMappingValue
58 | return new AsmAnnotationVisitor(super.visitAnnotation(descriptor, visiable), requestMappingValue, parentPath);
59 | }
60 | return super.visitAnnotation(descriptor, visiable);
61 | }
62 |
63 | @Override
64 | public void visitTypeInsn(int opcode, String type){
65 | super.visitTypeInsn(opcode, type);
66 | }
67 |
68 | @Override
69 | public void visitMaxs(int a, int b){
70 | try{
71 | super.visitMaxs(a, b);
72 | }catch (TypeNotPresentException e){
73 | return;
74 | }
75 | }
76 |
77 | @Override
78 | public void visitEnd(){
79 | // 处理一下Mapping
80 | if(requestMappingValue!=null){
81 | String fullMethodName = className + METHOD_SPLIT + FullMethodNameUtil.getMethodSignatureName(methodName, desc);
82 | recordMapping.put(fullMethodName, requestMappingValue);
83 | ApiRecord.getInstance().putApi(fullMethodName, requestMappingValue);
84 | }
85 |
86 | super.visitEnd();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Visitors/AsmSearchFilterClass.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Visitors;
2 |
3 | import com.diff.core.Recorders.AbstractRecord;
4 | import com.diff.core.Recorders.DubboRecord;
5 | import com.diff.core.Recorders.InterfaceRecord;
6 | import com.diff.core.Utils.FilterUtils;
7 | import com.diff.core.Utils.FullMethodNameUtil;
8 | import org.objectweb.asm.AnnotationVisitor;
9 | import org.objectweb.asm.ClassVisitor;
10 | import org.objectweb.asm.MethodVisitor;
11 |
12 | import static com.diff.core.Common.Code.*;
13 | import static org.objectweb.asm.Opcodes.*;
14 |
15 | /**
16 | * 第一次遍历所有class,记录下项目的所有className到FilterUtil中;
17 | * 记录下所有interface以及abstract类,以及它们与实体类的对应关系
18 | */
19 | public class AsmSearchFilterClass extends ClassVisitor{
20 | private final InterfaceRecord interfaceRecord = InterfaceRecord.getInstance();
21 | private final AbstractRecord abstractRecord = AbstractRecord.getInstance();
22 | private String className;
23 | private boolean isInterface = false;
24 | private boolean isAbstract = false;
25 | private boolean isDubbo = false;
26 |
27 | public AsmSearchFilterClass(ClassVisitor cv){
28 | super(ASM7, cv);
29 | }
30 |
31 | @Override
32 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces){
33 | // 记录className
34 | FilterUtils.addProjectPackage(name);
35 | className = name;
36 | // 如果是interface
37 | if((access & ACC_INTERFACE)==ACC_INTERFACE){
38 | interfaceRecord.putInterfaceClass(name);
39 | isInterface = true;
40 | }
41 | // 如果是abstract类
42 | if((access & ACC_ABSTRACT)==ACC_ABSTRACT){
43 | abstractRecord.putAbstractClass(name);
44 | isAbstract = true;
45 | }
46 | super.visit(version, access, name, signature, superName, interfaces);
47 | }
48 |
49 | @Override
50 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){
51 | // 保存方法名
52 | String methodName = FullMethodNameUtil.getMethodSignatureName(name, descriptor);
53 | FilterUtils.addProjectMethod(className + METHOD_SPLIT + methodName);
54 | // 接口或抽象类的abstract方法需要记录
55 | if(isInterface){
56 | if((access & ACC_ABSTRACT)==ACC_ABSTRACT){
57 | interfaceRecord.putMethod(className, methodName, true);
58 | }else{
59 | interfaceRecord.putMethod(className, methodName, false);
60 | }
61 | }
62 | if(isAbstract){
63 | if((access & ACC_ABSTRACT)==ACC_ABSTRACT){
64 | abstractRecord.putMethod(className, methodName, true);
65 | }else{
66 | abstractRecord.putMethod(className, methodName, false);
67 | }
68 | }
69 | if(isDubbo){
70 | DubboRecord.putDubboMethod(className + METHOD_SPLIT + methodName);
71 | }
72 | return new AsmSearchFilterMethod(
73 | super.visitMethod(access, name, descriptor, signature, exceptions),
74 | access, name, descriptor
75 | );
76 | }
77 |
78 | @Override
79 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visiable){
80 | if(FilterUtils.isDubboAnnotation(descriptor)){
81 | isDubbo = true;
82 | // DubboRecord.putDubboClass(descriptor);
83 | }
84 | return super.visitAnnotation(descriptor, visiable);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Visitors/AsmSearchFilterMethod.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Visitors;
2 |
3 | import org.objectweb.asm.MethodVisitor;
4 | import org.objectweb.asm.commons.AdviceAdapter;
5 |
6 | public class AsmSearchFilterMethod extends AdviceAdapter {
7 | public AsmSearchFilterMethod(MethodVisitor mv, int access, String methodName, String desc){
8 | super(ASM7, mv, access, methodName, desc);
9 | }
10 |
11 | @Override
12 | public void visitMaxs(int a, int b){
13 | // 这里不关心Type的报错,直接忽略
14 | try{
15 | super.visitMaxs(a, b);
16 | }catch (TypeNotPresentException ignored){
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Visitors/MethodVisitorAdapter.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Visitors;
2 |
3 | import com.github.javaparser.ast.NodeList;
4 | import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
5 | import com.github.javaparser.ast.body.MethodDeclaration;
6 | import com.github.javaparser.ast.body.Parameter;
7 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | import static com.diff.core.Common.Code.*;
13 |
14 | public class MethodVisitorAdapter extends VoidVisitorAdapter {
15 | private final Map mds = new HashMap<>();
16 |
17 | @Override
18 | public void visit(MethodDeclaration n, String arg){
19 | StringBuilder stringBuilder = new StringBuilder();
20 | stringBuilder.append("(");
21 | NodeList parameters = n.getParameters();
22 | for(Parameter parameter:parameters){
23 | String parameterType = parameter.getType().toString();
24 | if(parameterType.contains("<"))
25 | parameterType = parameterType.replace(parameterType.substring(parameterType.indexOf('<'), parameterType.lastIndexOf('>')+1), "");
26 | if(!stringBuilder.toString().equals("("))
27 | stringBuilder.append(", ");
28 | stringBuilder.append(parameterType);
29 | }
30 | stringBuilder.append(")");
31 | String returnType = n.getType().toString();
32 | if(returnType.contains("<"))
33 | returnType = returnType.replace(returnType.substring(returnType.indexOf('<'), returnType.lastIndexOf('>')+1), "");
34 | stringBuilder.append(returnType);
35 | mds.put(arg + METHOD_SPLIT + n.getName().toString() + stringBuilder, n);
36 | }
37 |
38 | @Override
39 | public void visit(ClassOrInterfaceDeclaration n, String arg){
40 | if(arg.substring(arg.length()-1).equals(PACKAGE_SPLIT))
41 | super.visit(n, arg + n.getName().toString());
42 | else
43 | super.visit(n, arg + SUB_CLASS_SPLIT + n.getName().toString());
44 | }
45 |
46 | public Map getMds() {
47 | return mds;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/Visitors/VisitorAdapter.java:
--------------------------------------------------------------------------------
1 | package com.diff.core.Visitors;
2 |
3 | import com.alibaba.fastjson.JSONArray;
4 | import com.alibaba.fastjson.JSONObject;
5 | import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
6 | import com.github.javaparser.ast.body.MethodDeclaration;
7 | import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
8 |
9 | public class VisitorAdapter extends VoidVisitorAdapter {
10 | @Override
11 | public void visit(MethodDeclaration n, JSONArray arg){
12 | JSONObject descript = new JSONObject();
13 | descript.put("name", n.getName().toString());
14 | descript.put("type", "method");
15 | descript.put("signature", n.getSignature().toString());
16 | arg.add(descript);
17 | super.visit(n, arg);
18 | }
19 |
20 | /**
21 | * [
22 | * {
23 | * "name": "app",
24 | * "type": "class",
25 | * "member": [
26 | * {
27 | * "name": "main",
28 | * "type": "method",
29 | * "signate": [Stirng[] args, int cnt]
30 | * },
31 | * {
32 | * "name": "main",
33 | * "type": "method",
34 | * "signate": "[int a, int b]"
35 | * },
36 | * {
37 | * "name": "tt",
38 | * "type": "class",
39 | * "member": [...]
40 | * }
41 | * ]
42 | * },
43 | * {...}
44 | * ]
45 | * @param n
46 | * @param arg
47 | */
48 | @Override
49 | public void visit(ClassOrInterfaceDeclaration n, JSONArray arg){
50 | JSONObject descript = new JSONObject();
51 | descript.put("name", n.getName().toString());
52 | descript.put("type", "class");
53 | JSONArray Members = new JSONArray();
54 | descript.put("member", Members);
55 | arg.add(descript);
56 | super.visit(n, Members);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/diff/core/main.java:
--------------------------------------------------------------------------------
1 | package com.diff.core;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.diff.core.Common.Config;
5 | import com.diff.core.Recorders.ApiRecord;
6 | import com.diff.core.Utils.FileTreeUtil;
7 | import com.diff.core.Utils.ParseUtil;
8 | import com.diff.core.Utils.ProjectChainUtils;
9 |
10 | import static com.diff.core.Common.Code.*;
11 |
12 | import java.util.*;
13 |
14 | public class main {
15 |
16 | public static void main(String[] args) throws Exception {
17 | Config config = Config.getInstance();
18 | Map> modules = FileTreeUtil.compireToPomTree(config.getString("oldProjectPath"), config.getString("newProjectPath"));
19 | List newModules = modules.get("newModules");
20 | List normalModules = modules.get("normalModules");
21 |
22 | List allModules = new ArrayList<>();
23 | allModules.addAll(newModules);
24 | allModules.addAll(normalModules);
25 | Map chains = ProjectChainUtils.getProjectChainFromPath(allModules);
26 |
27 | List update = new ArrayList<>();
28 | for(String module:newModules){
29 | // 新模块所有文件加入update
30 | List scan = FileTreeUtil.scanForDirectory(module);
31 | for(String file:scan){
32 | List scanMethods = ParseUtil.scanMethods(file);
33 | update.addAll(scanMethods);
34 | }
35 | // 检索mybatis xml配置
36 | }
37 | update.addAll(ProjectChainUtils.getProjectUpdateMethod(normalModules));
38 |
39 | System.out.println("涉及到更新的方法: ");
40 | for(String method:update){
41 | System.out.println(method);
42 | }
43 | System.out.println("建议回归的API: ");
44 | Set needToTestApi = new HashSet<>();
45 | for(String updateMethod:update){
46 | for(String startChain:chains.keySet()){
47 | if(startChain.equals(updateMethod) || chains.get(startChain).toJSONString().contains(updateMethod)){
48 | Set apis = ApiRecord.getInstance().getApis(startChain);
49 | if(apis == null){
50 | needToTestApi.add(DUBBO + METHOD_SIGNATURE_SPLIT + startChain);
51 | }else{
52 | for(String api:apis){
53 | needToTestApi.add(HTTP + METHOD_SIGNATURE_SPLIT + api);
54 | }
55 | }
56 | }
57 | }
58 | }
59 | for(String api:needToTestApi)
60 | System.out.println(api);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/resources/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "oldProjectPath": "/Users/xiaoandi/github/qqbot/qqbot",
3 | "newProjectPath": "/Users/xiaoandi/github/qqbot/diff_test/qqbot",
4 | "source": "src/main/java",
5 | "target": "target/classes",
6 | "resources": "src/main/resources"
7 | }
--------------------------------------------------------------------------------