From 6f7f9fdaf3fcdc49a4be22c9215dfa2b5466610e Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 13 Oct 2023 18:22:14 +0200 Subject: [PATCH] GH-1118: update cache once for multiple files being deleted --- .../vscode/boot/index/cache/IndexCache.java | 3 +- .../boot/index/cache/IndexCacheOnDisc.java | 27 +++++++ .../boot/index/cache/IndexCacheVoid.java | 4 + .../java/utils/SpringFactoriesIndexer.java | 14 +++- .../boot/java/utils/SpringIndexerJava.java | 19 +++-- .../boot/java/utils/SpringIndexerXML.java | 17 ++-- .../cache/test/IndexCacheOnDiscTest.java | 78 +++++++++++++++++++ .../cache/test/IndexCacheTimestampsOnly.java | 4 + 8 files changed, 148 insertions(+), 18 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCache.java index 138066e012..2eb9199158 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCache.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Pivotal, Inc. + * Copyright (c) 2019, 2023 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 @@ -30,6 +30,7 @@ public interface IndexCache { void remove(IndexCacheKey cacheKey); void removeFile(IndexCacheKey symbolCacheKey, String file, Class type); + void removeFiles(IndexCacheKey symbolCacheKey, String[] files, Class type); default T[] retrieveSymbols(IndexCacheKey cacheKey, String[] files, Class type) { Pair> r = retrieve(cacheKey, files, type); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDisc.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDisc.java index 1483a11746..9f897b2e18 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDisc.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDisc.java @@ -168,6 +168,33 @@ public void removeFile(IndexCacheKey cacheKey, String } } + @Override + public void removeFiles(IndexCacheKey cacheKey, String[] files, Class type) { + @SuppressWarnings("unchecked") + IndexCacheStore cacheStore = (IndexCacheStore) this.stores.get(cacheKey); + + if (cacheStore != null) { + + SortedMap timestampedFiles = new TreeMap<>(cacheStore.getTimestampedFiles()); + Map> changedDeps = new HashMap<>(cacheStore.getDependencies()); + Set docURIs = new HashSet<>(); + + for (String file : files) { + String docURI = UriUtil.toUri(new File(file)).toASCIIString(); + docURIs.add(docURI); + + timestampedFiles.remove(file); + changedDeps.remove(file); + } + + List cachedSymbols = cacheStore.getSymbols().stream() + .filter(cachedSymbol -> !docURIs.contains(cachedSymbol.getDocURI())) + .collect(Collectors.toList()); + + save(cacheKey, cachedSymbols, timestampedFiles, changedDeps, type); + } + } + @Override public void remove(IndexCacheKey cacheKey) { File cacheStore = new File(cacheDirectory, cacheKey.toString() + ".json"); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheVoid.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheVoid.java index 373205ff6f..efe5602d4a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheVoid.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheVoid.java @@ -47,6 +47,10 @@ public void remove(IndexCacheKey cacheKey) { public void removeFile(IndexCacheKey symbolCacheKey, String file, Class type) { } + @Override + public void removeFiles(IndexCacheKey symbolCacheKey, String[] files, Class type) { + } + @Override public long getModificationTimestamp(IndexCacheKey cacheKey, String docURI) { return 0; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java index c022c106ad..544ba4d178 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -284,11 +285,16 @@ public void updateFiles(IJavaProject project, DocumentDescriptor[] updatedDocs) @Override public void removeFiles(IJavaProject project, String[] docURIs) throws Exception { + String[] files = Arrays.stream(docURIs).map(docURI -> { + try { + return new File(new URI(docURI)).getAbsolutePath(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + }).toArray(String[]::new); + IndexCacheKey key = getCacheKey(project); - for (String docUri : docURIs) { - String file = new File(new URI(docUri)).getAbsolutePath(); - cache.removeFile(key, file, CachedSymbol.class); - } + cache.removeFiles(key, files, CachedSymbol.class); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index f92bcab2ab..b5d3f38e29 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -199,17 +199,22 @@ public void updateFiles(IJavaProject project, DocumentDescriptor[] updatedDocs) @Override public void removeFiles(IJavaProject project, String[] docURIs) throws Exception { + + String[] files = Arrays.stream(docURIs).map(docURI -> { + try { + return new File(new URI(docURI)).getAbsolutePath(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + }).toArray(String[]::new); + IndexCacheKey symbolsCacheKey = getCacheKey(project, SYMBOL_KEY); IndexCacheKey beansCacheKey = getCacheKey(project, BEANS_KEY); IndexCacheKey diagnosticsCacheKey = getCacheKey(project, DIAGNOSTICS_KEY); - - for (String docURI : docURIs) { - String file = new File(new URI(docURI)).getAbsolutePath(); - this.cache.removeFile(symbolsCacheKey, file, CachedSymbol.class); - this.cache.removeFile(beansCacheKey, file, CachedBean.class); - this.cache.removeFile(diagnosticsCacheKey, file, CachedDiagnostics.class); - } + this.cache.removeFiles(symbolsCacheKey, files, CachedSymbol.class); + this.cache.removeFiles(beansCacheKey, files, CachedBean.class); + this.cache.removeFiles(diagnosticsCacheKey, files, CachedDiagnostics.class); } private DocumentDescriptor[] filterDocuments(IJavaProject project, DocumentDescriptor[] updatedDocs) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java index 932fc304c9..8f3c4d276a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -212,15 +213,19 @@ public void updateFiles(IJavaProject project, DocumentDescriptor[] updatedDocs) @Override public void removeFiles(IJavaProject project, String[] docURIs) throws Exception { + String[] files = Arrays.stream(docURIs).map(docURI -> { + try { + return new File(new URI(docURI)).getAbsolutePath(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + }).toArray(String[]::new); + IndexCacheKey symbolsCacheKey = getCacheKey(project, SYMBOL_KEY); IndexCacheKey beansCacheKey = getCacheKey(project, BEANS_KEY); - for (String docURI : docURIs) { - String file = new File(new URI(docURI)).getAbsolutePath(); - - this.cache.removeFile(symbolsCacheKey, file, CachedSymbol.class); - this.cache.removeFile(beansCacheKey, file, CachedBean.class); - } + this.cache.removeFiles(symbolsCacheKey, files, CachedSymbol.class); + this.cache.removeFiles(beansCacheKey, files, CachedBean.class); } private void scanFile(IJavaProject project, String fileName, List generatedSymbols, List generatedBeans) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java index 4a2c7d5a06..85818b23a0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java @@ -621,4 +621,82 @@ void testFileDeleted() throws Exception { assertEquals(ImmutableSet.of(), cachedDependencies.get(file1.toString())); assertEquals(ImmutableSet.of("dep2"), cachedDependencies.get(file2.toString())); } + + @Test + void testMultipleFilesDeleted() throws Exception { + Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1"); + Path file2 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile2"); + Path file3 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile3"); + Path file4 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile4"); + + Files.createFile(file1); + Files.createFile(file2); + Files.createFile(file3); + Files.createFile(file4); + + FileTime timeFile1 = Files.getLastModifiedTime(file1); + FileTime timeFile2 = Files.getLastModifiedTime(file2); + FileTime timeFile3 = Files.getLastModifiedTime(file3); + FileTime timeFile4 = Files.getLastModifiedTime(file4); + String[] files = { + file1.toAbsolutePath().toString(), + file2.toAbsolutePath().toString(), + file3.toAbsolutePath().toString(), + file4.toAbsolutePath().toString() + }; + + String doc1URI = UriUtil.toUri(file1.toFile()).toASCIIString(); + String doc2URI = UriUtil.toUri(file2.toFile()).toASCIIString(); + String doc3URI = UriUtil.toUri(file3.toFile()).toASCIIString(); + String doc4URI = UriUtil.toUri(file4.toFile()).toASCIIString(); + + List generatedSymbols = new ArrayList<>(); + + WorkspaceSymbol symbol1 = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20))))); + EnhancedSymbolInformation enhancedSymbol1 = new EnhancedSymbolInformation(symbol1, null); + + WorkspaceSymbol symbol2 = new WorkspaceSymbol("symbol2", SymbolKind.Field, Either.forLeft(new Location(doc2URI, new Range(new Position(5, 10), new Position(5, 20))))); + EnhancedSymbolInformation enhancedSymbol2 = new EnhancedSymbolInformation(symbol2, null); + + WorkspaceSymbol symbol3 = new WorkspaceSymbol("symbol3", SymbolKind.Field, Either.forLeft(new Location(doc3URI, new Range(new Position(20, 11), new Position(20, 30))))); + EnhancedSymbolInformation enhancedSymbol3 = new EnhancedSymbolInformation(symbol3, null); + + WorkspaceSymbol symbol4 = new WorkspaceSymbol("symbol4", SymbolKind.Field, Either.forLeft(new Location(doc4URI, new Range(new Position(4, 4), new Position(5, 5))))); + EnhancedSymbolInformation enhancedSymbol4 = new EnhancedSymbolInformation(symbol4, null); + + generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1)); + generatedSymbols.add(new CachedSymbol(doc2URI, timeFile2.toMillis(), enhancedSymbol2)); + generatedSymbols.add(new CachedSymbol(doc3URI, timeFile3.toMillis(), enhancedSymbol3)); + generatedSymbols.add(new CachedSymbol(doc4URI, timeFile4.toMillis(), enhancedSymbol4)); + + Multimap dependencies = ImmutableMultimap.of( + file1.toString(), "dep1", + file2.toString(), "dep2" + ); + cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, dependencies, CachedSymbol.class); +// cache.removeFile(CACHE_KEY_VERSION_1, file1.toAbsolutePath().toString(), CachedSymbol.class); +// cache.removeFile(CACHE_KEY_VERSION_1, file3.toAbsolutePath().toString(), CachedSymbol.class); + cache.removeFiles(CACHE_KEY_VERSION_1, new String[] {file1.toAbsolutePath().toString(), file3.toAbsolutePath().toString()}, CachedSymbol.class); + + files = new String[]{file2.toAbsolutePath().toString(), file4.toAbsolutePath().toString()}; + Pair> result = cache.retrieve(CACHE_KEY_VERSION_1, files, CachedSymbol.class); + CachedSymbol[] cachedSymbols = result.getLeft(); + assertNotNull(result); + assertEquals(2, cachedSymbols.length); + + assertEquals("symbol2", cachedSymbols[0].getEnhancedSymbol().getSymbol().getName()); + assertEquals(SymbolKind.Field, cachedSymbols[0].getEnhancedSymbol().getSymbol().getKind()); + assertEquals(new Location(doc2URI, new Range(new Position(5, 10), new Position(5, 20))), cachedSymbols[0].getEnhancedSymbol().getSymbol().getLocation().getLeft()); + assertNull(cachedSymbols[0].getEnhancedSymbol().getAdditionalInformation()); + + assertEquals("symbol4", cachedSymbols[1].getEnhancedSymbol().getSymbol().getName()); + assertEquals(SymbolKind.Field, cachedSymbols[1].getEnhancedSymbol().getSymbol().getKind()); + assertEquals(new Location(doc4URI, new Range(new Position(4, 4), new Position(5, 5))), cachedSymbols[1].getEnhancedSymbol().getSymbol().getLocation().getLeft()); + assertNull(cachedSymbols[1].getEnhancedSymbol().getAdditionalInformation()); + + Multimap cachedDependencies = result.getRight(); + assertEquals(ImmutableSet.of(), cachedDependencies.get(file1.toString())); + assertEquals(ImmutableSet.of("dep2"), cachedDependencies.get(file2.toString())); + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheTimestampsOnly.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheTimestampsOnly.java index 36cba40d0d..c2044047b9 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheTimestampsOnly.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheTimestampsOnly.java @@ -85,6 +85,10 @@ public void remove(IndexCacheKey cacheKey) { public void removeFile(IndexCacheKey symbolCacheKey, String file, Class type) { } + @Override + public void removeFiles(IndexCacheKey symbolCacheKey, String[] files, Class type) { + } + @Override public long getModificationTimestamp(IndexCacheKey cacheKey, String file) { Map timestampMap = timestampCache.get(cacheKey);