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 3ef1a32a8..f81ca0fac 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 @@ -142,7 +142,9 @@ public class SpringIndexerJava implements SpringIndexer { @Override public void updateFile(IJavaProject project, UpdatedDoc updatedDoc) throws Exception { - if (updatedDoc != null && shouldProcessDocument(project, updatedDoc.getDocURI())) { + SymbolCacheKey cacheKey = getCacheKey(project); + if (updatedDoc != null && shouldProcessDocument(project, updatedDoc.getDocURI()) + && hasNewerModificationTimestamp(cacheKey, updatedDoc.getDocURI(), updatedDoc.getLastModified())) { scanFile(project, updatedDoc); } } @@ -156,7 +158,9 @@ public class SpringIndexerJava implements SpringIndexer { } private UpdatedDoc[] filterDocuments(IJavaProject project, UpdatedDoc[] updatedDocs) { - return Arrays.stream(updatedDocs).filter(doc -> shouldProcessDocument(project, doc.getDocURI())).toArray(UpdatedDoc[]::new); + SymbolCacheKey cacheKey = getCacheKey(project); + return Arrays.stream(updatedDocs).filter(doc -> shouldProcessDocument(project, doc.getDocURI())) + .filter(doc -> hasNewerModificationTimestamp(cacheKey, doc.getDocURI(), doc.getLastModified())).toArray(UpdatedDoc[]::new); } @Override @@ -176,6 +180,11 @@ public class SpringIndexerJava implements SpringIndexer { .findFirst() .isPresent(); } + + private boolean hasNewerModificationTimestamp(SymbolCacheKey cacheKey, String docURI, long modifiedTimestamp) { + long cachedModificationTImestamp = this.cache.getModificationTimestamp(cacheKey, UriUtil.toFileString(docURI)); + return modifiedTimestamp > cachedModificationTImestamp; + } private void scanFiles(IJavaProject project, UpdatedDoc[] docs) throws Exception { ASTParser parser = createParser(project, false); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCache.java index 4fc3f5734..bad9fad2a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCache.java @@ -35,5 +35,7 @@ public interface SymbolCache { Pair> r = retrieve(cacheKey, files); return r!=null ? r.getLeft() : null; } + + long getModificationTimestamp(SymbolCacheKey cacheKey, String docURI); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheOnDisc.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheOnDisc.java index 734c17aaf..d8b71b7fb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheOnDisc.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheOnDisc.java @@ -241,6 +241,20 @@ public class SymbolCacheOnDisc implements SymbolCache { } } + @Override + public long getModificationTimestamp(SymbolCacheKey cacheKey, String file) { + CacheStore cacheStore = this.stores.get(cacheKey); + + if (cacheStore != null) { + Long result = cacheStore.getTimestampedFiles().get(file); + if (result != null) { + return result; + } + } + + return 0; + } + private void save(SymbolCacheKey cacheKey, List generatedSymbols, SortedMap timestampedFiles, Map> dependencies) { CacheStore store = new CacheStore(timestampedFiles, generatedSymbols, dependencies); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheVoid.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheVoid.java index af3db83cc..31319a2ff 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheVoid.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolCacheVoid.java @@ -47,5 +47,9 @@ public class SymbolCacheVoid implements SymbolCache { public void removeFile(SymbolCacheKey symbolCacheKey, String file) { } + @Override + public long getModificationTimestamp(SymbolCacheKey cacheKey, String docURI) { + return 0; + } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java index 39fe26e9a..4861b6572 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java @@ -33,6 +33,7 @@ import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.boot.java.utils.test.TestFileScanListener; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.util.UriUtil; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java index 17b2a3563..c429b4331 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2019 Pivotal, Inc. + * Copyright (c) 2017, 2020 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 @@ -33,6 +33,7 @@ import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaDependencyTracker; +import org.springframework.ide.vscode.boot.java.utils.test.TestFileScanListener; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; @@ -50,7 +51,7 @@ import com.google.common.collect.ImmutableSet; @BootLanguageServerTest @Import(SymbolProviderTestConf.class) public class RequestMappingSymbolProviderTest { - + @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; @Autowired private SpringSymbolIndex indexer; @@ -93,7 +94,10 @@ public class RequestMappingSymbolProviderTest { TestFileScanListener fileScanListener = new TestFileScanListener(); indexer.getJavaIndexer().setFileScanListener(fileScanListener); - indexer.updateDocument(constantsUri, FileUtils.readFileToString(UriUtil.toFile(constantsUri)), "test triggered").get(); + + CompletableFuture updateFuture = indexer.updateDocument(constantsUri, FileUtils.readFileToString(UriUtil.toFile(constantsUri)), "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + fileScanListener.assertScannedUris(constantsUri, docUri); fileScanListener.assertScannedUri(constantsUri, 1); fileScanListener.assertScannedUri(docUri, 1); @@ -113,12 +117,18 @@ public class RequestMappingSymbolProviderTest { TestFileScanListener fileScanListener = new TestFileScanListener(); indexer.getJavaIndexer().setFileScanListener(fileScanListener); - indexer.updateDocument(pingUri, null, "test triggered").get(); + + CompletableFuture updateFuture = indexer.updateDocument(pingUri, null, "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + fileScanListener.assertScannedUris(pingUri, pongUri); fileScanListener.reset(); fileScanListener.assertScannedUris(/*none*/); - indexer.updateDocument(pongUri, null, "test triggered").get(); + + CompletableFuture updateFuture2 = indexer.updateDocument(pongUri, null, "test triggered"); + updateFuture2.get(5, TimeUnit.SECONDS); + fileScanListener.assertScannedUris(pingUri, pongUri); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerMultipleFilesTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerMultipleFilesTest.java index 4860b1962..a1286a6f0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerMultipleFilesTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerMultipleFilesTest.java @@ -11,8 +11,6 @@ package org.springframework.ide.vscode.boot.java.utils.test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; @@ -29,14 +27,15 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.boot.java.utils.SymbolCache; import org.springframework.ide.vscode.boot.java.utils.SymbolIndexConfig; -import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; import org.springframework.test.context.junit4.SpringRunner; @@ -46,16 +45,25 @@ import org.springframework.test.context.junit4.SpringRunner; */ @RunWith(SpringRunner.class) @BootLanguageServerTest -@Import(SymbolProviderTestConf.class) +@Import(SpringIndexerMultipleFilesTest.TimestampingAwareCacheConfig.class) public class SpringIndexerMultipleFilesTest { + // usually, the test config ignores any caching by using the void impl, + // but here we need the one that implements at least the timestamp caching + // in order to check the java symbol indexer feature which avoid scanning the + // same file again even if the timestamp hasn't changed + public static class TimestampingAwareCacheConfig extends SymbolProviderTestConf { + @Bean public SymbolCache symbolCache() { + return new SymbolCacheTimestampsOnly(); + } + } + @Autowired private BootLanguageServerHarness harness; @Autowired private SpringSymbolIndex indexer; @Autowired private JavaProjectFinder projectFinder; private File directory; private String projectDir; - private IJavaProject project; @Before public void setup() throws Exception { @@ -66,7 +74,7 @@ public class SpringIndexerMultipleFilesTest { projectDir = directory.toURI().toString(); // trigger project creation - project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); CompletableFuture initProject = indexer.waitOperation(); initProject.get(5, TimeUnit.SECONDS); @@ -74,25 +82,31 @@ public class SpringIndexerMultipleFilesTest { @Test public void testUpdateChangedSingleDocumentOnDisc() throws Exception { - String changedDocURI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); String originalContent = FileUtils.readFileToString(new File(new URI(changedDocURI))); try { // update document and update index - assertTrue(containsSymbol(indexer.getSymbols(changedDocURI), "@/mapping1", changedDocURI)); + List symbols = indexer.getSymbols(changedDocURI); + assertTrue(containsSymbol(symbols, "@/mapping1", changedDocURI)); String newContent = originalContent.replace("mapping1", "mapping1-CHANGED"); FileUtils.writeStringToFile(new File(new URI(changedDocURI)), newContent); + TestFileScanListener fileScanListener = new TestFileScanListener(); + indexer.getJavaIndexer().setFileScanListener(fileScanListener); + CompletableFuture updateFuture = indexer.updateDocument(changedDocURI, null, "test triggered"); updateFuture.get(5, TimeUnit.SECONDS); // check for updated index per document - List symbols = indexer.getSymbols(changedDocURI); + symbols = indexer.getSymbols(changedDocURI); assertEquals(2, symbols.size()); assertTrue(containsSymbol(symbols, "@/mapping1-CHANGED", changedDocURI, 6, 1, 6, 36)); assertTrue(containsSymbol(symbols, "@/mapping2", changedDocURI, 11, 1, 11, 28)); + + fileScanListener.assertScannedUris(changedDocURI); + fileScanListener.assertScannedUri(changedDocURI, 1); } finally { FileUtils.writeStringToFile(new File(new URI(changedDocURI)), originalContent); @@ -144,6 +158,64 @@ public class SpringIndexerMultipleFilesTest { FileUtils.writeStringToFile(new File(new URI(doc3URI)), original3Content); } } + + @Test + public void testDontScanUnchangedDocument() throws Exception { + String unchangedDocURI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); + + TestFileScanListener fileScanListener = new TestFileScanListener(); + indexer.getJavaIndexer().setFileScanListener(fileScanListener); + + CompletableFuture updateFuture = indexer.updateDocuments(new String[] {unchangedDocURI}, "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + + fileScanListener.assertScannedUris(); + fileScanListener.assertScannedUri(unchangedDocURI, 0); + } + + @Test + public void testDontScanUnchangedDocumentAmongMultipleChangedFiles() throws Exception { + + String doc1URI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); + String original1Content = FileUtils.readFileToString(new File(new URI(doc1URI))); + + String doc2URI = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); + + String doc3URI = directory.toPath().resolve("src/main/java/org/test/sub/MappingClassSubpackage.java").toUri().toString(); + String original3Content = FileUtils.readFileToString(new File(new URI(doc3URI))); + + try { + String new1Content = original1Content.replace("mapping1", "mapping1-CHANGED"); + FileUtils.writeStringToFile(new File(new URI(doc1URI)), new1Content); + + String new3Content = original3Content.replace("classlevel", "classlevel-CHANGED"); + FileUtils.writeStringToFile(new File(new URI(doc3URI)), new3Content); + + TestFileScanListener fileScanListener = new TestFileScanListener(); + indexer.getJavaIndexer().setFileScanListener(fileScanListener); + + CompletableFuture updateFuture = indexer.updateDocuments(new String[] {doc1URI, doc2URI, doc3URI}, "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + + // check for updated index per document + List symbols1 = indexer.getSymbols(doc1URI); + assertEquals(2, symbols1.size()); + assertTrue(containsSymbol(symbols1, "@/mapping1-CHANGED", doc1URI, 6, 1, 6, 36)); + assertTrue(containsSymbol(symbols1, "@/mapping2", doc1URI, 11, 1, 11, 28)); + + List symbols3 = indexer.getSymbols(doc3URI); + assertTrue(containsSymbol(symbols3, "@/classlevel-CHANGED/mapping-subpackage", doc3URI, 7, 1, 7, 38)); + + fileScanListener.assertScannedUris(doc1URI, doc3URI); + fileScanListener.assertScannedUri(doc1URI, 1); + fileScanListener.assertScannedUri(doc2URI, 0); + fileScanListener.assertScannedUri(doc3URI, 1); + } + finally { + FileUtils.writeStringToFile(new File(new URI(doc1URI)), original1Content); + FileUtils.writeStringToFile(new File(new URI(doc3URI)), original3Content); + } + } private boolean containsSymbol(List symbols, String name, String uri) { for (Iterator iterator = symbols.iterator(); iterator.hasNext();) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java index 930fb18ed..e3be3862d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2020 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 @@ -29,7 +29,6 @@ import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -49,7 +48,6 @@ public class SpringIndexerNonBootProjectTest { private File directory; private String projectDir; - private IJavaProject project; @Before public void setup() throws Exception { @@ -59,7 +57,7 @@ public class SpringIndexerNonBootProjectTest { projectDir = directory.toURI().toString(); // trigger project creation - project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); CompletableFuture initProject = indexer.waitOperation(); initProject.get(5, TimeUnit.SECONDS); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SymbolCacheOnDiscTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SymbolCacheOnDiscTest.java index 7a7a49823..acf24dec3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SymbolCacheOnDiscTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SymbolCacheOnDiscTest.java @@ -109,6 +109,9 @@ public class SymbolCacheOnDiscTest { assertEquals(2, dependencies.keySet().size()); assertEquals(dependencies.get(file1.toString()), ImmutableSet.of("file1dep1")); assertEquals(dependencies.get(file2.toString()), ImmutableSet.of("file2dep1", "file2dep2")); + + assertEquals(timeFile1.toMillis(), cache.getModificationTimestamp(new SymbolCacheKey("somekey", "1"), file1.toString())); + assertEquals(0, cache.getModificationTimestamp(new SymbolCacheKey("somekey", "1"), "random-non-existing-file")); } @Test @@ -282,6 +285,8 @@ public class SymbolCacheOnDiscTest { CachedSymbol[] cachedSymbols = cache.retrieveSymbols(new SymbolCacheKey("somekey", "1"), files); assertNotNull(cachedSymbols); assertEquals(2, cachedSymbols.length); + + assertEquals(timeFile1.toMillis() + 2000, cache.getModificationTimestamp(new SymbolCacheKey("somekey", "1"), file1.toString())); } @Test @@ -356,6 +361,10 @@ public class SymbolCacheOnDiscTest { assertSymbol(newEnhancedSymbol1, cachedSymbols); assertSymbol(updatedEnhancedSymbol2, cachedSymbols); assertSymbol(enhancedSymbol3, cachedSymbols); + + assertEquals(timeFile1.toMillis() + 2000, cache.getModificationTimestamp(new SymbolCacheKey("somekey", "1"), file1.toString())); + assertEquals(timeFile2.toMillis() + 3000, cache.getModificationTimestamp(new SymbolCacheKey("somekey", "1"), file2.toString())); + assertEquals(timeFile3.toMillis(), cache.getModificationTimestamp(new SymbolCacheKey("somekey", "1"), file3.toString())); } private void assertSymbol(EnhancedSymbolInformation enhancedSymbol, CachedSymbol[] cachedSymbols) { @@ -494,13 +503,9 @@ public class SymbolCacheOnDiscTest { Files.createFile(file1); Files.createFile(file2); - FileTime timeFile1 = Files.getLastModifiedTime(file1); FileTime timeFile2 = Files.getLastModifiedTime(file2); String[] files = {file1.toString()}; - String doc1URI = UriUtil.toUri(file1.toFile()).toString(); - String doc2URI = UriUtil.toUri(file2.toFile()).toString(); - List generatedSymbols1 = ImmutableList.of(); Multimap dependencies1 = ImmutableMultimap.of( file1.toString(), "dep1" diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SymbolCacheTimestampsOnly.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SymbolCacheTimestampsOnly.java new file mode 100644 index 000000000..362839e1b --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SymbolCacheTimestampsOnly.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2020 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 java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; +import org.springframework.ide.vscode.boot.java.utils.SymbolCache; +import org.springframework.ide.vscode.boot.java.utils.SymbolCacheKey; + +import com.google.common.collect.Multimap; + +/** + * @author Martin Lippert + */ +public class SymbolCacheTimestampsOnly implements SymbolCache { + + private Map> timestampCache; + + public SymbolCacheTimestampsOnly() { + this.timestampCache = new HashMap<>(); + } + + @Override + public void store(SymbolCacheKey cacheKey, String[] files, List generatedSymbols, Multimap dependencies) { + SortedMap timestampedFiles = new TreeMap<>(); + + timestampedFiles = Arrays.stream(files) + .filter(file -> new File(file).exists()) + .collect(Collectors.toMap(file -> file, file -> { + try { + return Files.getLastModifiedTime(new File(file).toPath()).toMillis(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, (v1,v2) -> { throw new RuntimeException(String.format("Duplicate key for values %s and %s", v1, v2));}, TreeMap::new)); + + timestampCache.put(cacheKey, timestampedFiles); + } + + @Override + public Pair> retrieve(SymbolCacheKey cacheKey, String[] files) { + return null; + } + + @Override + public void update(SymbolCacheKey cacheKey, String file, long lastModified, List generatedSymbols, Set dependencies) { + Map timestampMap = timestampCache.get(cacheKey); + timestampMap.put(file, lastModified); + } + + @Override + public void update(SymbolCacheKey cacheKey, String[] files, long[] lastModified, List generatedSymbols, Multimap dependencies) { + Map timestampMap = timestampCache.get(cacheKey); + + for (int i = 0; i < files.length; i++) { + timestampMap.put(files[i], lastModified[i]); + } + } + + @Override + public void remove(SymbolCacheKey cacheKey) { + } + + @Override + public void removeFile(SymbolCacheKey symbolCacheKey, String file) { + } + + @Override + public long getModificationTimestamp(SymbolCacheKey cacheKey, String file) { + Map timestampMap = timestampCache.get(cacheKey); + if (timestampMap != null) { + Long timestamp = timestampMap.get(file); + if (timestamp != null) { + return timestamp; + } + } + + return 0; + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/TestFileScanListener.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/TestFileScanListener.java similarity index 95% rename from headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/TestFileScanListener.java rename to headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/TestFileScanListener.java index 99fe3aeba..d2ad4e475 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/TestFileScanListener.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/TestFileScanListener.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Pivotal, Inc. + * Copyright (c) 2019, 2020 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 @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.requestmapping.test; +package org.springframework.ide.vscode.boot.java.utils.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue;