40 import com.sun.source.tree.Scope; |
40 import com.sun.source.tree.Scope; |
41 import com.sun.source.tree.Tree; |
41 import com.sun.source.tree.Tree; |
42 import com.sun.source.tree.Tree.Kind; |
42 import com.sun.source.tree.Tree.Kind; |
43 import com.sun.source.tree.TypeParameterTree; |
43 import com.sun.source.tree.TypeParameterTree; |
44 import com.sun.source.tree.VariableTree; |
44 import com.sun.source.tree.VariableTree; |
45 import com.sun.source.util.JavacTask; |
|
46 import com.sun.source.util.SourcePositions; |
45 import com.sun.source.util.SourcePositions; |
47 import com.sun.source.util.TreePath; |
46 import com.sun.source.util.TreePath; |
48 import com.sun.source.util.TreePathScanner; |
47 import com.sun.source.util.TreePathScanner; |
49 import com.sun.source.util.Trees; |
|
50 import com.sun.tools.javac.api.JavacScope; |
48 import com.sun.tools.javac.api.JavacScope; |
51 import com.sun.tools.javac.api.JavacTaskImpl; |
|
52 import com.sun.tools.javac.code.Flags; |
49 import com.sun.tools.javac.code.Flags; |
53 import com.sun.tools.javac.code.Symbol.CompletionFailure; |
50 import com.sun.tools.javac.code.Symbol.CompletionFailure; |
54 import com.sun.tools.javac.code.Symbol.VarSymbol; |
51 import com.sun.tools.javac.code.Symbol.VarSymbol; |
55 import com.sun.tools.javac.code.Symtab; |
52 import com.sun.tools.javac.code.Symtab; |
56 import com.sun.tools.javac.code.Type; |
53 import com.sun.tools.javac.code.Type; |
57 import com.sun.tools.javac.code.Type.ClassType; |
54 import com.sun.tools.javac.code.Type.ClassType; |
|
55 import jdk.internal.shellsupport.doc.JavadocHelper; |
58 import com.sun.tools.javac.util.Name; |
56 import com.sun.tools.javac.util.Name; |
59 import com.sun.tools.javac.util.Names; |
57 import com.sun.tools.javac.util.Names; |
60 import com.sun.tools.javac.util.Pair; |
58 import com.sun.tools.javac.util.Pair; |
61 import jdk.jshell.CompletenessAnalyzer.CaInfo; |
59 import jdk.jshell.CompletenessAnalyzer.CaInfo; |
62 import jdk.jshell.TaskFactory.AnalyzeTask; |
60 import jdk.jshell.TaskFactory.AnalyzeTask; |
1110 //JDK's classes are stable for both release and fastdebug builds: |
1105 //JDK's classes are stable for both release and fastdebug builds: |
1111 private final String[] keepParameterNames = new String[] { |
1106 private final String[] keepParameterNames = new String[] { |
1112 "-parameters" |
1107 "-parameters" |
1113 }; |
1108 }; |
1114 |
1109 |
1115 private String documentationImpl(String code, int cursor) { |
1110 private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) { |
1116 code = code.substring(0, cursor); |
1111 code = code.substring(0, cursor); |
1117 if (code.trim().isEmpty()) { //TODO: comment handling |
1112 if (code.trim().isEmpty()) { //TODO: comment handling |
1118 code += ";"; |
1113 code += ";"; |
1119 } |
1114 } |
1120 |
1115 |
1121 if (guessKind(code) == Kind.IMPORT) |
1116 if (guessKind(code) == Kind.IMPORT) |
1122 return null; |
1117 return Collections.<Documentation>emptyList(); |
1123 |
1118 |
1124 OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code)); |
1119 OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code)); |
1125 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames); |
1120 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames); |
1126 SourcePositions sp = at.trees().getSourcePositions(); |
1121 SourcePositions sp = at.trees().getSourcePositions(); |
1127 CompilationUnitTree topLevel = at.firstCuTree(); |
1122 CompilationUnitTree topLevel = at.firstCuTree(); |
1128 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor)); |
1123 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor)); |
1129 |
1124 |
1130 if (tp == null) |
1125 if (tp == null) |
1131 return null; |
1126 return Collections.<Documentation>emptyList(); |
1132 |
1127 |
1133 TreePath prevPath = null; |
1128 TreePath prevPath = null; |
1134 while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) { |
1129 while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && |
|
1130 tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER && |
|
1131 tp.getLeaf().getKind() != Kind.MEMBER_SELECT) { |
1135 prevPath = tp; |
1132 prevPath = tp; |
1136 tp = tp.getParentPath(); |
1133 tp = tp.getParentPath(); |
1137 } |
1134 } |
1138 |
1135 |
1139 if (tp == null) |
1136 if (tp == null) |
1140 return null; |
1137 return Collections.<Documentation>emptyList(); |
1141 |
1138 |
|
1139 Stream<Element> elements; |
1142 Iterable<Pair<ExecutableElement, ExecutableType>> candidates; |
1140 Iterable<Pair<ExecutableElement, ExecutableType>> candidates; |
1143 List<? extends ExpressionTree> arguments; |
1141 List<? extends ExpressionTree> arguments; |
1144 |
1142 |
1145 if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) { |
1143 if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) { |
1146 MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf(); |
1144 if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) { |
1147 candidates = methodCandidates(at, tp); |
1145 MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf(); |
1148 arguments = mit.getArguments(); |
1146 candidates = methodCandidates(at, tp); |
|
1147 arguments = mit.getArguments(); |
|
1148 } else { |
|
1149 NewClassTree nct = (NewClassTree) tp.getLeaf(); |
|
1150 candidates = newClassCandidates(at, tp); |
|
1151 arguments = nct.getArguments(); |
|
1152 } |
|
1153 |
|
1154 if (!isEmptyArgumentsContext(arguments)) { |
|
1155 List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath); |
|
1156 List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList(); |
|
1157 |
|
1158 candidates = |
|
1159 this.filterExecutableTypesByArguments(at, candidates, fullActuals) |
|
1160 .stream() |
|
1161 .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent()) |
|
1162 .collect(Collectors.toList()); |
|
1163 } |
|
1164 |
|
1165 elements = Util.stream(candidates).map(method -> method.fst); |
|
1166 } else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) { |
|
1167 Element el = at.trees().getElement(tp); |
|
1168 |
|
1169 if (el == null || |
|
1170 el.asType().getKind() == TypeKind.ERROR || |
|
1171 (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) { |
|
1172 //erroneous element: |
|
1173 return Collections.<Documentation>emptyList(); |
|
1174 } |
|
1175 |
|
1176 elements = Stream.of(el); |
1149 } else { |
1177 } else { |
1150 NewClassTree nct = (NewClassTree) tp.getLeaf(); |
1178 return Collections.<Documentation>emptyList(); |
1151 candidates = newClassCandidates(at, tp); |
1179 } |
1152 arguments = nct.getArguments(); |
1180 |
1153 } |
1181 List<Documentation> result = Collections.emptyList(); |
1154 |
1182 |
1155 if (!isEmptyArgumentsContext(arguments)) { |
1183 try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) { |
1156 List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath); |
1184 result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc)) |
1157 List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList(); |
1185 .filter(r -> r != null) |
1158 |
1186 .collect(Collectors.toList()); |
1159 candidates = |
1187 } catch (IOException ex) { |
1160 this.filterExecutableTypesByArguments(at, candidates, fullActuals) |
1188 proc.debug(ex, "JavadocHelper.close()"); |
1161 .stream() |
1189 } |
1162 .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent()) |
1190 |
1163 .collect(Collectors.toList()); |
1191 return result; |
1164 } |
1192 } |
1165 |
1193 |
1166 try (SourceCache sourceCache = new SourceCache(at)) { |
1194 private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) { |
1167 return Util.stream(candidates) |
1195 String javadoc = null; |
1168 .map(method -> Util.expunge(element2String(sourceCache, method.fst))) |
1196 try { |
1169 .collect(joining("\n")); |
1197 if (hasSyntheticParameterNames(el)) { |
1170 } |
1198 el = helper.getSourceElement(el); |
|
1199 } |
|
1200 if (computeJavadoc) { |
|
1201 javadoc = helper.getResolvedDocComment(el); |
|
1202 } |
|
1203 } catch (IOException ex) { |
|
1204 proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")"); |
|
1205 } |
|
1206 String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true)); |
|
1207 return new DocumentationImpl(signature, javadoc); |
|
1208 } |
|
1209 |
|
1210 public void close() { |
|
1211 for (AutoCloseable closeable : closeables) { |
|
1212 try { |
|
1213 closeable.close(); |
|
1214 } catch (Exception ex) { |
|
1215 proc.debug(ex, "SourceCodeAnalysisImpl.close()"); |
|
1216 } |
|
1217 } |
|
1218 } |
|
1219 |
|
1220 private static final class DocumentationImpl implements Documentation { |
|
1221 |
|
1222 private final String signature; |
|
1223 private final String javadoc; |
|
1224 |
|
1225 public DocumentationImpl(String signature, String javadoc) { |
|
1226 this.signature = signature; |
|
1227 this.javadoc = javadoc; |
|
1228 } |
|
1229 |
|
1230 @Override |
|
1231 public String signature() { |
|
1232 return signature; |
|
1233 } |
|
1234 |
|
1235 @Override |
|
1236 public String javadoc() { |
|
1237 return javadoc; |
|
1238 } |
|
1239 |
1171 } |
1240 } |
1172 |
1241 |
1173 private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) { |
1242 private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) { |
1174 if (arguments.size() == 1) { |
1243 if (arguments.size() == 1) { |
1175 Tree firstArgument = arguments.get(0); |
1244 Tree firstArgument = arguments.get(0); |
1176 return firstArgument.getKind() == Kind.ERRONEOUS; |
1245 return firstArgument.getKind() == Kind.ERRONEOUS; |
1177 } |
1246 } |
1178 return false; |
1247 return false; |
1179 } |
1248 } |
1180 |
1249 |
1181 private String element2String(SourceCache sourceCache, Element el) { |
|
1182 try { |
|
1183 if (hasSyntheticParameterNames(el)) { |
|
1184 el = sourceCache.getSourceMethod(el); |
|
1185 } |
|
1186 } catch (IOException ex) { |
|
1187 proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")"); |
|
1188 } |
|
1189 |
|
1190 return Util.expunge(elementHeader(sourceCache.originalTask, el, !hasSyntheticParameterNames(el))); |
|
1191 } |
|
1192 |
|
1193 private boolean hasSyntheticParameterNames(Element el) { |
1250 private boolean hasSyntheticParameterNames(Element el) { |
1194 if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD) |
1251 if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD) |
1195 return false; |
1252 return false; |
1196 |
1253 |
1197 ExecutableElement ee = (ExecutableElement) el; |
1254 ExecutableElement ee = (ExecutableElement) el; |
1200 return false; |
1257 return false; |
1201 |
1258 |
1202 return ee.getParameters() |
1259 return ee.getParameters() |
1203 .stream() |
1260 .stream() |
1204 .allMatch(param -> param.getSimpleName().toString().startsWith("arg")); |
1261 .allMatch(param -> param.getSimpleName().toString().startsWith("arg")); |
1205 } |
|
1206 |
|
1207 private final class SourceCache implements AutoCloseable { |
|
1208 private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); |
|
1209 private final Map<String, Map<String, Element>> topLevelName2Signature2Method = new HashMap<>(); |
|
1210 private final AnalyzeTask originalTask; |
|
1211 private final StandardJavaFileManager fm; |
|
1212 |
|
1213 public SourceCache(AnalyzeTask originalTask) { |
|
1214 this.originalTask = originalTask; |
|
1215 List<Path> sources = findSources(); |
|
1216 if (sources.iterator().hasNext()) { |
|
1217 StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); |
|
1218 try { |
|
1219 fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sources); |
|
1220 } catch (IOException ex) { |
|
1221 proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.<init>(...)"); |
|
1222 try { |
|
1223 fm.close(); |
|
1224 } catch (IOException closeEx) { |
|
1225 proc.debug(closeEx, "SourceCodeAnalysisImpl.SourceCache.close()"); |
|
1226 } |
|
1227 fm = null; |
|
1228 } |
|
1229 this.fm = fm; |
|
1230 } else { |
|
1231 //don't waste time if there are no sources |
|
1232 this.fm = null; |
|
1233 } |
|
1234 } |
|
1235 |
|
1236 public Element getSourceMethod(Element method) throws IOException { |
|
1237 if (fm == null) |
|
1238 return method; |
|
1239 |
|
1240 TypeElement type = topLevelType(method); |
|
1241 |
|
1242 if (type == null) |
|
1243 return method; |
|
1244 |
|
1245 String binaryName = originalTask.task.getElements().getBinaryName(type).toString(); |
|
1246 |
|
1247 Map<String, Element> cache = topLevelName2Signature2Method.get(binaryName); |
|
1248 |
|
1249 if (cache == null) { |
|
1250 topLevelName2Signature2Method.put(binaryName, cache = createMethodCache(binaryName)); |
|
1251 } |
|
1252 |
|
1253 String handle = elementHeader(originalTask, method, false); |
|
1254 |
|
1255 return cache.getOrDefault(handle, method); |
|
1256 } |
|
1257 |
|
1258 private TypeElement topLevelType(Element el) { |
|
1259 while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) { |
|
1260 el = el.getEnclosingElement(); |
|
1261 } |
|
1262 |
|
1263 return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null; |
|
1264 } |
|
1265 |
|
1266 private Map<String, Element> createMethodCache(String binaryName) throws IOException { |
|
1267 Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName); |
|
1268 |
|
1269 if (source == null) |
|
1270 return Collections.emptyMap(); |
|
1271 |
|
1272 Map<String, Element> signature2Method = new HashMap<>(); |
|
1273 Trees trees = Trees.instance(source.fst); |
|
1274 |
|
1275 new TreePathScanner<Void, Void>() { |
|
1276 @Override |
|
1277 public Void visitMethod(MethodTree node, Void p) { |
|
1278 Element currentMethod = trees.getElement(getCurrentPath()); |
|
1279 |
|
1280 if (currentMethod != null) { |
|
1281 signature2Method.put(elementHeader(originalTask, currentMethod, false), currentMethod); |
|
1282 } |
|
1283 |
|
1284 return null; |
|
1285 } |
|
1286 }.scan(source.snd, null); |
|
1287 |
|
1288 return signature2Method; |
|
1289 } |
|
1290 |
|
1291 private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException { |
|
1292 JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH, |
|
1293 binaryName, |
|
1294 JavaFileObject.Kind.SOURCE); |
|
1295 |
|
1296 if (jfo == null) |
|
1297 return null; |
|
1298 |
|
1299 List<JavaFileObject> jfos = Arrays.asList(jfo); |
|
1300 JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fm, d -> {}, null, null, jfos); |
|
1301 Iterable<? extends CompilationUnitTree> cuts = task.parse(); |
|
1302 |
|
1303 task.enter(); |
|
1304 |
|
1305 return Pair.of(task, cuts.iterator().next()); |
|
1306 } |
|
1307 |
|
1308 @Override |
|
1309 public void close() { |
|
1310 try { |
|
1311 if (fm != null) { |
|
1312 fm.close(); |
|
1313 } |
|
1314 } catch (IOException ex) { |
|
1315 proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.close()"); |
|
1316 } |
|
1317 } |
|
1318 } |
1262 } |
1319 |
1263 |
1320 private List<Path> availableSources; |
1264 private List<Path> availableSources; |
1321 |
1265 |
1322 private List<Path> findSources() { |
1266 private List<Path> findSources() { |
1326 List<Path> result = new ArrayList<>(); |
1270 List<Path> result = new ArrayList<>(); |
1327 Path home = Paths.get(System.getProperty("java.home")); |
1271 Path home = Paths.get(System.getProperty("java.home")); |
1328 Path srcZip = home.resolve("src.zip"); |
1272 Path srcZip = home.resolve("src.zip"); |
1329 if (!Files.isReadable(srcZip)) |
1273 if (!Files.isReadable(srcZip)) |
1330 srcZip = home.getParent().resolve("src.zip"); |
1274 srcZip = home.getParent().resolve("src.zip"); |
1331 if (Files.isReadable(srcZip)) |
1275 if (Files.isReadable(srcZip)) { |
1332 result.add(srcZip); |
1276 boolean keepOpen = false; |
|
1277 FileSystem zipFO = null; |
|
1278 |
|
1279 try { |
|
1280 URI uri = URI.create("jar:" + srcZip.toUri()); |
|
1281 zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap()); |
|
1282 Path root = zipFO.getRootDirectories().iterator().next(); |
|
1283 |
|
1284 if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) { |
|
1285 //non-modular format: |
|
1286 result.add(srcZip); |
|
1287 } else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) { |
|
1288 //modular format: |
|
1289 try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) { |
|
1290 for (Path p : ds) { |
|
1291 if (Files.isDirectory(p)) { |
|
1292 result.add(p); |
|
1293 } |
|
1294 } |
|
1295 } |
|
1296 |
|
1297 keepOpen = true; |
|
1298 } |
|
1299 } catch (IOException ex) { |
|
1300 proc.debug(ex, "SourceCodeAnalysisImpl.findSources()"); |
|
1301 } finally { |
|
1302 if (zipFO != null) { |
|
1303 if (keepOpen) { |
|
1304 closeables.add(zipFO); |
|
1305 } else { |
|
1306 try { |
|
1307 zipFO.close(); |
|
1308 } catch (IOException ex) { |
|
1309 proc.debug(ex, "SourceCodeAnalysisImpl.findSources()"); |
|
1310 } |
|
1311 } |
|
1312 } |
|
1313 } |
|
1314 } |
1333 return availableSources = result; |
1315 return availableSources = result; |
1334 } |
1316 } |
1335 |
1317 |
1336 private String elementHeader(AnalyzeTask at, Element el) { |
1318 private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) { |
1337 return elementHeader(at, el, true); |
|
1338 } |
|
1339 |
|
1340 private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames) { |
|
1341 switch (el.getKind()) { |
1319 switch (el.getKind()) { |
1342 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: { |
1320 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: { |
1343 TypeElement type = (TypeElement)el; |
1321 TypeElement type = (TypeElement)el; |
1344 String fullname = type.getQualifiedName().toString(); |
1322 String fullname = type.getQualifiedName().toString(); |
1345 Element pkg = at.getElements().getPackageOf(el); |
1323 Element pkg = at.getElements().getPackageOf(el); |
1346 String name = pkg == null ? fullname : |
1324 String name = pkg == null || useFQN ? fullname : |
1347 proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString()); |
1325 proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString()); |
1348 |
1326 |
1349 return name + typeParametersOpt(at, type.getTypeParameters()); |
1327 return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames); |
1350 } |
1328 } |
1351 case TYPE_PARAMETER: { |
1329 case TYPE_PARAMETER: { |
1352 TypeParameterElement tp = (TypeParameterElement)el; |
1330 TypeParameterElement tp = (TypeParameterElement)el; |
1353 String name = tp.getSimpleName().toString(); |
1331 String name = tp.getSimpleName().toString(); |
1354 |
1332 |