GH-1499: add additional indexing logic to allow additional reconciling of specific files once the index is complete

This commit is contained in:
Martin Lippert
2025-03-20 21:43:59 +01:00
parent d3b086893f
commit 3d1ccd7dfe
8 changed files with 408 additions and 196 deletions

View File

@@ -78,6 +78,10 @@ public class BeanRegistrarDeclarationReconciler implements JdtAstReconciler {
if (ASTUtils.findInTypeHierarchy(type, Set.of(Annotations.BEAN_REGISTRAR_INTERFACE)) == null) {
return true;
}
if (!isIndexComplete) {
throw new RequiredCompleteIndexException();
}
List<Bean> configBeans = new ArrayList<>();
Path p = Path.of(docURI);

View File

@@ -10,8 +10,10 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.reconcilers;
/**
* Indicates that a reconciler or indexer requires the full AST (including method bodies)
* to do its work accordingly.
*/
public class RequiredCompleteAstException extends RuntimeException {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = -3422411902406544588L;
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2025 Broadcom
* 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:
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.reconcilers;
/**
* Indicates that a reconciler relies on the index to be complete before it can
* reconcile a file. As a result, the corresponding file will be re-parsed and re-reconciled
* once the index is complete.
*
* A reconciler should throw this exception only in case it really requires
* access to the index and check other AST-related information first to avoid
* re-reconciling too many files when it is not really necessary.
*
* For example the reconciler that checks BeanRegistrar implementations against
* Import annotations in the index should only throw this exception if the class
* is indeed an implementation of BeanRegistrar.
*/
public class RequiredCompleteIndexException extends RuntimeException {
private static final long serialVersionUID = -6155363860106363727L;
}

View File

@@ -64,6 +64,7 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.reconcilers.CachedDiagnostics;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteIndexException;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -104,7 +105,7 @@ public class SpringIndexerJava implements SpringIndexer {
private final JavaProjectFinder projectFinder;
private final ProgressService progressService;
private final CompilationUnitCache cuCache;
private boolean scanTestJavaSources = false;
private JsonObject validationSeveritySettings;
private int scanChunkSize = 1000;
@@ -219,7 +220,101 @@ public class SpringIndexerJava implements SpringIndexer {
this.cache.removeFiles(beansCacheKey, files, CachedBean.class);
this.cache.removeFiles(diagnosticsCacheKey, files, CachedDiagnostics.class);
}
/**
* Computes document symbols ad-hoc, based on the given doc URI and the given content,
* re-using the AST from the compilation unit cache. This is meant to compute symbols
* for files opened in editors, so that symbols can be based on editor buffer content,
* not the file content on disc.
*
* @deprecated (will use computeDocumentSymbols in the future exclusively)
*/
public List<WorkspaceSymbol> computeSymbols(IJavaProject project, String docURI, String content) throws Exception {
if (content != null) {
URI uri = URI.create(docURI);
return cuCache.withCompilationUnit(project, uri, cu -> {
String file = UriUtil.toFileString(docURI);
SpringIndexerJavaScanResult result = new SpringIndexerJavaScanResult(project, new String[] {file});
IProblemCollector voidProblemCollector = new IProblemCollector() {
@Override
public void endCollecting() {
}
@Override
public void beginCollecting() {
}
@Override
public void accept(ReconcileProblem problem) {
}
};
AtomicReference<TextDocument> docRef = new AtomicReference<>();
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
0, docRef, content, voidProblemCollector, new ArrayList<>(), true, true, result);
scanAST(context, false);
return result.getGeneratedSymbols().stream().map(s -> s.getEnhancedSymbol()).collect(Collectors.toList());
});
}
return Collections.emptyList();
}
/**
* Computes document symbols ad-hoc, based on the given doc URI and the given content,
* re-using the AST from the compilation unit cache. This is meant to compute symbols
* for files opened in editors, so that symbols can be based on editor buffer content,
* not the file content on disc.
*/
@Override
public List<DocumentSymbol> computeDocumentSymbols(IJavaProject project, String docURI, String content) throws Exception {
if (content != null) {
URI uri = URI.create(docURI);
return cuCache.withCompilationUnit(project, uri, cu -> {
String file = UriUtil.toFileString(docURI);
SpringIndexerJavaScanResult result = new SpringIndexerJavaScanResult(project, new String[] {file});
IProblemCollector voidProblemCollector = new IProblemCollector() {
@Override
public void endCollecting() {
}
@Override
public void beginCollecting() {
}
@Override
public void accept(ReconcileProblem problem) {
}
};
AtomicReference<TextDocument> docRef = new AtomicReference<>();
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
0, docRef, content, voidProblemCollector, new ArrayList<>(), true, true, result);
scanAST(context, false);
List<SpringIndexElement> indexElements = result.getGeneratedBeans().stream()
.map(cachedBean -> cachedBean.getBean())
.toList();
return SpringIndexToSymbolsConverter.createDocumentSymbols(indexElements);
});
}
return Collections.emptyList();
}
/**
* filter the list of given documents down to those that should be processed and that don't have
* valid up-to-date cache data anymore
*/
private DocumentDescriptor[] filterDocuments(IJavaProject project, DocumentDescriptor[] updatedDocs) {
IndexCacheKey symbolsCacheKey = getCacheKey(project, SYMBOL_KEY);
IndexCacheKey beansCacheKey = getCacheKey(project, BEANS_KEY);
@@ -232,6 +327,10 @@ public class SpringIndexerJava implements SpringIndexer {
.toArray(DocumentDescriptor[]::new);
}
/**
* checks whether the given doc URI is from inside the given projects "interesting" folders
* This is used to filter out file updates for files that are outside of the source folders (for example)
*/
private boolean shouldProcessDocument(IJavaProject project, String docURI) {
try {
Path path = new File(new URI(docURI)).toPath();
@@ -281,126 +380,47 @@ public class SpringIndexerJava implements SpringIndexer {
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
if (cu != null) {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
List<CachedBean> generatedBeans = new ArrayList<CachedBean>();
List<CachedDiagnostics> generatedDiagnostics = new ArrayList<CachedDiagnostics>();
String file = UriUtil.toFileString(docURI);
SpringIndexerJavaScanResult result = new SpringIndexerJavaScanResult(project, new String[] {file});
AtomicReference<TextDocument> docRef = new AtomicReference<>();
String file = UriUtil.toFileString(docURI);
BiConsumer<String, Diagnostic> diagnosticsAggregator = new BiConsumer<>() {
@Override
public void accept(String docURI, Diagnostic diagnostic) {
generatedDiagnostics.add(new CachedDiagnostics(docURI, diagnostic));
result.getGeneratedDiagnostics().add(new CachedDiagnostics(docURI, diagnostic));
}
};
IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
lastModified, docRef, content, generatedSymbols, generatedBeans, problemCollector, new ArrayList<>(), !ignoreMethodBodies);
lastModified, docRef, content, problemCollector, new ArrayList<>(), !ignoreMethodBodies, true, result);
scanAST(context, true);
result.publishResults(symbolHandler);
// update cache
IndexCacheKey symbolCacheKey = getCacheKey(project, SYMBOL_KEY);
IndexCacheKey beansCacheKey = getCacheKey(project, BEANS_KEY);
IndexCacheKey diagnosticsCacheKey = getCacheKey(project, DIAGNOSTICS_KEY);
this.cache.update(symbolCacheKey, file, lastModified, generatedSymbols, context.getDependencies(), CachedSymbol.class);
this.cache.update(beansCacheKey, file, lastModified, generatedBeans, context.getDependencies(), CachedBean.class);
this.cache.update(diagnosticsCacheKey, file, lastModified, generatedDiagnostics, context.getDependencies(), CachedDiagnostics.class);
// dependencyTracker.dump();
WorkspaceSymbol[] symbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(WorkspaceSymbol[]::new);
List<SpringIndexElement> beans = generatedBeans.stream().filter(cachedBean -> cachedBean.getBean() != null).map(cachedBean -> cachedBean.getBean()).toList();
List<Diagnostic> diagnostics = generatedDiagnostics.stream().filter(cachedDiagnostics -> cachedDiagnostics.getDiagnostic() != null).map(cachedDiagnostic -> cachedDiagnostic.getDiagnostic()).collect(Collectors.toList());
symbolHandler.addSymbols(project, docURI, symbols, beans, diagnostics);
this.cache.update(symbolCacheKey, file, lastModified, result.getGeneratedSymbols(), context.getDependencies(), CachedSymbol.class);
this.cache.update(beansCacheKey, file, lastModified, result.getGeneratedBeans(), context.getDependencies(), CachedBean.class);
this.cache.update(diagnosticsCacheKey, file, lastModified, result.getGeneratedDiagnostics(), context.getDependencies(), CachedDiagnostics.class);
Set<String> scannedFiles = new HashSet<>();
scannedFiles.add(file);
fileScannedEvent(file);
reconcileWithCompleteIndex(project, result.getMarkedForReconcilingWithCompleteIndex());
scanAffectedFiles(project, context.getScannedTypes(), scannedFiles);
}
}
public List<WorkspaceSymbol> computeSymbols(IJavaProject project, String docURI, String content) throws Exception {
if (content != null) {
URI uri = URI.create(docURI);
return cuCache.withCompilationUnit(project, uri, cu -> {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
List<CachedBean> generatedBeans = new ArrayList<CachedBean>();
IProblemCollector voidProblemCollector = new IProblemCollector() {
@Override
public void endCollecting() {
}
@Override
public void beginCollecting() {
}
@Override
public void accept(ReconcileProblem problem) {
}
};
AtomicReference<TextDocument> docRef = new AtomicReference<>();
String file = UriUtil.toFileString(docURI);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, new ArrayList<>(), true);
scanAST(context, false);
return generatedSymbols.stream().map(s -> s.getEnhancedSymbol()).collect(Collectors.toList());
});
}
return Collections.emptyList();
}
@Override
public List<DocumentSymbol> computeDocumentSymbols(IJavaProject project, String docURI, String content) throws Exception {
if (content != null) {
URI uri = URI.create(docURI);
return cuCache.withCompilationUnit(project, uri, cu -> {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
List<CachedBean> generatedBeans = new ArrayList<CachedBean>();
IProblemCollector voidProblemCollector = new IProblemCollector() {
@Override
public void endCollecting() {
}
@Override
public void beginCollecting() {
}
@Override
public void accept(ReconcileProblem problem) {
}
};
AtomicReference<TextDocument> docRef = new AtomicReference<>();
String file = UriUtil.toFileString(docURI);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, new ArrayList<>(), true);
scanAST(context, false);
List<SpringIndexElement> indexElements = generatedBeans.stream()
.map(cachedBean -> cachedBean.getBean())
.toList();
return SpringIndexToSymbolsConverter.createDocumentSymbols(indexElements);
});
}
return Collections.emptyList();
}
private Set<String> scanFilesInternally(IJavaProject project, DocumentDescriptor[] docs) throws Exception {
final boolean ignoreMethodBodies = false;
@@ -420,16 +440,13 @@ public class SpringIndexerJava implements SpringIndexer {
lastModified[i] = docs[i].getLastModified();
}
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
List<CachedBean> generatedBeans = new ArrayList<CachedBean>();
List<CachedDiagnostics> generatedDiagnostics = new ArrayList<CachedDiagnostics>();
SpringIndexerJavaScanResult result = new SpringIndexerJavaScanResult(project, javaFiles);
Multimap<String, String> dependencies = MultimapBuilder.hashKeys().hashSetValues().build();
BiConsumer<String, Diagnostic> diagnosticsAggregator = new BiConsumer<>() {
@Override
public void accept(String docURI, Diagnostic diagnostic) {
generatedDiagnostics.add(new CachedDiagnostics(docURI, diagnostic));
result.getGeneratedDiagnostics().add(new CachedDiagnostics(docURI, diagnostic));
}
};
@@ -447,7 +464,7 @@ public class SpringIndexerJava implements SpringIndexer {
IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath,
lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, new ArrayList<>(), !ignoreMethodBodies);
lastModified, docRef, null, problemCollector, new ArrayList<>(), !ignoreMethodBodies, false, result);
scanAST(context, true);
@@ -468,20 +485,19 @@ public class SpringIndexerJava implements SpringIndexer {
parser.cleanup();
}
WorkspaceSymbol[] symbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(WorkspaceSymbol[]::new);
Map<String, List<SpringIndexElement>> beans = generatedBeans.stream().filter(cachedBean -> cachedBean.getBean() != null).collect(Collectors.groupingBy(CachedBean::getDocURI, Collectors.mapping(CachedBean::getBean, Collectors.toList())));
Map<String, List<Diagnostic>> diagnosticsByDoc = generatedDiagnostics.stream().filter(cachedDiagnostic -> cachedDiagnostic.getDiagnostic() != null).collect(Collectors.groupingBy(CachedDiagnostics::getDocURI, Collectors.mapping(CachedDiagnostics::getDiagnostic, Collectors.toList())));
addEmptyDiagnostics(diagnosticsByDoc, javaFiles);
symbolHandler.addSymbols(project, symbols, beans, diagnosticsByDoc);
// update the cache
IndexCacheKey symbolsCacheKey = getCacheKey(project, SYMBOL_KEY);
IndexCacheKey beansCacheKey = getCacheKey(project, BEANS_KEY);
IndexCacheKey diagnosticsCacheKey = getCacheKey(project, DIAGNOSTICS_KEY);
this.cache.update(symbolsCacheKey, javaFiles, lastModified, generatedSymbols, dependencies, CachedSymbol.class);
this.cache.update(beansCacheKey, javaFiles, lastModified, generatedBeans, dependencies, CachedBean.class);
this.cache.update(diagnosticsCacheKey, javaFiles, lastModified, generatedDiagnostics, dependencies, CachedDiagnostics.class);
this.cache.update(symbolsCacheKey, javaFiles, lastModified, result.getGeneratedSymbols(), dependencies, CachedSymbol.class);
this.cache.update(beansCacheKey, javaFiles, lastModified, result.getGeneratedBeans(), dependencies, CachedBean.class);
this.cache.update(diagnosticsCacheKey, javaFiles, lastModified, result.getGeneratedDiagnostics(), dependencies, CachedDiagnostics.class);
result.publishResults(symbolHandler);
reconcileWithCompleteIndex(project, result.getMarkedForReconcilingWithCompleteIndex());
return scannedTypes;
}
@@ -519,6 +535,10 @@ public class SpringIndexerJava implements SpringIndexer {
}
private void scanFiles(IJavaProject project, String[] javaFiles, boolean clean) throws Exception {
SpringIndexerJavaScanResult result = null;
// check cached elements first
IndexCacheKey symbolsCacheKey = getCacheKey(project, SYMBOL_KEY);
IndexCacheKey beansCacheKey = getCacheKey(project, BEANS_KEY);
IndexCacheKey diagnosticsCacheKey = getCacheKey(project, DIAGNOSTICS_KEY);
@@ -527,21 +547,26 @@ public class SpringIndexerJava implements SpringIndexer {
Pair<CachedBean[], Multimap<String, String>> cachedBeans = this.cache.retrieve(beansCacheKey, javaFiles, CachedBean.class);
Pair<CachedDiagnostics[], Multimap<String, String>> cachedDiagnostics = this.cache.retrieve(diagnosticsCacheKey, javaFiles, CachedDiagnostics.class);
CachedSymbol[] symbols;
CachedBean[] beans;
CachedDiagnostics[] diagnostics;
if (!clean && cachedSymbols != null && cachedBeans != null && cachedDiagnostics != null) {
// use cached data
if (clean || cachedSymbols == null || cachedBeans == null || cachedDiagnostics == null) {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
List<CachedBean> generatedBeans = new ArrayList<CachedBean>();
List<CachedDiagnostics> generatedDiagnostics = new ArrayList<CachedDiagnostics>();
result = new SpringIndexerJavaScanResult(project, javaFiles, symbolHandler, cachedSymbols.getLeft(), cachedBeans.getLeft(), cachedDiagnostics.getLeft());
this.dependencyTracker.restore(cachedSymbols.getRight());
log.info("scan java files, AST parse, pass 1 for files: {}", javaFiles.length);
log.info("scan java files used cached data: {} - no. of cached symbols retrieved: {}", project.getElementName(), result.getGeneratedSymbols().size());
log.info("scan java files restored cached dependency data: {} - no. of cached dependencies: {}", cachedSymbols.getRight().size());
}
else {
// continue scanning everything
result = new SpringIndexerJavaScanResult(project, javaFiles);
final SpringIndexerJavaScanResult finalResult = result;
BiConsumer<String, Diagnostic> diagnosticsAggregator = new BiConsumer<>() {
@Override
public void accept(String docURI, Diagnostic diagnostic) {
generatedDiagnostics.add(new CachedDiagnostics(docURI, diagnostic));
finalResult.getGeneratedDiagnostics().add(new CachedDiagnostics(docURI, diagnostic));
}
};
@@ -550,49 +575,30 @@ public class SpringIndexerJava implements SpringIndexer {
for (int i = 0; i < chunks.size(); i++) {
log.info("scan java files, AST parse, chunk {} for files: {}", i, javaFiles.length);
String[] pass2Files = scanFiles(project, annotations, chunks.get(i), generatedSymbols, generatedBeans, diagnosticsAggregator, true);
String[] pass2Files = scanFiles(project, annotations, chunks.get(i), diagnosticsAggregator, true, result);
if (pass2Files.length > 0) {
log.info("scan java files, AST parse, pass 2, chunk {} for files: {}", i, javaFiles.length);
scanFiles(project, annotations, pass2Files, generatedSymbols, generatedBeans, diagnosticsAggregator, false);
scanFiles(project, annotations, pass2Files, diagnosticsAggregator, false, result);
}
}
log.info("scan java files done, number of symbols created: " + generatedSymbols.size());
log.info("scan java files done, number of symbols created: " + result.getGeneratedSymbols().size());
this.cache.store(symbolsCacheKey, javaFiles, generatedSymbols, dependencyTracker.getAllDependencies(), CachedSymbol.class);
this.cache.store(beansCacheKey, javaFiles, generatedBeans, dependencyTracker.getAllDependencies(), CachedBean.class);
this.cache.store(diagnosticsCacheKey, javaFiles, generatedDiagnostics, dependencyTracker.getAllDependencies(), CachedDiagnostics.class);
// dependencyTracker.dump();
symbols = (CachedSymbol[]) generatedSymbols.toArray(new CachedSymbol[generatedSymbols.size()]);
beans = (CachedBean[]) generatedBeans.toArray(new CachedBean[generatedBeans.size()]);
diagnostics = (CachedDiagnostics[]) generatedDiagnostics.toArray(new CachedDiagnostics[generatedDiagnostics.size()]);
}
else {
symbols = cachedSymbols.getLeft();
beans = cachedBeans.getLeft();
diagnostics = cachedDiagnostics.getLeft();
log.info("scan java files used cached data: {} - no. of cached symbols retrieved: {}", project.getElementName(), symbols.length);
this.dependencyTracker.restore(cachedSymbols.getRight());
log.info("scan java files restored cached dependency data: {} - no. of cached dependencies: {}", cachedSymbols.getRight().size());
}
if (symbols != null && beans != null) {
WorkspaceSymbol[] enhancedSymbols = Arrays.stream(symbols).map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(WorkspaceSymbol[]::new);
Map<String, List<SpringIndexElement>> allBeans = Arrays.stream(beans).filter(cachedBean -> cachedBean.getBean() != null).collect(Collectors.groupingBy(CachedBean::getDocURI, Collectors.mapping(CachedBean::getBean, Collectors.toList())));
Map<String, List<Diagnostic>> diagnosticsByDoc = Arrays.stream(diagnostics).filter(cachedDiagnostic -> cachedDiagnostic.getDiagnostic() != null).collect(Collectors.groupingBy(CachedDiagnostics::getDocURI, Collectors.mapping(CachedDiagnostics::getDiagnostic, Collectors.toList())));
addEmptyDiagnostics(diagnosticsByDoc, javaFiles);
symbolHandler.addSymbols(project, enhancedSymbols, allBeans, diagnosticsByDoc);
this.cache.store(symbolsCacheKey, javaFiles, result.getGeneratedSymbols(), dependencyTracker.getAllDependencies(), CachedSymbol.class);
this.cache.store(beansCacheKey, javaFiles, result.getGeneratedBeans(), dependencyTracker.getAllDependencies(), CachedBean.class);
this.cache.store(diagnosticsCacheKey, javaFiles, result.getGeneratedDiagnostics(), dependencyTracker.getAllDependencies(), CachedDiagnostics.class);
}
result.publishResults(symbolHandler);
reconcileWithCompleteIndex(project, result.getMarkedForReconcilingWithCompleteIndex());
log.info("reconciling stats - counter: " + reconciler.getStatsCounter());
log.info("reconciling stats - timer: " + reconciler.getStatsTimer());
}
private String[] scanFiles(IJavaProject project, AnnotationHierarchies annotations, String[] javaFiles, List<CachedSymbol> generatedSymbols, List<CachedBean> generatedBeans,
BiConsumer<String, Diagnostic> diagnosticsAggregator, boolean ignoreMethodBodies) throws Exception {
private String[] scanFiles(IJavaProject project, AnnotationHierarchies annotations, String[] javaFiles,
BiConsumer<String, Diagnostic> diagnosticsAggregator, boolean ignoreMethodBodies, SpringIndexerJavaScanResult result) throws Exception {
PercentageProgressTask progressTask = this.progressService.createPercentageProgressTask(INDEX_FILES_TASK_ID + project.getElementName(),
javaFiles.length, "Spring Tools: Indexing Java Sources for '" + project.getElementName() + "'");
@@ -611,7 +617,7 @@ public class SpringIndexerJava implements SpringIndexer {
IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath,
lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, nextPassFiles, !ignoreMethodBodies);
lastModified, docRef, null, problemCollector, nextPassFiles, !ignoreMethodBodies, false, result);
scanAST(context, true);
progressTask.increment();
@@ -632,15 +638,68 @@ public class SpringIndexerJava implements SpringIndexer {
}
}
private void addEmptyDiagnostics(Map<String, List<Diagnostic>> diagnosticsByDoc, String[] javaFiles) {
for (int i = 0; i < javaFiles.length; i++) {
File file = new File(javaFiles[i]);
String docURI = UriUtil.toUri(file).toASCIIString();
if (!diagnosticsByDoc.containsKey(docURI)) {
diagnosticsByDoc.put(docURI, Collections.emptyList());
}
private void reconcileWithCompleteIndex(IJavaProject project, Map<String, Long> markedForReconcilingWithCompleteIndex) throws Exception {
if (markedForReconcilingWithCompleteIndex.isEmpty()) {
return;
}
boolean ignoreMethodBodies = false;
String[] javaFiles = markedForReconcilingWithCompleteIndex.keySet().toArray(String[]::new);
long[] modificationTimestamps = new long[javaFiles.length];
for (int i = 0; i < javaFiles.length; i++) {
modificationTimestamps[i] = markedForReconcilingWithCompleteIndex.get(javaFiles[i]);
}
// reconcile
SpringIndexerJavaScanResult reconcilingResult = new SpringIndexerJavaScanResult(project, javaFiles);
BiConsumer<String, Diagnostic> diagnosticsAggregator = new BiConsumer<>() {
@Override
public void accept(String docURI, Diagnostic diagnostic) {
reconcilingResult.getGeneratedDiagnostics().add(new CachedDiagnostics(docURI, diagnostic));
}
};
FileASTRequestor requestor = new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
File file = new File(sourceFilePath);
String docURI = UriUtil.toUri(file).toASCIIString();
long lastModified = file.lastModified();
AtomicReference<TextDocument> docRef = new AtomicReference<>();
IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath,
lastModified, docRef, null, problemCollector, new ArrayList<>(), !ignoreMethodBodies, true, reconcilingResult);
try {
DocumentUtils.getTempTextDocument(docURI, docRef, null);
reconcile(context);
} catch (Exception e) {
log.error("problem creating temp document during re-reconciling for: " + docURI, e);
}
}
};
AnnotationHierarchies annotations = new AnnotationHierarchies();
ASTParserCleanupEnabled parser = createParser(project, annotations, ignoreMethodBodies);
try {
parser.createASTs(javaFiles, null, new String[0], requestor, null);
}
finally {
parser.cleanup();
}
// cache update
IndexCacheKey diagnosticsCacheKey = getCacheKey(project, DIAGNOSTICS_KEY);
this.cache.update(diagnosticsCacheKey, javaFiles, modificationTimestamps, reconcilingResult.getGeneratedDiagnostics(), dependencyTracker.getAllDependencies(), CachedDiagnostics.class);
// publish
reconcilingResult.publishResults(symbolHandler);
}
private void scanAST(final SpringIndexerJavaContext context, boolean includeReconcile) {
@@ -739,20 +798,13 @@ public class SpringIndexerJava implements SpringIndexer {
dependencyTracker.update(context.getFile(), context.getDependencies());;
}
private void reconcile(SpringIndexerJavaContext context) {
// reconciling
IProblemCollector problemCollector = new IProblemCollector() {
List<ReconcileProblem> problems = new ArrayList<>();
@Override
public void endCollecting() {
for (ReconcileProblem p : problems) {
context.getProblemCollector().accept(p);
}
}
@Override
public void beginCollecting() {
problems.clear();
@@ -762,12 +814,21 @@ public class SpringIndexerJava implements SpringIndexer {
public void accept(ReconcileProblem problem) {
problems.add(problem);
}
@Override
public void endCollecting() {
for (ReconcileProblem p : problems) {
context.getProblemCollector().accept(p);
}
}
};
try {
problemCollector.beginCollecting();
reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), problemCollector, context.isFullAst());
reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), problemCollector, context.isFullAst(), context.isIndexComplete());
problemCollector.endCollecting();
} catch (RequiredCompleteAstException e) {
if (!context.isFullAst()) {
// Let problems be found in the next pass, don't add the problems to the aggregate problems collector to not duplicate them with the next pass
@@ -777,6 +838,9 @@ public class SpringIndexerJava implements SpringIndexer {
problemCollector.endCollecting();
log.error("Complete AST required but it is complete already. Parsing ", context.getDocURI());
}
} catch (RequiredCompleteIndexException e) {
context.getResult().markForReconcilingWithCompleteIndex(context.getFile(), context.getLastModified());
log.error("Complete AST required but it is complete already. Parsing ", context.getDocURI());
}
}
@@ -812,9 +876,9 @@ public class SpringIndexerJava implements SpringIndexer {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(node);
for (Iterator<IAnnotationBinding> itr = annotationHierarchies.iterator(typeBinding); itr.hasNext();) {
IAnnotationBinding ab = itr.next();
/*
* If meta annotations of the current annotation is a "sub-type" of one of the annotations from symbol providers then add it to meta annotations
*/
//
// If meta annotations of the current annotation is a "sub-type" of one of the annotations from symbol providers then add it to meta annotations
//
if (annotationHierarchies.isAnnotatedWithAnnotationByBindingKey(ab, symbolProviders::containsKey)) {
metaAnnotations.add(ab.getAnnotationType());
}
@@ -934,7 +998,6 @@ public class SpringIndexerJava implements SpringIndexer {
.map(file -> file.getAbsolutePath() + "#" + file.lastModified())
.collect(Collectors.joining(","));
// return new IndexCacheKey(project.getElementName(), "java", elementType, DigestUtils.md5Hex(GENERATION + "-" + classpathIdentifier).toUpperCase());
return new IndexCacheKey(project.getElementName(), "java", elementType, DigestUtils.md5Hex(GENERATION + "-" + validationSeveritySettings.toString() + "-" + classpathIdentifier).toUpperCase());
}
@@ -1017,6 +1080,10 @@ public class SpringIndexerJava implements SpringIndexer {
}
}
public void setScanChunkSize(int chunkSize) {
this.scanChunkSize = chunkSize;
}
public void setValidationSeveritySettings(JsonObject javaValidationSettingsJson) {
this.validationSeveritySettings = javaValidationSettingsJson;
}
@@ -1025,10 +1092,6 @@ public class SpringIndexerJava implements SpringIndexer {
this.fileScanListener = fileScanListener;
}
public void setScanChunkSize(int chunkSize) {
this.scanChunkSize = chunkSize;
}
private void fileScannedEvent(String file) {
if (fileScanListener != null) {
fileScanListener.fileScanned(file);

View File

@@ -35,15 +35,15 @@ public class SpringIndexerJavaContext {
private final long lastModified;
private final AtomicReference<TextDocument> docRef;
private final String content;
private final List<CachedSymbol> generatedSymbols;
private final List<CachedBean> beans;
private final IProblemCollector getProblemCollector;
private final List<String> nextPassFiles;
private final boolean fullAst;
private final boolean isIndexComplete;
private final SpringIndexerJavaScanResult scanResult;
private final Set<String> dependencies = new HashSet<>();
private final Set<String> scannedTypes = new HashSet<>();
public SpringIndexerJavaContext(
IJavaProject project,
CompilationUnit cu,
@@ -52,11 +52,11 @@ public class SpringIndexerJavaContext {
long lastModified,
AtomicReference<TextDocument> docRef,
String content,
List<CachedSymbol> generatedSymbols,
List<CachedBean> beans,
IProblemCollector problemCollector,
List<String> nextPassFiles,
boolean fullAst
boolean fullAst,
boolean isIndexComplete,
SpringIndexerJavaScanResult scanResult
) {
super();
this.project = project;
@@ -66,11 +66,11 @@ public class SpringIndexerJavaContext {
this.lastModified = lastModified;
this.docRef = docRef;
this.content = content;
this.generatedSymbols = generatedSymbols;
this.getProblemCollector = problemCollector;
this.beans = beans;
this.nextPassFiles = nextPassFiles;
this.fullAst = fullAst;
this.isIndexComplete = isIndexComplete;
this.scanResult = scanResult;
}
public IJavaProject getProject() {
@@ -100,13 +100,17 @@ public class SpringIndexerJavaContext {
public String getContent() {
return content;
}
public SpringIndexerJavaScanResult getResult() {
return scanResult;
}
public List<CachedSymbol> getGeneratedSymbols() {
return generatedSymbols;
return getResult().getGeneratedSymbols();
}
public List<CachedBean> getBeans() {
return beans;
return getResult().getGeneratedBeans();
}
public List<String> getNextPassFiles() {
@@ -147,15 +151,19 @@ public class SpringIndexerJavaContext {
return fullAst;
}
public boolean isIndexComplete() {
return isIndexComplete;
}
public void resetDocumentRelatedElements(String docURI) {
Iterator<CachedBean> beansIterator = beans.iterator();
Iterator<CachedBean> beansIterator = getBeans().iterator();
while (beansIterator.hasNext()) {
if (beansIterator.next().getDocURI().equals(docURI)) {
beansIterator.remove();
}
}
Iterator<CachedSymbol> symbolsIterator = generatedSymbols.iterator();
Iterator<CachedSymbol> symbolsIterator = getGeneratedSymbols().iterator();
while (symbolsIterator.hasNext()) {
if (symbolsIterator.next().getDocURI().equals(docURI)) {
symbolsIterator.remove();

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019, 2020 Pivotal, Inc.
* Copyright (c) 2019, 2025 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,7 +13,6 @@ package org.springframework.ide.vscode.boot.java.utils;
import java.util.Collection;
import java.util.Set;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,10 +25,6 @@ public class SpringIndexerJavaDependencyTracker {
private Multimap<String, String> dependencies = MultimapBuilder.hashKeys().hashSetValues().build();
public void addDependency(String sourceFile, ITypeBinding dependsOn) {
dependencies.put(sourceFile, dependsOn.getKey());
}
public void dump() {
log.info("=== Dependencies ===");
for (String sourceFile : dependencies.keySet()) {

View File

@@ -0,0 +1,112 @@
/*******************************************************************************
* Copyright (c) 2025 Broadcom
* 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:
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.WorkspaceSymbol;
import org.springframework.ide.vscode.boot.java.beans.CachedBean;
import org.springframework.ide.vscode.boot.java.reconcilers.CachedDiagnostics;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.commons.util.UriUtil;
/**
* Class to capture the major results of scanning one or more files
* for Spring symbol creation, indexing, and reconciling
*
* @author Martin Lippert
*/
public class SpringIndexerJavaScanResult {
private final Map<String, Long> markedForReconciling;
private final List<CachedSymbol> generatedSymbols;
private final List<CachedBean> generatedBeans;
private final List<CachedDiagnostics> generatedDiagnostics;
private final IJavaProject project;
private final String[] javaFiles;
public SpringIndexerJavaScanResult(IJavaProject project, String[] javaFiles) {
this.project = project;
this.javaFiles = javaFiles;
this.markedForReconciling = new HashMap<>();
this.generatedSymbols = new ArrayList<CachedSymbol>();
this.generatedBeans = new ArrayList<CachedBean>();
this.generatedDiagnostics = new ArrayList<CachedDiagnostics>();
}
public SpringIndexerJavaScanResult(IJavaProject project, String[] javaFiles, SymbolHandler symbolHandler,
CachedSymbol[] symbols, CachedBean[] beans, CachedDiagnostics[] diagnostics) {
this.project = project;
this.javaFiles = javaFiles;
this.markedForReconciling = new HashMap<>();
this.generatedSymbols = Arrays.asList(symbols);
this.generatedBeans = Arrays.asList(beans);
this.generatedDiagnostics = Arrays.asList(diagnostics);
}
public Map<String, Long> getMarkedForReconcilingWithCompleteIndex() {
return markedForReconciling;
}
public void markForReconcilingWithCompleteIndex(String file, long lastModified) {
this.markedForReconciling.put(file, lastModified);
}
public List<CachedBean> getGeneratedBeans() {
return generatedBeans;
}
public List<CachedSymbol> getGeneratedSymbols() {
return generatedSymbols;
}
public List<CachedDiagnostics> getGeneratedDiagnostics() {
return generatedDiagnostics;
}
public void publishResults(SymbolHandler symbolHandler) {
WorkspaceSymbol[] enhancedSymbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(WorkspaceSymbol[]::new);
Map<String, List<SpringIndexElement>> allBeans = generatedBeans.stream().filter(cachedBean -> cachedBean.getBean() != null).collect(Collectors.groupingBy(CachedBean::getDocURI, Collectors.mapping(CachedBean::getBean, Collectors.toList())));
Map<String, List<Diagnostic>> diagnosticsByDoc = generatedDiagnostics.stream().filter(cachedDiagnostic -> cachedDiagnostic.getDiagnostic() != null).collect(Collectors.groupingBy(CachedDiagnostics::getDocURI, Collectors.mapping(CachedDiagnostics::getDiagnostic, Collectors.toList())));
addEmptyDiagnostics(diagnosticsByDoc, javaFiles); // to make sure that files without diagnostics publish an empty array of diagnostics
symbolHandler.addSymbols(this.project, enhancedSymbols, allBeans, diagnosticsByDoc);
}
private void addEmptyDiagnostics(Map<String, List<Diagnostic>> diagnosticsByDoc, String[] javaFiles) {
for (int i = 0; i < javaFiles.length; i++) {
File file = new File(javaFiles[i]);
String docURI = UriUtil.toUri(file).toASCIIString();
if (!diagnosticsByDoc.containsKey(docURI)) {
diagnosticsByDoc.put(docURI, Collections.emptyList());
}
}
}
}