This commit is contained in:
BoykoAlex
2022-02-03 12:35:58 -05:00
parent 9ef209da74
commit 50a65d890a
8 changed files with 292 additions and 63 deletions

View File

@@ -106,6 +106,7 @@
<reactor-netty>0.7.5.RELEASE</reactor-netty>
<commons-io-version>2.4</commons-io-version>
<commons-codec-version>1.13</commons-codec-version>
<rewrite-version>7.18.0-SNAPSHOT</rewrite-version>
<signing.skip>true</signing.skip>
<signing.alias>vmware</signing.alias>

View File

@@ -38,6 +38,17 @@
<enabled>true</enabled>
</releases>
</repository>
<repository>
<id>rewrite-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<dependencies>
@@ -93,6 +104,11 @@
<artifactId>org.eclipse.jdt.core</artifactId>
<version>${jdt.core.version}</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
<version>${rewrite-version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>

View File

@@ -36,6 +36,7 @@ import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.SymbolCache;
import org.springframework.ide.vscode.boot.java.utils.SymbolCacheOnDisc;
import org.springframework.ide.vscode.boot.java.utils.SymbolCacheVoid;
@@ -150,12 +151,12 @@ public class BootLanguageServerBootApp {
}
@ConditionalOnMissingClass("org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness")
@Bean SourceLinks sourceLinks(SimpleLanguageServer server, CompilationUnitCache cuCache, BootLanguageServerParams params) {
@Bean SourceLinks sourceLinks(SimpleLanguageServer server, ORCompilationUnitCache cuCache, BootLanguageServerParams params) {
return SourceLinkFactory.createSourceLinks(server, cuCache, params.projectFinder);
}
@Bean CompilationUnitCache cuCache(SimpleLanguageServer server, BootLanguageServerParams params) {
return new CompilationUnitCache(params.projectFinder, server, params.projectObserver);
@Bean ORCompilationUnitCache cuCache(SimpleLanguageServer server, BootLanguageServerParams params) {
return new ORCompilationUnitCache(params.projectFinder, server, params.projectObserver);
}
@Bean SpringXMLCompletionEngine xmlCompletionEngine(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, BootJavaConfig config) {

View File

@@ -17,15 +17,13 @@ import java.nio.file.Path;
import java.util.Optional;
import java.util.Stack;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.openrewrite.java.tree.TypeUtils;
import org.openrewrite.marker.Position;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaModuleData;
@@ -44,11 +42,11 @@ public abstract class AbstractSourceLinks implements SourceLinks {
private static final Logger log = LoggerFactory.getLogger(AbstractSourceLinks.class);
private CompilationUnitCache cuCache;
private ORCompilationUnitCache cuCache;
private JavaProjectFinder projectFinder;
protected AbstractSourceLinks(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
protected AbstractSourceLinks(ORCompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
this.cuCache = cuCache;
this.projectFinder = projectFinder;
}
@@ -142,55 +140,30 @@ public abstract class AbstractSourceLinks implements SourceLinks {
int lastDotIndex = fqName.lastIndexOf('.');
String packageName = fqName.substring(0, lastDotIndex);
String typeName = fqName.substring(lastDotIndex + 1);
if (packageName.equals(cu.getPackage().getName().getFullyQualifiedName())) {
if (packageName.equals(TypeUtils.asFullyQualified(cu.getPackageDeclaration().getExpression().getType()).getFullyQualifiedName())) {
Stack<String> visitedType = new Stack<>();
cu.accept(new ASTVisitor() {
new JavaIsoVisitor<>() {
private boolean visitDeclaration(AbstractTypeDeclaration node) {
visitedType.push(node.getName().getIdentifier());
public org.openrewrite.java.tree.J.ClassDeclaration visitClassDeclaration(org.openrewrite.java.tree.J.ClassDeclaration classDecl, Object p) {
String fqName = classDecl.getType().getFullyQualifiedName();
visitedType.push(fqName);
if (values[1] < 0) {
if (String.join("$", visitedType.toArray(new String[visitedType.size()])).equals(typeName)) {
values[0] = node.getName().getStartPosition();
values[1] = node.getName().getLength();
Position pos = classDecl.getName().getMarkers().findFirst(Position.class).orElseThrow();
values[0] = pos.getStartPosition();
values[1] = pos.getLength();
}
}
return values[1] < 0;
}
@Override
public boolean visit(TypeDeclaration node) {
return visitDeclaration(node);
}
@Override
public boolean visit(AnnotationTypeDeclaration node) {
return visitDeclaration(node);
}
@Override
public boolean visit(EnumDeclaration node) {
return visitDeclaration(node);
}
@Override
public void endVisit(EnumDeclaration node) {
visitedType.pop();
super.endVisit(node);
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
visitedType.pop();
super.endVisit(node);
}
@Override
public void endVisit(TypeDeclaration node) {
visitedType.pop();
super.endVisit(node);
}
});
if (values[1] < 0) {
return super.visitClassDeclaration(classDecl, visitedType);
} else {
return classDecl;
}
};
}.visitNonNull(cu, visitedType);
}
return values[1] < 0 ? null : new Region(values[0], values[1]);
}

View File

@@ -15,10 +15,10 @@ import java.net.URLEncoder;
import java.nio.file.Path;
import java.util.Optional;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IJavaModuleData;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
@@ -37,7 +37,7 @@ public class AtomSourceLinks extends AbstractSourceLinks {
private static Supplier<Logger> LOG = Suppliers.memoize(() -> LoggerFactory.getLogger(AbstractSourceLinks.class));
public AtomSourceLinks(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
public AtomSourceLinks(ORCompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
super(cuCache, projectFinder);
}

View File

@@ -13,7 +13,7 @@ package org.springframework.ide.vscode.boot.java.links;
import java.nio.file.Path;
import java.util.Optional;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
@@ -51,7 +51,7 @@ public final class SourceLinkFactory {
* @param server the boot LS
* @return appropriate source links object
*/
public static SourceLinks createSourceLinks(SimpleLanguageServer server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
public static SourceLinks createSourceLinks(SimpleLanguageServer server, ORCompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
switch (LspClient.currentClient()) {
case VSCODE:
case THEIA:

View File

@@ -13,8 +13,8 @@ package org.springframework.ide.vscode.boot.java.links;
import java.nio.file.Path;
import java.util.Optional;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IJavaModuleData;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
@@ -28,7 +28,7 @@ import org.springframework.ide.vscode.commons.util.text.Region;
*/
public class VSCodeSourceLinks extends AbstractSourceLinks {
public VSCodeSourceLinks(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
public VSCodeSourceLinks(ORCompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
super(cuCache, projectFinder);
}

View File

@@ -0,0 +1,238 @@
package org.springframework.ide.vscode.boot.java.utils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URI;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.Parser.Input;
import org.openrewrite.Result;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.UpdateSourcePositions;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import reactor.core.Disposable;
public class ORCompilationUnitCache implements DocumentContentProvider, Disposable {
private static final Logger logger = LoggerFactory.getLogger(CompilationUnitCache.class);
private static final long CU_ACCESS_EXPIRATION = 1;
private JavaProjectFinder projectFinder;
private ProjectObserver projectObserver;
private final ProjectObserver.Listener projectListener;
private final SimpleTextDocumentService documentService;
private final Cache<URI, CompilationUnit> uriToCu;
private final Cache<IJavaProject, Set<URI>> projectToDocs;
private final Cache<IJavaProject, JavaParser> javaParsers;
public ORCompilationUnitCache(JavaProjectFinder projectFinder, SimpleLanguageServer server, ProjectObserver projectObserver) {
this.projectFinder = projectFinder;
this.projectObserver = projectObserver;
// PT 154618835 - Avoid retaining the CU in the cache as it consumes memory if it hasn't been
// accessed after some time
this.uriToCu = CacheBuilder.newBuilder()
.expireAfterWrite(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES)
.build();
this.projectToDocs = CacheBuilder.newBuilder().build();
this.javaParsers = CacheBuilder.newBuilder().build();
this.documentService = server == null ? null : server.getTextDocumentService();
// IMPORTANT ===> these notifications arrive within the lsp message loop, so reactions to them have to be fast
// and not be blocked by waiting for anything
if (this.documentService != null) {
this.documentService.onDidChangeContent(doc -> invalidateCuForJavaFile(doc.getDocument().getId().getUri()));
this.documentService.onDidClose(doc -> invalidateCuForJavaFile(doc.getId().getUri()));
}
if (this.projectFinder != null) {
for (IJavaProject project : this.projectFinder.all()) {
logger.info("CU Cache: initial lookup env creation for project <{}>", project.getElementName());
loadJavaParser(project);
}
}
this.projectListener = new ProjectObserver.Listener() {
@Override
public void deleted(IJavaProject project) {
logger.info("CU Cache: deleted project {}", project.getElementName());
invalidateProject(project);
}
@Override
public void created(IJavaProject project) {
logger.info("CU Cache: created project {}", project.getElementName());
invalidateProject(project);
loadJavaParser(project);
}
@Override
public void changed(IJavaProject project) {
logger.info("CU Cache: changed project {}", project.getElementName());
invalidateProject(project);
// Load the new cache the value right away
loadJavaParser(project);
}
};
if (this.projectObserver != null) {
this.projectObserver.addListener(this.projectListener);
}
}
public void dispose() {
if (this.projectObserver != null) {
this.projectObserver.removeListener(this.projectListener);
}
}
private JavaParser loadJavaParser(IJavaProject project) {
try {
return javaParsers.get(project, () -> JavaParser.fromJavaVersion().classpath(getClasspathEntries(project)).build());
} catch (ExecutionException e) {
logger.error("{}", e);
return null;
}
}
private static String[] getClasspathEntries(IJavaProject project) throws Exception {
if (project == null) {
return new String[0];
} else {
IClasspath classpath = project.getClasspath();
Stream<File> classpathEntries = IClasspathUtil.getAllBinaryRoots(classpath).stream();
return classpathEntries
.filter(file -> file.exists())
.map(file -> file.getAbsolutePath()).toArray(String[]::new);
}
}
private void invalidateCuForJavaFile(String uriStr) {
logger.info("CU Cache: invalidate AST for {}", uriStr);
URI uri = URI.create(uriStr);
uriToCu.invalidate(uri);
JavaParser parser = javaParsers.getIfPresent(uri);
if (parser != null) {
parser.reset();
}
}
private void invalidateProject(IJavaProject project) {
logger.info("CU Cache: invalidate project <{}>", project.getElementName());
Set<URI> docUris = projectToDocs.getIfPresent(project);
if (docUris != null) {
uriToCu.invalidateAll(docUris);
projectToDocs.invalidate(project);
}
javaParsers.invalidate(project);
}
@Override
public String fetchContent(URI uri) throws Exception {
if (documentService != null) {
TextDocument document = documentService.getLatestSnapshot(uri.toString());
if (document != null) {
return document.get();
}
}
return IOUtils.toString(uri);
}
/**
* Never research shows at the AST is thread-safe when used in read-only mode:
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=58314
*
* This means that the previous implemented synchronization around the requestor
* working on the AST is not necessary as long as the requestor operates in read-only
* mode on the AST nodes.
*
* Warning: Callers should take care to do all AST processing inside of the requestor callback and
* not pass of AST nodes to helper functions that work aynchronously or store AST nodes or ITypeBindings
* for later use. The JDT ASTs are not thread safe!
*/
public <T> T withCompilationUnit(IJavaProject project, URI uri, Function<CompilationUnit, T> requestor) {
logger.info("CU Cache: work item submitted for doc {}", uri.toString());
if (project != null) {
CompilationUnit cu = null;
try {
cu = uriToCu.get(uri, () -> {
JavaParser javaParser = loadJavaParser(project);
Input input = new Input(Paths.get(uri), () -> {
try {
return new ByteArrayInputStream(fetchContent(uri).getBytes());
} catch (Exception e) {
throw new IllegalStateException("Unexpected error fetching document content");
}
});
Result result = new UpdateSourcePositions().run(javaParser.parseInputs(List.of(input), null, new InMemoryExecutionContext())).get(0);
logger.info("CU Cache: created new AST for {}", uri.toString());
return (CompilationUnit) (result.getAfter() == null ? result.getBefore() : result.getAfter());
});
if (cu != null) {
projectToDocs.get(project, () -> new HashSet<>()).add(uri);
}
} catch (Exception e) {
logger.error("", e);
}
if (cu != null) {
try {
logger.info("CU Cache: start work on AST for {}", uri.toString());
return requestor.apply(cu);
}
catch (CancellationException e) {
throw e;
}
catch (Exception e) {
logger.error("", e);
}
finally {
logger.info("CU Cache: end work on AST for {}", uri.toString());
}
}
}
return requestor.apply(null);
}
}