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..395501302 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CUResolver.java @@ -0,0 +1,361 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +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; + +/** + * Reflection based implementation of JDT package public CompilationUnitResolver. + * It is used to resolve the {@link CompilationUnitDeclaration} + * + * @author Alex Boyko + * + */ +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> 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 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; + } + +} 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 ff4dc4b94..710c08b30 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2019 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -13,8 +13,11 @@ package org.springframework.ide.vscode.boot.java.utils; import java.io.File; 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; @@ -23,10 +26,17 @@ import java.util.function.Function; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; 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.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.core.BasicCompilationUnit; +import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; +import org.eclipse.jdt.internal.core.INameEnvironmentWithProgress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.java.IClasspath; @@ -40,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); @@ -49,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; @@ -59,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() @@ -77,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() { @@ -111,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; }); @@ -150,41 +222,94 @@ public final class CompilationUnitCache implements DocumentContentProvider { } } - public static CompilationUnit parse(TextDocument document, IJavaProject project) throws Exception { - String[] classpathEntries = getClasspathEntries(project); - String docURI = document.getUri(); - String unitName = docURI.substring(docURI.lastIndexOf("/")); - char[] source = document.get(0, document.getLength()).toCharArray(); - return parse(source, docURI, unitName, classpathEntries); +// public static CompilationUnit parse(TextDocument document, IJavaProject project) throws Exception { +// String[] classpathEntries = getClasspathEntries(project); +// String docURI = document.getUri(); +// String unitName = docURI.substring(docURI.lastIndexOf("/")); +// char[] source = document.get(0, document.getLength()).toCharArray(); +// return parse(source, docURI, unitName, classpathEntries); +// } +// +// 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); +// } +// +// public static CompilationUnit parse(char[] source, String docURI, String unitName, String[] classpathEntries) throws Exception { +// ASTParser parser = ASTParser.newParser(AST.JLS11); +// 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(true); +// +// String[] sourceEntries = new String[] {}; +// parser.setEnvironment(classpathEntries, sourceEntries, null, false); +// +// parser.setUnitName(unitName); +// parser.setSource(source); +// +// CompilationUnit cu = (CompilationUnit) parser.createAST(null); +// +// return cu; +// } + + public static CompilationUnit parse2(char[] source, String docURI, String unitName, IJavaProject project) throws Exception { + List classpaths = createClasspath(getClasspathEntries(project)); + return parse2(source, docURI, unitName, classpaths, null); } - - public static 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); - } - - public static CompilationUnit parse(char[] source, String docURI, String unitName, String[] classpathEntries) throws Exception { - ASTParser parser = ASTParser.newParser(AST.JLS11); + + 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(true); - - String[] sourceEntries = new String[] {}; - parser.setEnvironment(classpathEntries, sourceEntries, null, false); - - parser.setUnitName(unitName); - parser.setSource(source); - - CompilationUnit cu = (CompilationUnit) parser.createAST(null); + String apiLevel = JavaCore.VERSION_11; + JavaCore.setComplianceOptions(apiLevel, options); + if (environment == null) { + environment = CUResolver.createLookupEnvironment(classpaths.toArray(new Classpath[classpaths.size()])); + } + + BasicCompilationUnit sourceUnit = new BasicCompilationUnit(source, null, unitName, (IJavaElement) null); + + 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; + } + + 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]; @@ -200,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/java/utils/test/AstParserTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/AstParserTest.java new file mode 100644 index 000000000..5431b57ad --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/AstParserTest.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.net.URI; +import java.net.URL; + +import org.apache.commons.io.IOUtils; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.junit.Before; +import org.junit.Test; +import org.springframework.ide.vscode.boot.java.links.SourceLinks; +import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; + + +public class AstParserTest { + + private ProjectsHarness projects = ProjectsHarness.INSTANCE; + + private MavenJavaProject jp; + + @Before + public void setup() throws Exception { + jp = projects.mavenProject("empty-boot-15-web-app"); + assertTrue(jp.getIndex().findType("org.springframework.boot.SpringApplication").exists()); + } + + @Test + public void test1() 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.parse2(content, uri.toString(), unitName, jp); + + assertNotNull(cu); + + cu.accept(new ASTVisitor() { + + @Override + public boolean visit(TypeDeclaration node) { + ITypeBinding binding = node.resolveBinding(); + assertNotNull(binding); + return super.visit(node); + } + + @Override + public boolean visit(SingleMemberAnnotation node) { + IAnnotationBinding annotationBinding = node.resolveAnnotationBinding(); + assertNotNull(annotationBinding); + ITypeBinding binding = node.resolveTypeBinding(); + assertNotNull(binding); + return super.visit(node); + } + + @Override + public boolean visit(NormalAnnotation node) { + IAnnotationBinding annotationBinding = node.resolveAnnotationBinding(); + assertNotNull(annotationBinding); + ITypeBinding binding = node.resolveTypeBinding(); + assertNotNull(binding); + return super.visit(node); + } + + @Override + public boolean visit(MarkerAnnotation node) { + IAnnotationBinding annotationBinding = node.resolveAnnotationBinding(); + assertNotNull(annotationBinding); + ITypeBinding binding = node.resolveTypeBinding(); + assertNotNull(binding); + return super.visit(node); + } + + @Override + public boolean visit(MethodDeclaration node) { + IMethodBinding binding = node.resolveBinding(); + assertNotNull(binding); + if (node.getReturnType2() != null) { + ITypeBinding returnTypeBinding = node.getReturnType2().resolveBinding(); + assertNotNull(returnTypeBinding); + } + return super.visit(node); + } + + @Override + public boolean visit(FieldDeclaration node) { + ITypeBinding binding = node.getType().resolveBinding(); + assertNotNull(binding); + return super.visit(node); + } + + }); + + } + +}