diff --git a/src/main/java/com/hy/java/uct/cdtocode/RelationTypeXlsReader.java b/src/main/java/com/hy/java/uct/cdtocode/RelationTypeXlsReader.java index fccb53ca52c4abceb3eb7b06a9ad3ada5a0fe615..cbedf057c94452557120e49f254552677aedde1e 100644 --- a/src/main/java/com/hy/java/uct/cdtocode/RelationTypeXlsReader.java +++ b/src/main/java/com/hy/java/uct/cdtocode/RelationTypeXlsReader.java @@ -17,7 +17,7 @@ public class RelationTypeXlsReader { private static final String doc_dir = System.getProperty("user.dir") + "\\src\\main\\resources\\cdtocode\\doc\\"; public static void main(String[] args) { - FileNode srcdoc_fn = Traverser.traverseDir(doc_dir); + FileNode srcdoc_fn = Traverser.traverseDir(doc_dir, false); CountGroup cg = new CountGroup(); for (FileNode proj_dir_fn : srcdoc_fn.children) { if (!proj_dir_fn.path.contains("jetty")) { diff --git a/src/main/java/com/hy/java/uct/cdtocode/reader/CodeReader.java b/src/main/java/com/hy/java/uct/cdtocode/reader/CodeReader.java index 4a96ad4e203384965f71e6b413616da7604a9620..2cf143c21968e564381da145efa20e3ffc836326 100644 --- a/src/main/java/com/hy/java/uct/cdtocode/reader/CodeReader.java +++ b/src/main/java/com/hy/java/uct/cdtocode/reader/CodeReader.java @@ -23,7 +23,7 @@ public class CodeReader { // 读取配置文件 FileEditor code_path_file = new FileEditor(code_path_file_path); // 配置文件指定的代码根目录所对应的FileNode。遍历其children下的java文件即可 - FileNode code_files_root = Traverser.traverseDir(code_path_file.readFileToString()); + FileNode code_files_root = Traverser.traverseDir(code_path_file.readFileToString(), false); // 遍历 System.out.println("正在遍历" + code_files_root.path + "下的源码文件,请稍候..."); filterJavaFileFromFN(code_files_root, result); diff --git a/src/main/java/com/hy/java/uct/sdtocode/mapper/CodeMessageTracer.java b/src/main/java/com/hy/java/uct/sdtocode/mapper/CodeMessageTracer.java index 37d42f9f5f4e11597eb116187b1eaf9326193c95..b3f3ea2d29bf45d1fda55ab0cbc7d1091cd175a8 100644 --- a/src/main/java/com/hy/java/uct/sdtocode/mapper/CodeMessageTracer.java +++ b/src/main/java/com/hy/java/uct/sdtocode/mapper/CodeMessageTracer.java @@ -18,11 +18,14 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.Parameter; import com.github.javaparser.ast.stmt.BlockStmt; import com.hy.java.uct.sdtocode.util.TracedMessagePath; import com.hy.java.uct.util.sd.Message; import com.hy.java.uct.util.sd.UMLObject; import com.hy.java.utility.common.Pair; +import com.hy.java.utility.common.Traverser; +import com.hy.java.utility.common.Traverser.FileNode; import com.hy.java.utility.common.Triple; /** @@ -34,6 +37,8 @@ import com.hy.java.utility.common.Triple; * * 第三种※:1)解析消息中的内容。将每个标识符对应为一个类或者一个方法。比如第一个标识符必须是类;后面的标识符如果是小写开头,那认为它是某个类型的变量名,所以在前面的标识符所属的类中找具有该名称的变量其所属的类。最终在最后一个标识符那儿找方法名。※注意:存在一种情况,即“前面的标识符所属的类中找不到后面的标识符”。所以应该对每个标识符与其下一个标识符都进行单独的查找,然后记录所有“ * 能从上一个标识符找到下一个标识符”的链条片段。将所有片段连接起来,断开的部分就直接放上对应的原消息内容即可。2)将由标识符链接起来的链条作为消息的追踪。记录链条两端。3)对比链条两端与消息两端的对象。如果存在支撑类之类的关系,则认为消息两端应该是支撑类;否则仍保留链条两端作为消息两端。 + * + * 注:搜索“这个阈值可以再改”,可查找所有经验阈值 */ public class CodeMessageTracer { /** @@ -41,9 +46,14 @@ public class CodeMessageTracer { */ public static List trace(Pair, List> objs_in_SD, Map classFullName_javaFileDir_map) { List res = new ArrayList<>(); - // 逐条追踪。不用追踪返回消息,只用它作为对应正向消息的参考。追踪完正向消息后返回消息的“返回起点”自然就是正向消息的最后一个带返回值的函数。 List msgs_inSD = objs_in_SD.getRight(); - for (Message msg_inSD : msgs_inSD) { + int msg_num = msgs_inSD.size(); + for (int msg_index = 0; msg_index < msg_num; msg_index++) { + res.add(msgs_inSD.get(msg_index)); + } + // 逐条追踪。不用追踪返回消息,只用它作为对应正向消息的参考。追踪完正向消息后返回消息的“返回起点”自然就是正向消息的最后一个带返回值的函数。 + for (int msg_index = 0; msg_index < msg_num; msg_index++) { + Message msg_inSD = res.get(msg_index); // 只追踪正向消息,不追踪返回消息 if (!msg_inSD.is_return) { /* @@ -59,7 +69,7 @@ public class CodeMessageTracer { * * 2)①在支撑类中找该属性的相关操作,对比每个操作与消息的语义相似度。比如new操作可以对应消息内容run。②对属性类型对应的类,找其中与消息内容语义相似的方法。 */ - traceByAttri(msg_inSD, res, objs_in_SD, classFullName_javaFileDir_map); + traceByAttri(msg_inSD, msg_index, res, classFullName_javaFileDir_map); /* * 2、第二种追踪:针对对象和方法 * @@ -69,7 +79,7 @@ public class CodeMessageTracer { * * 3)①找该方法内是否有对消息另一端的调用,如参数、方法内的变量、语句等。②对与消息相似的方法,找到其所属的类,在这个类里看该方法的返回类型是否与另一端相似。 */ - traceByMethod(msg_inSD, res, objs_in_SD, classFullName_javaFileDir_map); + traceByMethod(msg_inSD, msg_index, res, objs_in_SD, classFullName_javaFileDir_map); /* * 3、第三种追踪:针对消息 * @@ -81,7 +91,7 @@ public class CodeMessageTracer { * 3)对比链条两端与消息两端的对象。如果存在支撑类之类的关系,则认为消息两端应该是支撑类;否则仍保留链条两端作为消息两端。 */ if (msg_is_complex) { - traceByMsg(msg_inSD, res, objs_in_SD, classFullName_javaFileDir_map); + traceByMsg(msg_inSD, msg_index, res, objs_in_SD, classFullName_javaFileDir_map); } System.out.println("================================================================="); } @@ -118,8 +128,9 @@ public class CodeMessageTracer { * 2)①在支撑类中找该属性的相关操作,对比每个操作与消息的语义相似度。比如new操作可以对应消息内容run。②对属性类型对应的类,找其中与消息内容语义相似的方法。 * * @param msg_inSD + * @param msg_index */ - private static void traceByAttri(Message msg_inSD, List res, Pair, List> objs_in_SD, Map classFullName_javaFileDir_map) { + private static void traceByAttri(Message msg_inSD, int msg_index, List res, Map classFullName_javaFileDir_map) { System.out.println("①对于" + msg_inSD.msg + ",开始针对消息两端对象和代码中的属性进行追踪"); // 解析出msg中的方法名 String method_in_msg = parseMethodInMsg(msg_inSD.msg); @@ -163,16 +174,17 @@ public class CodeMessageTracer { for (Pair attri_p : sim_attris) { FieldDeclaration sim_attri = attri_p.getLeft(); // ①在支撑类中找该属性的相关操作,对比每个操作与消息的语义相似度。比如new操作可以对应消息内容run。 - trace_attri_in_supportingClsCode(sim_attri, attri_p.getRight(), clsCode_unit, msg_inSD, method_in_msg, res, uo_mapped_file_dir); + trace_attri_in_supportingClsCode(sim_attri, attri_p.getRight(), clsCode_unit, msg_inSD, msg_index, method_in_msg, uo_mapped_file_dir); // ②对属性类型对应的类,找其中与消息内容语义相似的方法。 Pair attri_clsCode = getAttriClsCode(sim_attri.getElementType().asString(), uo_mapped_file, uo_mapped_file_dir, classFullName_javaFileDir_map); - trace_attri_in_typeClsCode(sim_attri, attri_p.getRight(), attri_clsCode, msg_inSD, method_in_msg, res, clsCode_unit); + trace_attri_in_typeClsCode(sim_attri, attri_p.getRight(), attri_clsCode, msg_inSD, msg_index, method_in_msg, clsCode_unit); } } } catch (FileNotFoundException e) { e.printStackTrace(); } } + res.set(msg_index, msg_inSD); System.out.println("对于" + msg_inSD.msg + ",完成针对对象和属性的追踪了"); } @@ -290,10 +302,12 @@ public class CodeMessageTracer { * 3、如果包含,则分析改行内的方法是否与消息msg_inSD相似。 * * @param msg_inSD + * @param msg_index * * @param uo_mapped_file_dir + * @return */ - private static void trace_attri_in_supportingClsCode(FieldDeclaration sim_attri, Double attri_name_similarity, ClassOrInterfaceDeclaration clsCode_unit, Message msg_inSD, String msg, List res, String uo_mapped_file_dir) { + private static void trace_attri_in_supportingClsCode(FieldDeclaration sim_attri, Double attri_name_similarity, ClassOrInterfaceDeclaration clsCode_unit, Message msg_inSD, int msg_index, String msg, String uo_mapped_file_dir) { String[] clsCode_plainText = clsCode_unit.toString().split("\n"); /* * 1、按行读取支撑类clsCode_unit的文件内容。 @@ -344,11 +358,6 @@ public class CodeMessageTracer { } } } - if (res.contains(msg_inSD)) { - res.set(res.indexOf(msg_inSD), msg_inSD); - } else { - res.add(msg_inSD); - } System.out.println("检查完支撑类" + clsCode_unit.getNameAsString() + "中所有包含属性" + sim_attri.getVariable(0).getNameAsString() + "的代码行与消息" + msg + "的相似度了"); } @@ -389,7 +398,7 @@ public class CodeMessageTracer { } } else { // 这是些奇奇怪怪的字符串,可能有方法、也可能没有。看它跟消息像不像吧 - res.add(method_strs[i]); + res.add(method_strs[i].replaceAll("\"", "").replaceAll("!", "").trim()); } i++; } while (i < method_strs.length - 1); @@ -425,9 +434,11 @@ public class CodeMessageTracer { * ②对属性类型对应的类,找其中与消息内容语义相似的方法。 * * @param msg_inSD + * @param msg_index * @param clsCode_unit + * @return */ - private static void trace_attri_in_typeClsCode(FieldDeclaration sim_attri, Double attri_name_sim, Pair attri_clsCode, Message msg_inSD, String msg, List res, ClassOrInterfaceDeclaration clsCode_unit) { + private static void trace_attri_in_typeClsCode(FieldDeclaration sim_attri, Double attri_name_sim, Pair attri_clsCode, Message msg_inSD, int msg_index, String msg, ClassOrInterfaceDeclaration clsCode_unit) { List methods_in_attriClsCode = attri_clsCode.getRight().getMethods(); for (MethodDeclaration md : methods_in_attriClsCode) { String md_name = md.getNameAsString(); @@ -440,11 +451,6 @@ public class CodeMessageTracer { msg_inSD.traced_path_ls.add(trace_path); } } - if (res.contains(msg_inSD)) { - res.set(res.indexOf(msg_inSD), msg_inSD); - } else { - res.add(msg_inSD); - } System.out.println("检查完支撑类" + clsCode_unit.getNameAsString() + "中属性" + sim_attri.getVariable(0).getNameAsString() + "的类型实现类中所有方法与" + msg + "的相似度了"); } @@ -458,9 +464,10 @@ public class CodeMessageTracer { * 3)①找该方法内是否有对消息另一端的调用,如参数、方法内的变量、语句等。②对与消息相似的方法,找到其所属的类,在这个类里看该方法的返回类型是否与另一端相似。 * * @param msg_inSD + * @param msg_index */ - private static void traceByMethod(Message msg_inSD, List res, Pair, List> objs_in_SD, Map classFullName_javaFileDir_map) { - System.out.println("①对于" + msg_inSD.msg + ",开始针对消息两端对象和代码中的方法进行追踪"); + private static void traceByMethod(Message msg_inSD, int msg_index, List res, Pair, List> objs_in_SD, Map classFullName_javaFileDir_map) { + System.out.println("②对于" + msg_inSD.msg + ",开始针对消息两端对象和代码中的方法进行追踪"); // 解析出msg中的方法名 String method_in_msg = parseMethodInMsg(msg_inSD.msg); /* @@ -480,39 +487,20 @@ public class CodeMessageTracer { ClassOrInterfaceDeclaration clsCode_unit = o_class.get(); // 记录支撑类中的方法 List methods = clsCode_unit.getMethods(); - // 记录方法逻辑中用到的其他方法 - List> method_code_sim = new ArrayList<>(); - - /* - * 记录方法逻辑中用到的其他方法 - * - * 记录方法逻辑中用到的其他方法 - * - * 记录方法逻辑中用到的其他方法 - * - * 记录方法逻辑中用到的其他方法 - * - * 记录方法逻辑中用到的其他方法 - * - * 记录方法逻辑中用到的其他方法 - */ - for (MethodDeclaration _method : methods) { - - Optional o_body = _method.getBody(); - if (o_body.isPresent()) { - BlockStmt body = o_body.get(); - String[] codes = body.toString().split("\n"); - for (String code_line : codes) { - List methods_in_codeLine = parseMethodsInCL(code_line.trim()); - System.out.println(methods_in_codeLine.toString()); - } - } - } + // 解析方法逻辑中用到的方法:uo_mapped_file中import的类、uo_mapped_file_dir同包中的类、内部类中的所有方法;对比代码行中的同名方法。记录MethodDeclaration + List> methods_in_codeLine = getAllMethodsWithinCodeLine(methods); + List methods_in_ImportsPackageInnerCls = getAllMethodsInImportsAndPackage(uo_mapped_file, uo_mapped_file_dir, classFullName_javaFileDir_map); + // 对比代码行中的同名方法。顺便也把支撑类中的方法存进去 + List> method_code_sim = getAllMethodsUsedByUO(methods, methods_in_codeLine, methods_in_ImportsPackageInnerCls); /* * 2)计算每个方法与消息内容的语义相似度。找到相似的方法。 */ - for (MethodDeclaration _method : methods) { - + for (Triple tri : method_code_sim) { + double sim = jaccard_similarity(tri.getLeft().getNameAsString(), method_in_msg); + // 这个阈值可以再改 + if (sim > 0.5) { + tri.setRight(sim); + } } /* * 3)①找该方法内是否有对消息另一端的调用,如参数、方法内的变量、语句等。 @@ -520,16 +508,194 @@ public class CodeMessageTracer { * ②对与消息相似的方法,找到其所属的类,在这个类里看该方法的返回类型是否与另一端相似。 */ for (Triple tri : method_code_sim) { - + if (tri.getRight() > 0.5) { + // ①找该方法内是否有对消息另一端的调用,如参数、方法内的变量、语句等。 + checkIfCall(tri, target_uo.getTitle(), msg_inSD, uo_mapped_file_dir); + // ②对与消息相似的方法,找到其所属的类,在这个类里看该方法的返回类型是否与另一端相似。 + checkIfReturn(tri, target_uo.getTitle(), msg_inSD, uo_mapped_file_dir); + } } } } catch (FileNotFoundException e) { e.printStackTrace(); } } + res.set(msg_index, msg_inSD); System.out.println("对于" + msg_inSD.msg + ",完成针对对象和方法的追踪了"); } + /** + * 获取方法内部逻辑中用到的其他方法 + * + * Pair<代码行,方法> + */ + private static List> getAllMethodsWithinCodeLine(List methods) { + List> res = new ArrayList<>(); + for (MethodDeclaration _method : methods) { + Optional o_body = _method.getBody(); + if (o_body.isPresent()) { + BlockStmt body = o_body.get(); + String[] codes = body.toString().split("\n"); + for (String code_line : codes) { + List methods_in_codeLine = parseMethodsInCL(code_line.trim()); + if (!methods_in_codeLine.isEmpty()) { + for (String _method_in_codeLine : methods_in_codeLine) { + if (!_method_in_codeLine.isBlank()) { + if (_method_in_codeLine.startsWith("new ")) { + _method_in_codeLine = _method_in_codeLine.substring(4); + } + res.add(Pair.createPair(code_line, _method_in_codeLine.trim())); + } + } + } + } + } + } + return res; + } + + /** + * 记录uo_mapped_file中import的类和uo_mapped_file_dir同包中的类的所有方法 + */ + private static List getAllMethodsInImportsAndPackage(CompilationUnit uo_mapped_file, String uo_mapped_file_dir, Map classFullName_javaFileDir_map) { + List res = new ArrayList<>(); + List cls_path_ls = new ArrayList<>(); + /* + * 获取import中的所有类和uo_mapped_file_dir同包中的类的路径 + */ + // 获取import的所有类的路径 + NodeList imports = uo_mapped_file.getImports(); + for (ImportDeclaration _import : imports) { + String import_name = _import.getNameAsString(); + // 在classFullName_javaFileDir_map中找java文件 + if (classFullName_javaFileDir_map.containsKey(import_name)) { + cls_path_ls.add(classFullName_javaFileDir_map.get(import_name)); + } + } + // 获取uo_mapped_file_dir同包中的类的路径 + String package_dir = uo_mapped_file_dir.substring(0, uo_mapped_file_dir.lastIndexOf("\\")); + FileNode fn = Traverser.traverseDir(package_dir, false); + for (FileNode child : fn.children) { + if (child.path.substring(0, child.path.lastIndexOf("\\")).equals(package_dir)) { + cls_path_ls.add(child.path); + } + } + /* + * 记录cls_path_ls中所有类的所有方法 + */ + for (String path : cls_path_ls) { + if (path.endsWith(".java")) { + String short_name = path.substring(path.lastIndexOf("\\") + 1, path.lastIndexOf(".")); + try { + Optional o_class = StaticJavaParser.parse(new File(path)).getClassByName(short_name); + if (o_class.isPresent()) { + ClassOrInterfaceDeclaration clsCode = o_class.get(); + res.addAll(clsCode.getMethods()); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + /* + * ※额外可选:获取uo_mapped_file的内部类中的方法 + */ + String[] code_lines = uo_mapped_file.toString().split("\n"); + String uo_short_name = uo_mapped_file_dir.substring(uo_mapped_file_dir.lastIndexOf("\\") + 1, uo_mapped_file_dir.lastIndexOf(".")); + for (String code_line : code_lines) { + if (code_line.contains("class ")) { + String temp = code_line.substring(code_line.indexOf("class ") + 6); + String possible_innerCls_name = temp.substring(0, temp.indexOf(" ")); + if (!possible_innerCls_name.equals(uo_short_name)) { + List inner_class_declaration = uo_mapped_file.getLocalDeclarationFromClassname(possible_innerCls_name); + if (!inner_class_declaration.isEmpty()) { + res.addAll(inner_class_declaration.get(0).getMethods()); + } + } + } + } + return res; + } + + /** + * 对比代码行中的同名方法,记录MethodDeclaration + * + * Triple<方法,在uo中用到该方法的代码行,相似度> + * + * @param methods + */ + private static List> getAllMethodsUsedByUO(List methods, List> methods_in_codeLine, List methods_in_importAndPackage) { + List> res = new ArrayList<>(); + for (MethodDeclaration _method : methods) { + res.add(Triple.createTriple(_method, _method.getDeclarationAsString(), 0.0)); + } + for (Pair method_in_codeLine : methods_in_codeLine) { + for (MethodDeclaration method_in_importAndPackage : methods_in_importAndPackage) { + if (method_in_codeLine.getRight().equals(method_in_importAndPackage.getNameAsString())) { + res.add(Triple.createTriple(method_in_importAndPackage, method_in_codeLine.getLeft(), 0.0)); + } + } + } + return res; + } + + /** + * ①找该方法内是否有对消息另一端的调用,如参数、方法内的变量、语句等。 + */ + private static void checkIfCall(Triple tri, String target_title, Message msg_inSD, String uo_mapped_file_dir) { + MethodDeclaration md = tri.getLeft(); + /* + * 找方法的参数是否有对消息另一端的调用 + */ + double parameter_sim = 0.0; + NodeList ps = md.getParameters(); + for (Parameter p : ps) { + double temp = jaccard_similarity(p.getTypeAsString(), target_title); + parameter_sim = temp > parameter_sim ? temp : parameter_sim; + temp = jaccard_similarity(p.getNameAsString(), target_title); + parameter_sim = temp > parameter_sim ? temp : parameter_sim; + } + /* + * 找方法中的语句、变量是否有对消息另一端的调用 + */ + double codeLine_sim = 0.0; + Optional o_body = md.getBody(); + if (o_body.isPresent()) { + BlockStmt body = o_body.get(); + String[] codes = body.toString().split("\n"); + for (String code_line : codes) { + if (code_line.toLowerCase().contains(target_title.toLowerCase())) { + codeLine_sim = 1.0; + break; + } + } + } + /* + * 根据相似度决定是否追踪到此方法 + */ + // 这个阈值可以再改 + if (parameter_sim > 0.3 || codeLine_sim > 0.3) { + TracedMessagePath trace_path = new TracedMessagePath(); + double max = parameter_sim > codeLine_sim ? parameter_sim : codeLine_sim; + trace_path.path.add(Triple.createTriple(uo_mapped_file_dir, tri.getMid().trim(), tri.getRight() * max)); + msg_inSD.traced_path_ls.add(trace_path); + } + } + + /** + * ②对与消息相似的方法,找到其所属的类,在这个类里看该方法的返回类型是否与另一端相似。 + */ + private static void checkIfReturn(Triple tri, String target_title, Message msg_inSD, String uo_mapped_file_dir) { + MethodDeclaration md = tri.getLeft(); + double returnType_sim = jaccard_similarity(md.getTypeAsString(), target_title); + // 这个阈值可以再改 + if (returnType_sim > 0.5) { + TracedMessagePath trace_path = new TracedMessagePath(); + trace_path.path.add(Triple.createTriple(uo_mapped_file_dir, tri.getMid().trim(), tri.getRight() * returnType_sim)); + msg_inSD.traced_path_ls.add(trace_path); + } + } + /** * 第三种追踪:针对消息 * @@ -541,10 +707,337 @@ public class CodeMessageTracer { * 3)对比链条两端与消息两端的对象。如果存在支撑类之类的关系,则认为消息两端应该是支撑类;否则仍保留链条两端作为消息两端。 * * @param msg_inSD + * @param msg_index * @param res */ - private static void traceByMsg(Message msg_inSD, List res, Pair, List> objs_in_SD, Map classFullName_javaFileDir_map) { - // TODO Auto-generated method stub + private static void traceByMsg(Message msg_inSD, int msg_index, List res, Pair, List> objs_in_SD, Map classFullName_javaFileDir_map) { + System.out.println("③对于" + msg_inSD.msg + ",开始针对消息内容进行追踪"); + /* + * 1)解析消息中的内容。将每个标识符对应为一个类或者一个方法。比如第一个标识符必须是类;后面的标识符如果是小写开头,那认为它是某个类型的属性名,所以在前面的标识符所属的类中找具有该名称的属性其所属的类。最终在最后一个标识符那儿找方法名。※注意:存在一种情况,即“前面的标识符所属的类中找不到后面的标识符”。所以应该对每个标识符与其下一个标识符都进行单独的查找,然后记录所有“ + * 能从上一个标识符找到下一个标识符”的链条片段。将所有片段连接起来,断开的部分就直接放上对应的原消息内容即可。 + */ + System.out.println("※※※※※※※※※※\n对于" + msg_inSD.msg + ",解析消息中的内容"); + // 分隔消息中的每个标识符 + String[] identifiers = msg_inSD.msg.split("\\."); + // 保存每个标识符的<在消息中的位置,<标识符(或与该标识符同名的属性)所属类的文件地址,产生标识符的代码片段,追踪概率>,标识符所属类的对象> + List, ClassOrInterfaceDeclaration>>> index_clsFilePath_ls = new ArrayList<>(); + // 将每个标识符对应为一个类或者一个方法 + int i = 0; + do { + // traced_files_for_current_identifier记录当前标识符所追踪到的所有文件(所以是List,里面记录的都是index_clsFilePath_clsObj) + List, ClassOrInterfaceDeclaration>> traced_files_for_current_identifier = new ArrayList<>(); + // 第一个标识符必须是类。在classFullName_javaFileDir_map中找小写完全匹配的类(或msg.source的支撑类的内部类) + if (i == 0) { + // 完全匹配,所以只追踪到一个index_clsFilePath_clsObj,即<标识符(或与该标识符同名的属性)所属类的文件地址,产生标识符的代码片段,追踪概率> + Triple, ClassOrInterfaceDeclaration> index_clsFilePath_clsObj = new Triple<>(i, null, null); + Triple isExist_Dir_Obj = getClsFilePathForIdentifier(identifiers[i], classFullName_javaFileDir_map, msg_inSD.source); + if (isExist_Dir_Obj.getLeft()) { + String code = isExist_Dir_Obj.getRight().getNameAsString(); + double possibility = 1.0; + index_clsFilePath_clsObj.setMid(Triple.createTriple(isExist_Dir_Obj.getMid(), code, possibility)); + index_clsFilePath_clsObj.setRight(isExist_Dir_Obj.getRight()); + traced_files_for_current_identifier.add(index_clsFilePath_clsObj); + } + } + // 后面的标识符如果是小写开头,那认为它是某个类型的属性名,所以在前面的标识符所属的类中找具有该名称的属性其所属的类 + // ※注意:如果再前面的标识符所属的类中找不到后面的标识符,则在classFullName_javaFileDir_map中找类 + else if (i < identifiers.length - 1) { + // 如果是小写开头,则从前面标识符的属性中找 + if (Character.isLowerCase(identifiers[i].charAt(0))) { + // 如果前面标识符有对应的类文件,则找属性 + List, ClassOrInterfaceDeclaration>> formerID_tracedFiles = index_clsFilePath_ls.get(i - 1); + boolean former_has_attri = false; + // 遍历前面标识符所追踪到的所有类文件,每个文件找一个最相似的属性 + for (Triple, ClassOrInterfaceDeclaration> formerID_tracedFile : formerID_tracedFiles) { + if (formerID_tracedFile.getRight() != null) { + Triple, ClassOrInterfaceDeclaration> index_clsFilePath_clsObj = new Triple<>(i, null, null); + ClassOrInterfaceDeclaration formerIdentifier_clsCode = formerID_tracedFile.getRight(); + List attris_in_formerClsCode = formerIdentifier_clsCode.getFields(); + // 不管是属性名字相似、还是属性类型相似,都记录属性的类型 + for (FieldDeclaration attri : attris_in_formerClsCode) { + double sim_type = jaccard_similarity(identifiers[i], attri.getElementType().asString()); + double sim_var = jaccard_similarity(identifiers[i], attri.getVariable(0).getNameAsString()); + if (sim_type >= 0.8 || sim_var >= 0.8) { + // 找attri.getElementType()对应的类的文件地址(有可能是前面的类的内部类) + Pair attri_type_dir = getAttriTypeDir(attri.getElementType().asString(), classFullName_javaFileDir_map, formerID_tracedFile.getMid().getLeft()); + index_clsFilePath_clsObj.setMid(Triple.createTriple(attri_type_dir.getLeft(), attri.toString(), Math.max(sim_type, sim_var))); + index_clsFilePath_clsObj.setRight(attri_type_dir.getRight()); + former_has_attri = true; + } + } + traced_files_for_current_identifier.add(index_clsFilePath_clsObj); + } + } + // 如果前面标识符没有对应的类文件,则从全部文件的全名中找(或msg.source的支撑类的内部类) + if (!former_has_attri) { + Triple, ClassOrInterfaceDeclaration> index_clsFilePath_clsObj = new Triple<>(i, null, null); + Triple isExist_Dir_Obj = getClsFilePathForIdentifier(identifiers[i], classFullName_javaFileDir_map, msg_inSD.source); + if (isExist_Dir_Obj.getLeft()) { + String code = isExist_Dir_Obj.getRight().getNameAsString(); + double possibility = 1.0; + index_clsFilePath_clsObj.setMid(Triple.createTriple(isExist_Dir_Obj.getMid(), code, possibility)); + index_clsFilePath_clsObj.setRight(isExist_Dir_Obj.getRight()); + traced_files_for_current_identifier.add(index_clsFilePath_clsObj); + } + } + } + // 如果标识符是大写开头,则去找前面标识符的类的内部类(找不到的话再直接从全部文件的全名中找对应类) + else { + Triple, ClassOrInterfaceDeclaration> index_clsFilePath_clsObj = new Triple<>(i, null, null); + Triple isExist_Dir_Obj = new Triple<>(false, null, null); + List, ClassOrInterfaceDeclaration>> formerID_tracedFiles = index_clsFilePath_ls.get(i - 1); + // 去找前面标识符的类的内部类 + for (Triple, ClassOrInterfaceDeclaration> formerID_tracedFile : formerID_tracedFiles) { + if (formerID_tracedFile.getRight() != null) { + isExist_Dir_Obj = getClsFilePathForIdentifierFromFormer(identifiers[i], classFullName_javaFileDir_map, formerID_tracedFile.getMid().getLeft()); + } + } + // 找不到的话再直接从全部文件的全名中找对应类 + if (!isExist_Dir_Obj.getLeft()) { + isExist_Dir_Obj = getClsFilePathForIdentifier(identifiers[i], classFullName_javaFileDir_map, msg_inSD.source); + } + if (isExist_Dir_Obj.getLeft()) { + String code = isExist_Dir_Obj.getRight().getNameAsString(); + double possibility = 1.0; + index_clsFilePath_clsObj.setMid(Triple.createTriple(isExist_Dir_Obj.getMid(), code, possibility)); + index_clsFilePath_clsObj.setRight(isExist_Dir_Obj.getRight()); + traced_files_for_current_identifier.add(index_clsFilePath_clsObj); + } + } + } + // 最终在最后一个标识符那儿找方法名。该方法一定属于上一个标识符对应的类。如果上一个标识符没有对应类,则直接放上这个代表方法的字符串即可 + else { + List, ClassOrInterfaceDeclaration>> formerID_tracedFiles = index_clsFilePath_ls.get(i - 1); + for (Triple, ClassOrInterfaceDeclaration> formerID_tracedFile : formerID_tracedFiles) { + if (formerID_tracedFile.getRight() != null) { + Triple, ClassOrInterfaceDeclaration> index_clsFilePath_clsObj = new Triple<>(i, null, null); + ClassOrInterfaceDeclaration formerIdentifier_clsCode = formerID_tracedFile.getRight(); + List methods_in_formerClsCode = formerIdentifier_clsCode.getMethods(); + boolean former_contains_md = false; + String code = null; + double sim = 0.0; + double max = sim; + for (MethodDeclaration method_in_formerClsCode : methods_in_formerClsCode) { + sim = jaccard_similarity(identifiers[i], method_in_formerClsCode.getNameAsString()); + if (sim >= 0.5) { + former_contains_md = true; + if (sim > max) { + max = sim; + code = method_in_formerClsCode.getDeclarationAsString(); + } + } + } + if (former_contains_md) { + index_clsFilePath_clsObj.setMid(Triple.createTriple(formerID_tracedFile.getMid().getLeft(), code, sim)); + index_clsFilePath_clsObj.setRight(formerID_tracedFile.getRight()); + traced_files_for_current_identifier.add(index_clsFilePath_clsObj); + } + } + } + } + // 断开的部分就直接放上对应的原消息内容即可 + if (traced_files_for_current_identifier.size() <= 0) { + Triple, ClassOrInterfaceDeclaration> index_clsFilePath_clsObj = new Triple<>(i, null, null); + index_clsFilePath_clsObj.setMid(Triple.createTriple(null, identifiers[i], 0.0)); + traced_files_for_current_identifier.add(index_clsFilePath_clsObj); + } + index_clsFilePath_ls.add(traced_files_for_current_identifier); + i++; + } while (i < identifiers.length); + /* + * 2)将由标识符链接起来的链条作为消息的追踪。记录链条两端。 + */ + System.out.println("对于" + msg_inSD.msg + ",将由标识符链接起来的链条作为消息的追踪"); + // 按照identifier的顺序保存多个traced_path + List temp_tracedPath_startingFromID = new ArrayList<>(); + int traced_identifiers_num = index_clsFilePath_ls.size(); + int id_index = 0; + do { + // 先将第一个标识符的所有追踪文件保存至TracedMessagePath,作为起点 + if (id_index == 0) { + List, ClassOrInterfaceDeclaration>> tracedFiles_for_firstIdentifier = index_clsFilePath_ls.get(id_index); + for (Triple, ClassOrInterfaceDeclaration> a_tracedFile_for_firstIdentifier : tracedFiles_for_firstIdentifier) { + TracedMessagePath traced_path = new TracedMessagePath(); + traced_path.path.add(a_tracedFile_for_firstIdentifier.getMid()); + temp_tracedPath_startingFromID.add(traced_path); + } + } + // 后面标识符的每个追踪文件都得在前面的一个TracedMessagePath后出现一次 + else { + int temp_tracedPath_currentSize = temp_tracedPath_startingFromID.size(); + List tmp_record_tracedPath = new ArrayList<>(); + for (int j = 0; j < temp_tracedPath_currentSize; j++) { + TracedMessagePath Jth_tracedPath_till_formerID = temp_tracedPath_startingFromID.get(j); + List, ClassOrInterfaceDeclaration>> tracedFiles_for_currentIdentifier = index_clsFilePath_ls.get(id_index); + for (Triple, ClassOrInterfaceDeclaration> a_tracedFile_for_currentIdentifier : tracedFiles_for_currentIdentifier) { + TracedMessagePath new_ttp = new TracedMessagePath(); + new_ttp.path.addAll(Jth_tracedPath_till_formerID.path); + new_ttp.path.add(a_tracedFile_for_currentIdentifier.getMid()); + tmp_record_tracedPath.add(new_ttp); + } + } + temp_tracedPath_startingFromID.clear(); + temp_tracedPath_startingFromID.addAll(tmp_record_tracedPath); + } + id_index++; + } while (id_index < traced_identifiers_num); + msg_inSD.traced_path_ls.addAll(temp_tracedPath_startingFromID); + /* + * 3)对比链条两端与消息两端的对象。如果存在支撑类之类的关系,则认为消息两端应该是支撑类;否则仍保留链条两端作为消息两端。 + * + * 这一步其实不用做,因为目前就是基于消息两端的支撑类做的追踪 + */ + System.out.println("对于" + msg_inSD.msg + ",对比链条两端与消息两端的对象\n※※※※※※※※※※"); + res.set(msg_index, msg_inSD); + System.out.println("对于" + msg_inSD.msg + ",完成针对消息内容的追踪了"); + } + + /** + * 根据标识符,在classFullName_javaFileDir_map中(或内部类中)找小写完全匹配的类 + * + * @param source + */ + private static Triple getClsFilePathForIdentifier(String identifier, Map classFullName_javaFileDir_map, UMLObject src_uo) { + Triple res = new Triple<>(false, null, null); + Set fullNames = classFullName_javaFileDir_map.keySet(); + for (String fullName : fullNames) { + String shortName = fullName.substring(fullName.lastIndexOf(".") + 1); + if (identifier.toLowerCase().equals(shortName.toLowerCase())) { + res.setLeft(true); + String clsFile_dir = classFullName_javaFileDir_map.get(fullName); + res.setMid(clsFile_dir); + try { + Optional o_clsFile = StaticJavaParser.parse(new File(clsFile_dir)).getClassByName(shortName); + if (o_clsFile.isPresent()) { + res.setRight(o_clsFile.get()); + } else { + o_clsFile = StaticJavaParser.parse(new File(clsFile_dir)).findFirst(ClassOrInterfaceDeclaration.class); + if (o_clsFile.isPresent()) { + res.setRight(o_clsFile.get()); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + break; + } + } + // 有可能是支撑类的内部类 + if (res.getLeft() == false) { + for (String uo_mapped_file_dir : src_uo.mapped_file_dir_ls) { + // uo_mapped_file_dir是支撑类的文件地址。根据文件地址,用javaparser去解析文件 + try { + CompilationUnit uo_mapped_file = StaticJavaParser.parse(new File(uo_mapped_file_dir)); + if (Character.isLowerCase(identifier.charAt(0))) { + StringBuilder sb = new StringBuilder(identifier); + sb.setCharAt(0, Character.toUpperCase(identifier.charAt(0))); + identifier = sb.toString(); + } + List inner_class_declaration = uo_mapped_file.getLocalDeclarationFromClassname(identifier); + if (!inner_class_declaration.isEmpty()) { + res.setLeft(true); + res.setMid(uo_mapped_file_dir); + res.setRight(inner_class_declaration.get(0)); + break; + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + return res; + } + + private static Pair getAttriTypeDir(String attri_type, Map classFullName_javaFileDir_map, String former_dir) { + Pair res = new Pair<>(); + Set fullNames = classFullName_javaFileDir_map.keySet(); + for (String fullName : fullNames) { + String shortName = fullName.substring(fullName.lastIndexOf(".") + 1); + if (attri_type.equals(shortName)) { + String clsFile_dir = classFullName_javaFileDir_map.get(fullName); + res.setLeft(clsFile_dir); + try { + Optional o_clsFile = StaticJavaParser.parse(new File(clsFile_dir)).getClassByName(shortName); + if (o_clsFile.isPresent()) { + res.setRight(o_clsFile.get()); + } else { + o_clsFile = StaticJavaParser.parse(new File(clsFile_dir)).findFirst(ClassOrInterfaceDeclaration.class); + if (o_clsFile.isPresent()) { + res.setRight(o_clsFile.get()); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + break; + } + } + // 有可能是前面标识符所属类的内部类 + if (res.getLeft() == null) { + try { + CompilationUnit former_clsFile = StaticJavaParser.parse(new File(former_dir)); + List inner_class_declaration = former_clsFile.getLocalDeclarationFromClassname(attri_type); + if (!inner_class_declaration.isEmpty()) { + res.setLeft(former_dir); + res.setRight(inner_class_declaration.get(0)); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + return res; + } + + /** + * 根据标识符,在classFullName_javaFileDir_map中(或内部类中)找小写完全匹配的类 + * + * @param source + */ + private static Triple getClsFilePathForIdentifierFromFormer(String identifier, Map classFullName_javaFileDir_map, String former_dir) { + Triple res = new Triple<>(false, null, null); + Set fullNames = classFullName_javaFileDir_map.keySet(); + for (String fullName : fullNames) { + String shortName = fullName.substring(fullName.lastIndexOf(".") + 1); + if (identifier.toLowerCase().equals(shortName.toLowerCase())) { + res.setLeft(true); + String clsFile_dir = classFullName_javaFileDir_map.get(fullName); + res.setMid(clsFile_dir); + try { + Optional o_clsFile = StaticJavaParser.parse(new File(clsFile_dir)).getClassByName(shortName); + if (o_clsFile.isPresent()) { + res.setRight(o_clsFile.get()); + } else { + o_clsFile = StaticJavaParser.parse(new File(clsFile_dir)).findFirst(ClassOrInterfaceDeclaration.class); + if (o_clsFile.isPresent()) { + res.setRight(o_clsFile.get()); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + break; + } + } + // 有可能是前面标识符所属类的内部类 + if (res.getLeft() == false) { + try { + CompilationUnit former_clsFile = StaticJavaParser.parse(new File(former_dir)); + if (Character.isLowerCase(identifier.charAt(0))) { + StringBuilder sb = new StringBuilder(identifier); + sb.setCharAt(0, Character.toUpperCase(identifier.charAt(0))); + identifier = sb.toString(); + } + List inner_class_declaration = former_clsFile.getLocalDeclarationFromClassname(identifier); + if (!inner_class_declaration.isEmpty()) { + res.setLeft(true); + res.setMid(former_dir); + res.setRight(inner_class_declaration.get(0)); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + return res; } /** @@ -567,13 +1060,18 @@ public class CodeMessageTracer { } /** - * 保存追踪结果 + * 整理、保存追踪结果 */ - public static void save(List trace_msgs, String string) { + public static void save(List trace_msgs, String res_dir) { for (Message msg : trace_msgs) { - for (TracedMessagePath traced_path : msg.traced_path_ls) { - for (Triple tri : traced_path.path) { - System.out.println(msg.msg + "有" + tri.getRight() + "概率追踪到" + tri.getLeft() + "的代码段" + tri.getMid()); + int tracing_ways = msg.traced_path_ls.size(); + for (int i = 0; i < tracing_ways; i++) { + TracedMessagePath tracing_way = msg.traced_path_ls.get(i); + System.out.println(msg.msg + "的第" + (i + 1) + "种追踪链:"); + int identifier_num = tracing_way.path.size(); + for (int j = 0; j < identifier_num; j++) { + Triple traced_file = tracing_way.path.get(j); + System.out.println("\t" + msg.msg + "的第" + (j + 1) + "个标识符有" + traced_file.getRight() + "概率追踪到文件" + traced_file.getLeft() + "的代码段" + traced_file.getMid()); } } } diff --git a/src/main/java/com/hy/java/uct/sdtocode/reader/CodeReader.java b/src/main/java/com/hy/java/uct/sdtocode/reader/CodeReader.java index 05f5465fcf00395e8c717a3368ded8c4ccf4736a..c74b33d49cdbd84ed6a29401003625290eed14b5 100644 --- a/src/main/java/com/hy/java/uct/sdtocode/reader/CodeReader.java +++ b/src/main/java/com/hy/java/uct/sdtocode/reader/CodeReader.java @@ -23,7 +23,7 @@ public class CodeReader { // 读取配置文件 FileEditor code_path_file = new FileEditor(code_path_file_path); // 配置文件指定的代码根目录所对应的FileNode。遍历其children下的java文件即可 - FileNode code_files_root = Traverser.traverseDir(code_path_file.readFileToString()); + FileNode code_files_root = Traverser.traverseDir(code_path_file.readFileToString(), false); // 遍历 System.out.println("正在遍历" + code_files_root.path + "下的源码文件,请稍候..."); filterJavaFileFromFN(code_files_root, result); diff --git a/src/main/java/com/hy/java/uct/sdtocode/util/TracedMessagePath.java b/src/main/java/com/hy/java/uct/sdtocode/util/TracedMessagePath.java index 36d66ce1b486a4887091599d45419b08b2642946..127b5e20cc45e8eb751e4e1d5eeec8dce4dedb90 100644 --- a/src/main/java/com/hy/java/uct/sdtocode/util/TracedMessagePath.java +++ b/src/main/java/com/hy/java/uct/sdtocode/util/TracedMessagePath.java @@ -13,6 +13,8 @@ import com.hy.java.utility.common.Triple; * 每条链路有个总体的追踪概率 */ public class TracedMessagePath { - // 一条链路:由一系列组成的List + /** + * 一条链路:由一系列{@code }组成的List + */ public List> path = new ArrayList<>(); } diff --git a/src/main/java/com/hy/java/uct/umlrecog/ClassDiagramRecognizer.java b/src/main/java/com/hy/java/uct/umlrecog/ClassDiagramRecognizer.java index 99f4ea845a332cf7e119b63f5c750c8ed5629758..9ab813e5d6e6037315ababa01d89189812ed32c9 100644 --- a/src/main/java/com/hy/java/uct/umlrecog/ClassDiagramRecognizer.java +++ b/src/main/java/com/hy/java/uct/umlrecog/ClassDiagramRecognizer.java @@ -190,7 +190,7 @@ public class ClassDiagramRecognizer { // 用repo_name去查找图片时所使用的字符串 String search_string = cd_dir + "cd-" + repo_name.replaceAll("/", "_"); // 遍历cd_dir下的文件 - List cd_dir_files = Traverser.traverseDir(cd_dir).children; + List cd_dir_files = Traverser.traverseDir(cd_dir, false).children; for (FileNode cd_dir_file : cd_dir_files) { // 过滤掉result.txt文件,只找图片 if (cd_dir_file.path.equals(cd_dir + "result.txt")) { diff --git a/src/main/java/com/hy/java/uct/umlrecog/SequenceDiagramRecognizer.java b/src/main/java/com/hy/java/uct/umlrecog/SequenceDiagramRecognizer.java index 959e49363489d2acfb9e2a1e080c04b04165f5e6..6ee12078d2eaa227c2ff87bf96b655c54ae00807 100644 --- a/src/main/java/com/hy/java/uct/umlrecog/SequenceDiagramRecognizer.java +++ b/src/main/java/com/hy/java/uct/umlrecog/SequenceDiagramRecognizer.java @@ -84,7 +84,7 @@ public class SequenceDiagramRecognizer { // 用repo_name去查找图片时所使用的字符串 String search_string = sd_dir + "sd-" + repo_name.replaceAll("/", "_"); // 遍历cd_dir下的文件 - List sd_dir_files = Traverser.traverseDir(sd_dir).children; + List sd_dir_files = Traverser.traverseDir(sd_dir, false).children; for (FileNode sd_dir_file : sd_dir_files) { // 过滤掉result.txt文件,只找图片 if (sd_dir_file.path.equals(sd_dir + "result.txt")) { diff --git a/src/main/java/com/hy/java/uct/util/sd/UMLObject.java b/src/main/java/com/hy/java/uct/util/sd/UMLObject.java index 5dcdf64bc953ed319266d6d0d8b1a724d5b27466..abd7171133e8eec5e125bd5df459f8c948ce496a 100644 --- a/src/main/java/com/hy/java/uct/util/sd/UMLObject.java +++ b/src/main/java/com/hy/java/uct/util/sd/UMLObject.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.List; import com.hy.java.uct.umlrecog.util.Rectangle; -import com.hy.java.uct.util.cd.UMLClass; public class UMLObject { /**