38 import com.sun.source.tree.NewClassTree; |
38 import com.sun.source.tree.NewClassTree; |
39 import com.sun.source.tree.Scope; |
39 import com.sun.source.tree.Scope; |
40 import com.sun.source.tree.Tree; |
40 import com.sun.source.tree.Tree; |
41 import com.sun.source.tree.Tree.Kind; |
41 import com.sun.source.tree.Tree.Kind; |
42 import com.sun.source.tree.VariableTree; |
42 import com.sun.source.tree.VariableTree; |
|
43 import com.sun.source.util.JavacTask; |
43 import com.sun.source.util.SourcePositions; |
44 import com.sun.source.util.SourcePositions; |
44 import com.sun.source.util.TreePath; |
45 import com.sun.source.util.TreePath; |
45 import com.sun.source.util.TreePathScanner; |
46 import com.sun.source.util.TreePathScanner; |
|
47 import com.sun.source.util.Trees; |
46 import com.sun.tools.javac.api.JavacScope; |
48 import com.sun.tools.javac.api.JavacScope; |
|
49 import com.sun.tools.javac.api.JavacTaskImpl; |
47 import com.sun.tools.javac.code.Flags; |
50 import com.sun.tools.javac.code.Flags; |
48 import com.sun.tools.javac.code.Symbol.CompletionFailure; |
51 import com.sun.tools.javac.code.Symbol.CompletionFailure; |
49 import com.sun.tools.javac.code.Symbol.VarSymbol; |
52 import com.sun.tools.javac.code.Symbol.VarSymbol; |
50 import com.sun.tools.javac.code.Symtab; |
53 import com.sun.tools.javac.code.Symtab; |
51 import com.sun.tools.javac.code.Type; |
54 import com.sun.tools.javac.code.Type; |
981 .stream() |
994 .stream() |
982 .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent()) |
995 .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent()) |
983 .collect(Collectors.toList()); |
996 .collect(Collectors.toList()); |
984 } |
997 } |
985 |
998 |
986 return Util.stream(candidates) |
999 try (SourceCache sourceCache = new SourceCache(at)) { |
987 .map(method -> Util.expunge(element2String(method.fst))) |
1000 return Util.stream(candidates) |
988 .collect(joining("\n")); |
1001 .map(method -> Util.expunge(element2String(sourceCache, method.fst))) |
|
1002 .collect(joining("\n")); |
|
1003 } |
989 } |
1004 } |
990 |
1005 |
991 private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) { |
1006 private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) { |
992 if (arguments.size() == 1) { |
1007 if (arguments.size() == 1) { |
993 Tree firstArgument = arguments.get(0); |
1008 Tree firstArgument = arguments.get(0); |
994 return firstArgument.getKind() == Kind.ERRONEOUS; |
1009 return firstArgument.getKind() == Kind.ERRONEOUS; |
995 } |
1010 } |
996 return false; |
1011 return false; |
997 } |
1012 } |
998 |
1013 |
999 private String element2String(Element el) { |
1014 private String element2String(SourceCache sourceCache, Element el) { |
|
1015 try { |
|
1016 if (hasSyntheticParameterNames(el)) { |
|
1017 el = sourceCache.getSourceMethod(el); |
|
1018 } |
|
1019 } catch (IOException ex) { |
|
1020 proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")"); |
|
1021 } |
|
1022 |
|
1023 return Util.expunge(elementHeader(el)); |
|
1024 } |
|
1025 |
|
1026 private boolean hasSyntheticParameterNames(Element el) { |
|
1027 if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD) |
|
1028 return false; |
|
1029 |
|
1030 ExecutableElement ee = (ExecutableElement) el; |
|
1031 |
|
1032 if (ee.getParameters().isEmpty()) |
|
1033 return false; |
|
1034 |
|
1035 return ee.getParameters() |
|
1036 .stream() |
|
1037 .allMatch(param -> param.getSimpleName().toString().startsWith("arg")); |
|
1038 } |
|
1039 |
|
1040 private final class SourceCache implements AutoCloseable { |
|
1041 private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); |
|
1042 private final Map<String, Map<String, Element>> topLevelName2Signature2Method = new HashMap<>(); |
|
1043 private final AnalyzeTask originalTask; |
|
1044 private final StandardJavaFileManager fm; |
|
1045 |
|
1046 public SourceCache(AnalyzeTask originalTask) { |
|
1047 this.originalTask = originalTask; |
|
1048 Iterable<? extends Path> sources = findSources(); |
|
1049 if (sources.iterator().hasNext()) { |
|
1050 StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); |
|
1051 try { |
|
1052 fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sources); |
|
1053 } catch (IOException ex) { |
|
1054 proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.<init>(...)"); |
|
1055 fm = null; |
|
1056 } |
|
1057 this.fm = fm; |
|
1058 } else { |
|
1059 //don't waste time if there are no sources |
|
1060 this.fm = null; |
|
1061 } |
|
1062 } |
|
1063 |
|
1064 public Element getSourceMethod(Element method) throws IOException { |
|
1065 if (fm == null) |
|
1066 return method; |
|
1067 |
|
1068 TypeElement type = topLevelType(method); |
|
1069 |
|
1070 if (type == null) |
|
1071 return method; |
|
1072 |
|
1073 String binaryName = originalTask.task.getElements().getBinaryName(type).toString(); |
|
1074 |
|
1075 Map<String, Element> cache = topLevelName2Signature2Method.get(binaryName); |
|
1076 |
|
1077 if (cache == null) { |
|
1078 topLevelName2Signature2Method.put(binaryName, cache = createMethodCache(binaryName)); |
|
1079 } |
|
1080 |
|
1081 String handle = elementHeader(method, false); |
|
1082 |
|
1083 return cache.getOrDefault(handle, method); |
|
1084 } |
|
1085 |
|
1086 private TypeElement topLevelType(Element el) { |
|
1087 while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) { |
|
1088 el = el.getEnclosingElement(); |
|
1089 } |
|
1090 |
|
1091 return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null; |
|
1092 } |
|
1093 |
|
1094 private Map<String, Element> createMethodCache(String binaryName) throws IOException { |
|
1095 Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName); |
|
1096 |
|
1097 if (source == null) |
|
1098 return Collections.emptyMap(); |
|
1099 |
|
1100 Map<String, Element> signature2Method = new HashMap<>(); |
|
1101 Trees trees = Trees.instance(source.fst); |
|
1102 |
|
1103 new TreePathScanner<Void, Void>() { |
|
1104 @Override @DefinedBy(Api.COMPILER_TREE) |
|
1105 public Void visitMethod(MethodTree node, Void p) { |
|
1106 Element currentMethod = trees.getElement(getCurrentPath()); |
|
1107 |
|
1108 if (currentMethod != null) { |
|
1109 signature2Method.put(elementHeader(currentMethod, false), currentMethod); |
|
1110 } |
|
1111 |
|
1112 return null; |
|
1113 } |
|
1114 }.scan(source.snd, null); |
|
1115 |
|
1116 return signature2Method; |
|
1117 } |
|
1118 |
|
1119 private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException { |
|
1120 JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH, |
|
1121 binaryName, |
|
1122 JavaFileObject.Kind.SOURCE); |
|
1123 |
|
1124 if (jfo == null) |
|
1125 return null; |
|
1126 |
|
1127 List<JavaFileObject> jfos = Arrays.asList(jfo); |
|
1128 JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fm, d -> {}, null, null, jfos); |
|
1129 Iterable<? extends CompilationUnitTree> cuts = task.parse(); |
|
1130 |
|
1131 task.enter(); |
|
1132 |
|
1133 return Pair.of(task, cuts.iterator().next()); |
|
1134 } |
|
1135 |
|
1136 @Override |
|
1137 public void close() { |
|
1138 try { |
|
1139 if (fm != null) { |
|
1140 fm.close(); |
|
1141 } |
|
1142 } catch (IOException ex) { |
|
1143 proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.close()"); |
|
1144 } |
|
1145 } |
|
1146 } |
|
1147 |
|
1148 private Iterable<? extends Path> availableSources; |
|
1149 |
|
1150 private Iterable<? extends Path> findSources() { |
|
1151 if (availableSources != null) { |
|
1152 return availableSources; |
|
1153 } |
|
1154 List<Path> result = new ArrayList<>(); |
|
1155 Path home = Paths.get(System.getProperty("java.home")); |
|
1156 Path srcZip = home.resolve("src.zip"); |
|
1157 if (!Files.isReadable(srcZip)) |
|
1158 srcZip = home.getParent().resolve("src.zip"); |
|
1159 if (Files.isReadable(srcZip)) |
|
1160 result.add(srcZip); |
|
1161 return availableSources = result; |
|
1162 } |
|
1163 |
|
1164 private String elementHeader(Element el) { |
|
1165 return elementHeader(el, true); |
|
1166 } |
|
1167 |
|
1168 private String elementHeader(Element el, boolean includeParameterNames) { |
1000 switch (el.getKind()) { |
1169 switch (el.getKind()) { |
1001 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: |
1170 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: |
1002 return ((TypeElement) el).getQualifiedName().toString(); |
1171 return ((TypeElement) el).getQualifiedName().toString(); |
1003 case FIELD: |
1172 case FIELD: |
1004 return element2String(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType(); |
1173 return elementHeader(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType(); |
1005 case ENUM_CONSTANT: |
1174 case ENUM_CONSTANT: |
1006 return element2String(el.getEnclosingElement()) + "." + el.getSimpleName(); |
1175 return elementHeader(el.getEnclosingElement()) + "." + el.getSimpleName(); |
1007 case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: |
1176 case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: |
1008 return el.getSimpleName() + ":" + el.asType(); |
1177 return el.getSimpleName() + ":" + el.asType(); |
1009 case CONSTRUCTOR: case METHOD: |
1178 case CONSTRUCTOR: case METHOD: |
1010 StringBuilder header = new StringBuilder(); |
1179 StringBuilder header = new StringBuilder(); |
1011 header.append(element2String(el.getEnclosingElement())); |
1180 header.append(elementHeader(el.getEnclosingElement())); |
1012 if (el.getKind() == ElementKind.METHOD) { |
1181 if (el.getKind() == ElementKind.METHOD) { |
1013 header.append("."); |
1182 header.append("."); |
1014 header.append(el.getSimpleName()); |
1183 header.append(el.getSimpleName()); |
1015 } |
1184 } |
1016 header.append("("); |
1185 header.append("("); |