diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java index 0b5522993..127b49a3d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java @@ -113,7 +113,9 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP if (nameRange.isPresent()) { List codeLenses = assembleCodeLenses(project, runningApps, app -> definedBean(app, getBeanType(beanType), id), doc, nameRange.get(), typeDeclaration); - return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; + if (codeLenses != null) { + return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; + } } } } catch (Exception e) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CUResolver.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CUResolver.java new file mode 100644 index 000000000..697e1ef99 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CUResolver.java @@ -0,0 +1,444 @@ +package org.springframework.ide.vscode.boot.java.utils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.core.CancelableProblemFactory; +import org.eclipse.jdt.internal.core.INameEnvironmentWithProgress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Supplier; + +class CUResolver { + + private static final Logger log = LoggerFactory.getLogger(CUResolver.class); + + private static final Supplier> BINDING_TABLES_CLASS = () -> { + try { + return Class.forName("org.eclipse.jdt.core.dom.DefaultBindingResolver$BindingTables"); + } catch (ClassNotFoundException e) { + log.error("{}", e); + return null; + } + }; + + private static final Supplier> BINDING_TABLES_CONSTRUCTOR = () -> { + try { + Class clazz = BINDING_TABLES_CLASS.get(); + if (clazz != null) { + Constructor ctor = clazz.getDeclaredConstructor(); + ctor.setAccessible(true); + return ctor; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier GET_CLASSPATH_METHOD = () -> { + try { + Method getClasspathMethod = ASTParser.class.getDeclaredMethod("getClasspath"); + getClasspathMethod.setAccessible(true); + return getClasspathMethod; + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + return null; + } + }; + + private static final Supplier BITS_FIELD = () -> { + try { + Field field = ASTParser.class.getDeclaredField("bits"); + field.setAccessible(true); + return field; + } catch (SecurityException | NoSuchFieldException e) { + log.error("{}", e); + return null; + } + }; + + private static final Supplier WORKING_COPY_OWNER_FIELD = () -> { + try { + Field field = ASTParser.class.getDeclaredField("workingCopyOwner"); + field.setAccessible(true); + return field; + } catch (SecurityException | NoSuchFieldException e) { + log.error("{}", e); + return null; + } + }; + + private static final Supplier API_LEVEL_FIELD = () -> { + try { + Field field = ASTParser.class.getDeclaredField("apiLevel"); + field.setAccessible(true); + return field; + } catch (SecurityException | NoSuchFieldException e) { + log.error("{}", e); + return null; + } + }; + + private static final Supplier> COMPILATION_UNIT_RESOLVER_CLASS = () -> { + try { + return Class.forName("org.eclipse.jdt.core.dom.CompilationUnitResolver"); + } catch (ClassNotFoundException e) { + log.error("{}", e); + return null; + } + + }; + + private static final Supplier> COMPILATION_UNIT_RESOLVER_CONSTRUCTOR = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Constructor ctor = clazz.getDeclaredConstructor(INameEnvironment.class, IErrorHandlingPolicy.class, + CompilerOptions.class, ICompilerRequestor.class, IProblemFactory.class, IProgressMonitor.class, + boolean.class); + ctor.setAccessible(true); + return ctor; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier> LOOKUP_ENVIRONMENT_CONSTRUCTOR = () -> { + Class nameEnvironmentWithProgressClass; + try { + nameEnvironmentWithProgressClass = Class.forName("org.eclipse.jdt.core.dom.NameEnvironmentWithProgress"); + Constructor lookupCtor = nameEnvironmentWithProgressClass.getDeclaredConstructor( + Classpath[].class, + String[].class, + IProgressMonitor.class + ); + lookupCtor.setAccessible(true); + return lookupCtor; + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) { + log.error("{}", e); + return null; + } + }; + + private static final Supplier GET_HANDLER_POLICY_METHOD = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Method handlerPolicyMethod = clazz.getDeclaredMethod("getHandlingPolicy"); + handlerPolicyMethod.setAccessible(true); + return handlerPolicyMethod; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier GET_REQUESTOR_METHOD = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Method getRequestorMethod = clazz.getDeclaredMethod("getRequestor"); + getRequestorMethod.setAccessible(true); + return getRequestorMethod; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier GET_COMPILER_OPTIONS_METHOD = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Method compilerOptionsMethod = clazz.getDeclaredMethod("getCompilerOptions", Map.class, boolean.class); + compilerOptionsMethod.setAccessible(true); + return compilerOptionsMethod; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier> NODE_SEARCHER_CLASS = () -> { + try { + return Class.forName("org.eclipse.jdt.core.dom.NodeSearcher"); + } catch (ClassNotFoundException e) { + log.error("{}", e); + return null; + } + }; + + private static final Supplier PARSE_METHOD = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + Class nodeSearcherClass = NODE_SEARCHER_CLASS.get(); + if (clazz != null && nodeSearcherClass != null) { + Method parseMethod = clazz.getDeclaredMethod("parse", + org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class, + nodeSearcherClass, + Map.class, + int.class); + parseMethod.setAccessible(true); + return parseMethod; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier RESOLVE_METHOD = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + Class nodeSearcherClass = NODE_SEARCHER_CLASS.get(); + if (clazz != null && nodeSearcherClass != null) { + Method resolveMethod = clazz.getDeclaredMethod("resolve", + CompilationUnitDeclaration.class, + org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class, + nodeSearcherClass, + boolean.class, + boolean.class, + boolean.class); + resolveMethod.setAccessible(true); + return resolveMethod; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier CONVERT_METHOD = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Method convertMethod = clazz.getDeclaredMethod("convert", + CompilationUnitDeclaration.class, + char[].class, + int.class, + Map.class, + boolean.class, + WorkingCopyOwner.class, + BINDING_TABLES_CLASS.get(), + int.class, + IProgressMonitor.class, + boolean.class); + convertMethod.setAccessible(true); + return convertMethod; + } + } catch (NoSuchMethodException | SecurityException e) { + log.error("{}", e); + } + return null; + }; + + private static final Supplier HAS_COMPILATION_ABORTED_FIELD = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Field field = clazz.getDeclaredField("hasCompilationAborted"); + field.setAccessible(true); + return field; + } + } catch (Exception e) { + log.error("{}", e); + } + return null; + }; + + static CompilationUnitDeclaration resolve(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, + List classpaths, Map options, int flags, INameEnvironmentWithProgress environment) + throws JavaModelException { + try { + + CompilerOptions compilerOptions = (CompilerOptions) GET_COMPILER_OPTIONS_METHOD.get().invoke(null, options, + (flags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); + CancelableProblemFactory problemFactory = new CancelableProblemFactory(new NullProgressMonitor()); + boolean ignoreMethodBodies = (flags & ICompilationUnit.IGNORE_METHOD_BODIES) != 0; + compilerOptions.ignoreMethodBodies = ignoreMethodBodies; + Object resolver = COMPILATION_UNIT_RESOLVER_CONSTRUCTOR.get().newInstance(environment, + GET_HANDLER_POLICY_METHOD.get().invoke(null), compilerOptions, + GET_REQUESTOR_METHOD.get().invoke(null), problemFactory, new NullProgressMonitor(), false); + boolean analyzeAndGenerateCode = !ignoreMethodBodies; + // no existing compilation unit declaration + CompilationUnitDeclaration unit = (CompilationUnitDeclaration) RESOLVE_METHOD.get().invoke(resolver, null, + sourceUnit, null, true, // method verification + analyzeAndGenerateCode, // analyze code + analyzeAndGenerateCode); // generate code + boolean hasCompilationAborted = HAS_COMPILATION_ABORTED_FIELD.get().getBoolean(resolver); + if (hasCompilationAborted) { + // the bindings could not be resolved due to missing types in name environment + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=86541 + CompilationUnitDeclaration unitDeclaration = parse(sourceUnit, options, flags); +// if (unit != null) { +// final int problemCount = unit.compilationResult.problemCount; +// if (problemCount != 0) { +// unitDeclaration.compilationResult.problems = new CategorizedProblem[problemCount]; +// System.arraycopy(unit.compilationResult.problems, 0, unitDeclaration.compilationResult.problems, 0, problemCount); +// unitDeclaration.compilationResult.problemCount = problemCount; +// } +// } else if (resolver.abortProblem != null) { +// unitDeclaration.compilationResult.problemCount = 1; +// unitDeclaration.compilationResult.problems = new CategorizedProblem[] { resolver.abortProblem }; +// } + return unitDeclaration; + } + return unit; + + } catch (Exception e) { + log.error("{}", e); + } + return null; + } + + static CompilationUnitDeclaration parse(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, Map options, int flags) { + try { + return (CompilationUnitDeclaration) PARSE_METHOD.get() + .invoke(null, sourceUnit, null, options, flags); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + log.error("{}", e); + } + return null; + } + + static CompilationUnit convert( + CompilationUnitDeclaration compilationUnitDeclaration, + char[] source, + int apiLevel, + Map options, + boolean needToResolveBindings, + WorkingCopyOwner owner, + int flags) { + try { + return (CompilationUnit) CONVERT_METHOD.get().invoke(null, + compilationUnitDeclaration, + source, + apiLevel, + options, + needToResolveBindings, + owner, + needToResolveBindings ? BINDING_TABLES_CONSTRUCTOR.get().newInstance() : null, + flags, + new NullProgressMonitor(), + false); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException + | InstantiationException e) { + log.error("{}", e); + } + return null; + } + + static final Supplier BINDING_RECOVERY_FLAG = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Field field = clazz.getDeclaredField("BINDING_RECOVERY"); + field.setAccessible(true); + return field.getInt(null); + } + } catch (Exception e) { + log.error("{}", e); + } + return 0; + }; + + static final Supplier IGNORE_METHOD_BODIES_FLAG = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Field field = clazz.getDeclaredField("IGNORE_METHOD_BODIES"); + field.setAccessible(true); + return field.getInt(null); + } + } catch (Exception e) { + log.error("{}", e); + } + return 0; + }; + + static final Supplier STATEMENT_RECOVERY_FLAG = () -> { + try { + Class clazz = COMPILATION_UNIT_RESOLVER_CLASS.get(); + if (clazz != null) { + Field field = clazz.getDeclaredField("STATEMENT_RECOVERY"); + field.setAccessible(true); + return field.getInt(null); + } + } catch (Exception e) { + log.error("{}", e); + } + return 0; + }; + static INameEnvironmentWithProgress createLookupEnvironment(Classpath[] classpath) { + try { + return (INameEnvironmentWithProgress) LOOKUP_ENVIRONMENT_CONSTRUCTOR.get().newInstance(classpath, null, new NullProgressMonitor()); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + log.error("{}", e); + } + return null; + } + + @SuppressWarnings("unchecked") + static List getClasspath(ASTParser parser) { + try { + return (List) GET_CLASSPATH_METHOD.get().invoke(parser); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + log.error("{}", e); + } + return null; + } + + static int getBits(ASTParser parser) { + try { + return BITS_FIELD.get().getInt(parser); + } catch (IllegalAccessException | IllegalArgumentException e) { + log.error("{}", e); + } + return 0; + } + + static WorkingCopyOwner getWorkingCopyOwner(ASTParser parser) { + try { + return (WorkingCopyOwner) WORKING_COPY_OWNER_FIELD.get().get(parser); + } catch (IllegalArgumentException | IllegalAccessException e) { + log.error("{}", e); + } + return null; + } + + static int getApiLevel(ASTParser parser) { + try { + return API_LEVEL_FIELD.get().getInt(parser); + } catch (IllegalArgumentException | IllegalAccessException e) { + log.error("{}", e); + } + return 0; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java index 3659147de..e67beb61f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java @@ -11,13 +11,13 @@ package org.springframework.ide.vscode.boot.java.utils; import java.io.File; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.net.URI; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; @@ -26,23 +26,16 @@ import java.util.function.Function; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.internal.compiler.ICompilerRequestor; -import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; -import org.eclipse.jdt.internal.compiler.IProblemFactory; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; -import org.eclipse.jdt.internal.compiler.env.INameEnvironment; -import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.core.BasicCompilationUnit; -import org.eclipse.jdt.internal.core.CancelableProblemFactory; +import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; import org.eclipse.jdt.internal.core.INameEnvironmentWithProgress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +50,9 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + public final class CompilationUnitCache implements DocumentContentProvider { private static final Logger logger = LoggerFactory.getLogger(CompilationUnitCache.class); @@ -66,6 +62,7 @@ public final class CompilationUnitCache implements DocumentContentProvider { private ProjectObserver projectObserver; private Cache uriToCu; private Cache> projectToDocs; + private Cache, INameEnvironmentWithProgress>> lookupEnvCache; private ProjectObserver.Listener projectListener; private SimpleTextDocumentService documents; @@ -76,8 +73,8 @@ public final class CompilationUnitCache implements DocumentContentProvider { this.projectFinder = projectFinder; this.projectObserver = projectObserver; this.documents = documents; - projectListener = ProjectObserver.onAny(this::invalidateProject); - + this.lookupEnvCache = CacheBuilder.newBuilder().build(); + // PT 154618835 - Avoid retaining the CU in the cache as it consumes memory if it hasn't been // accessed after some time uriToCu = CacheBuilder.newBuilder() @@ -94,9 +91,64 @@ public final class CompilationUnitCache implements DocumentContentProvider { documents.onDidClose(doc -> invalidateCuForJavaFile(doc.getId().getUri())); } + CompletableFuture.runAsync(() -> { + writeLock.lock(); + try { + for (IJavaProject project : projectFinder.all()) { + loadLookupEnvTuple(project); + } + } finally { + writeLock.unlock(); + } + }); + + projectListener = new ProjectObserver.Listener() { + + @Override + public void deleted(IJavaProject project) { + CompletableFuture.runAsync(() -> { + writeLock.lock(); + try { + invalidateProject(project); + } finally { + writeLock.unlock(); + } + }); + } + + @Override + public void created(IJavaProject project) { + CompletableFuture.runAsync(() -> { + writeLock.lock(); + try { + invalidateProject(project); + // Load the new cache the value right away + loadLookupEnvTuple(project); + } finally { + writeLock.unlock(); + } + }); + } + + @Override + public void changed(IJavaProject project) { + CompletableFuture.runAsync(() -> { + writeLock.lock(); + try { + invalidateProject(project); + // Load the new cache the value right away + loadLookupEnvTuple(project); + } finally { + writeLock.unlock(); + } + }); + } + }; + if (this.projectObserver != null) { this.projectObserver.addListener(projectListener); } + } public void dispose() { @@ -128,7 +180,10 @@ public final class CompilationUnitCache implements DocumentContentProvider { try { cu = uriToCu.get(uri, () -> { - CompilationUnit cUnit = parse(uri.toString(), fetchContent(uri).toCharArray(), project); + Tuple2, INameEnvironmentWithProgress> lookupEnvTuple = loadLookupEnvTuple(project); + String utiStr = uri.toString(); + String unitName = utiStr.substring(utiStr.lastIndexOf("/")); + CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), utiStr, unitName, lookupEnvTuple.getT1(), lookupEnvTuple.getT2()); projectToDocs.get(project, () -> new HashSet<>()).add(uri); return cUnit; }); @@ -175,7 +230,7 @@ public final class CompilationUnitCache implements DocumentContentProvider { return parse(source, docURI, unitName, classpathEntries); } - public static CompilationUnit parse(String uri, char[] source, IJavaProject project) throws Exception { + public CompilationUnit parse(String uri, char[] source, IJavaProject project) throws Exception { String[] classpathEntries = getClasspathEntries(project); String unitName = uri.substring(uri.lastIndexOf("/")); return parse(source, uri, unitName, classpathEntries); @@ -202,97 +257,59 @@ public final class CompilationUnitCache implements DocumentContentProvider { return cu; } - @SuppressWarnings("unchecked") - public static CompilationUnit parse2(char[] source, String docURI, String unitName, String[] classpathEntries) throws Exception { - ASTParser parser = ASTParser.newParser(AST.JLS11); + public static CompilationUnit parse2(char[] source, String docURI, String unitName, String[] classpathEntries, INameEnvironmentWithProgress environment) throws Exception { + List classpaths = createClasspath(classpathEntries); + return parse2(source, docURI, unitName, classpaths, environment); + } + + private static CompilationUnit parse2(char[] source, String docURI, String unitName, List classpaths, INameEnvironmentWithProgress environment) throws Exception { Map options = JavaCore.getOptions(); - JavaCore.setComplianceOptions(JavaCore.VERSION_11, options); - parser.setCompilerOptions(options); - parser.setKind(ASTParser.K_COMPILATION_UNIT); - parser.setStatementsRecovery(true); - parser.setBindingsRecovery(true); - parser.setResolveBindings(false); - - String[] sourceEntries = new String[] {}; - parser.setEnvironment(classpathEntries, sourceEntries, null, false); - - parser.setUnitName(unitName); - parser.setSource(source); + String apiLevel = JavaCore.VERSION_11; + JavaCore.setComplianceOptions(apiLevel, options); + if (environment == null) { + environment = CUResolver.createLookupEnvironment(classpaths.toArray(new Classpath[classpaths.size()])); + } - Method getClasspathMethod = ASTParser.class.getDeclaredMethod("getClasspath"); - getClasspathMethod.setAccessible(true); - List classpaths = (List) getClasspathMethod.invoke(parser); - - Class clazz = Class.forName("org.eclipse.jdt.core.dom.CompilationUnitResolver"); - Constructor ctor = clazz.getConstructor( - INameEnvironment.class, - IErrorHandlingPolicy.class, - CompilerOptions.class, - ICompilerRequestor.class, - IProblemFactory.class, - IProgressMonitor.class, - boolean.class - ); - ctor.setAccessible(true); - - Classpath[] allEntries = new Classpath[classpaths.size()]; - classpaths.toArray(allEntries); - Class nameEnvironmentWithProgressClass = Class.forName("org.eclipse.jdt.core.dom.NameEnvironmentWithProgress"); - Constructor lookupCtor = nameEnvironmentWithProgressClass.getConstructor( - Classpath[].class, - String[].class, - IProgressMonitor.class - ); - lookupCtor.setAccessible(true); - INameEnvironmentWithProgress environment = (INameEnvironmentWithProgress) lookupCtor.newInstance(allEntries, null, new NullProgressMonitor()); - - Method handlerPolicyMethod = clazz.getDeclaredMethod("getHandlingPolicy"); - handlerPolicyMethod.setAccessible(true); - - Method getRequestorMethod = clazz.getDeclaredMethod("getRequestor"); - getRequestorMethod.setAccessible(true); - - CancelableProblemFactory problemFactory = new CancelableProblemFactory(new NullProgressMonitor()); - - Method compilerOptionsMethod = clazz.getDeclaredMethod("getCompilerOptions", Map.class, boolean.class); - compilerOptionsMethod.setAccessible(true); - Object compilerOptionsObj = compilerOptionsMethod.invoke(parser, options, true); - - Object resolver = ctor.newInstance( - environment, - handlerPolicyMethod.invoke(null), - compilerOptionsObj, - getRequestorMethod.invoke(null), - problemFactory, - new NullProgressMonitor(), - false - ); - BasicCompilationUnit sourceUnit = new BasicCompilationUnit(source, null, unitName, (IJavaElement) null); - CompilationUnit cu = (CompilationUnit) parser.createAST(null); - Class nodeSearcherClass = Class.forName("org.eclipse.jdt.core.dom.NodeSearcher"); - Method resolveMethod = clazz.getDeclaredMethod("resolve", - CompilationUnitDeclaration.class, - org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class, - nodeSearcherClass, - boolean.class, - boolean.class, - boolean.class); - resolveMethod.setAccessible(true); + int flags = 0; + boolean needToResolveBindings = true; + flags |= ICompilationUnit.ENABLE_STATEMENTS_RECOVERY; + flags |= ICompilationUnit.ENABLE_BINDINGS_RECOVERY; + CompilationUnitDeclaration unit = null; + try { + unit = CUResolver.resolve(sourceUnit, classpaths, options, flags, environment); + } catch (Exception e) { + flags &= ~ICompilationUnit.ENABLE_BINDINGS_RECOVERY; + unit = CUResolver.parse(sourceUnit, options, flags); + needToResolveBindings = false; + } - CompilationUnitDeclaration unit = - (CompilationUnitDeclaration) resolveMethod.invoke(resolver, - null, // no existing compilation unit declaration - sourceUnit, - null, - true, // method verification - true, // analyze code - true); // generate code + CompilationUnit cu = CUResolver.convert(unit, source, AST.JLS11, options, needToResolveBindings, DefaultWorkingCopyOwner.PRIMARY, flags); return cu; } + private static List createClasspath(String[] classpathEntries) { + ASTParser parser = ASTParser.newParser(AST.JLS11); + String[] sourceEntries = new String[] {}; + parser.setEnvironment(classpathEntries, sourceEntries, null, false); + return CUResolver.getClasspath(parser); + } + + private Tuple2, INameEnvironmentWithProgress> loadLookupEnvTuple(IJavaProject project) { + try { + return lookupEnvCache.get(project, () -> { + List classpaths = createClasspath(getClasspathEntries(project)); + INameEnvironmentWithProgress environment = CUResolver.createLookupEnvironment(classpaths.toArray(new Classpath[classpaths.size()])); + return Tuples.of(classpaths, environment); + }); + } catch (ExecutionException e) { + logger.error("{}", e); + return null; + } + } + private static String[] getClasspathEntries(IJavaProject project) throws Exception { if (project == null) { return new String[0]; @@ -308,14 +325,10 @@ public final class CompilationUnitCache implements DocumentContentProvider { private void invalidateProject(IJavaProject project) { Set docUris = projectToDocs.getIfPresent(project); if (docUris != null) { - writeLock.lock(); - try { - uriToCu.invalidateAll(docUris); - projectToDocs.invalidate(project); - } finally { - writeLock.unlock(); - } + uriToCu.invalidateAll(docUris); + projectToDocs.invalidate(project); } + lookupEnvCache.invalidate(project); } @Override diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/SomeTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/SomeTest.java index 314f0a108..9e4e80695 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/SomeTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/SomeTest.java @@ -43,9 +43,26 @@ public class SomeTest { char[] content = IOUtils.toString(uri).toCharArray(); - CompilationUnit cu = CompilationUnitCache.parse2(content, uri.toString(), unitName, getClasspathEntries(jp)); + CompilationUnit cu = CompilationUnitCache.parse2(content, uri.toString(), unitName, getClasspathEntries(jp), null); + + System.out.println(cu); } +// @Test +// public void test2() throws Exception { +// URL sourceUrl = SourceLinks.source(jp, "org.springframework.boot.SpringApplication").get(); +// +// URI uri = sourceUrl.toURI(); +// +// String unitName = "SpringApplication"; +// +// char[] content = IOUtils.toString(uri).toCharArray(); +// +// CompilationUnit cu = CompilationUnitCache.parse(content, uri.toString(), unitName, getClasspathEntries(jp)); +// +// System.out.println(cu); +// } + private static String[] getClasspathEntries(IJavaProject project) throws Exception { if (project == null) { return new String[0];