WIP
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user