initial implementation of delta-based index cache

This commit is contained in:
Martin Lippert
2024-12-06 11:18:14 +01:00
parent 48dbf77fb7
commit 9f42dcc8de
2 changed files with 1293 additions and 0 deletions

View File

@@ -0,0 +1,589 @@
/*******************************************************************************
* Copyright (c) 2024 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.index.cache;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.UriUtil;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
/**
* @author Martin Lippert
*/
public class IndexCacheOnDiscDeltaBased implements IndexCache {
private final File cacheDirectory;
private static final Logger log = LoggerFactory.getLogger(IndexCacheOnDiscDeltaBased.class);
public IndexCacheOnDiscDeltaBased(File cacheDirectory) {
this.cacheDirectory = cacheDirectory;
if (!this.cacheDirectory.exists()) {
this.cacheDirectory.mkdirs();
}
if (!this.cacheDirectory.exists()) {
log.warn("symbol cache directory does not exist and cannot be created: " + this.cacheDirectory.toString());
}
}
@Override
public <T extends IndexCacheable> void store(IndexCacheKey cacheKey, String[] files, List<T> elements, Multimap<String, String> dependencies, Class<T> type) {
if (dependencies == null) {
dependencies = ImmutableMultimap.of();
}
SortedMap<String, Long> 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));
IndexCacheStore<T> store = new IndexCacheStore<T>(timestampedFiles, elements, dependencies.asMap(), type);
persist(cacheKey, new DeltaSnapshot<T>(store), false);
}
@SuppressWarnings("unchecked")
@Override
public <T extends IndexCacheable> Pair<T[], Multimap<String, String>> retrieve(IndexCacheKey cacheKey, String[] files, Class<T> type) {
File cacheStore = new File(cacheDirectory, cacheKey.toString() + ".json");
if (cacheStore.exists()) {
try (JsonReader reader = new JsonReader(new FileReader(cacheStore))) {
IndexCacheStore<T> store = retrieveStoreFromIncrementalStorage(cacheKey, type);
SortedMap<String, Long> 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));
if (isFileMatch(timestampedFiles, store.getTimestampedFiles())) {
List<T> symbols = store.getSymbols();
Map<String, Collection<String>> storedDependencies = store.getDependencies();
Multimap<String, String> dependencies = MultimapBuilder.hashKeys().hashSetValues().build();
if (storedDependencies!=null && !storedDependencies.isEmpty()) {
for (Entry<String, Collection<String>> entry : storedDependencies.entrySet()) {
dependencies.replaceValues(entry.getKey(), entry.getValue());
}
}
return Pair.of(
(T[]) symbols.toArray((T[]) Array.newInstance(type, symbols.size())),
MultimapBuilder.hashKeys().hashSetValues().build(dependencies)
);
}
}
catch (Exception e) {
log.error("error reading cached symbols", e);
}
}
return null;
}
@Override
public <T extends IndexCacheable> void removeFile(IndexCacheKey cacheKey, String file, Class<T> type) {
persist(cacheKey, new DeltaDelete<T>(new String[] {file}), true);
}
@Override
public <T extends IndexCacheable> void removeFiles(IndexCacheKey cacheKey, String[] files, Class<T> type) {
persist(cacheKey, new DeltaDelete<T>(files), true);
}
@Override
public void remove(IndexCacheKey cacheKey) {
File cacheStore = new File(cacheDirectory, cacheKey.toString() + ".json");
if (cacheStore.exists()) {
cacheStore.delete();
}
}
@Override
public <T extends IndexCacheable> void update(IndexCacheKey cacheKey, String file, long lastModified,
List<T> generatedSymbols, Set<String> dependencies, Class<T> type) {
if (dependencies == null) {
dependencies = ImmutableSet.of();
}
// creating and storing delta
SortedMap<String, Long> timestampsDelta = new TreeMap<>();
timestampsDelta.put(file, lastModified);
Map<String, Collection<String>> dependenciesDelta = new HashMap<>();
dependenciesDelta.put(file, ImmutableSet.copyOf(dependencies));
IndexCacheStore<T> deltaStore = new IndexCacheStore<T>(timestampsDelta, generatedSymbols, dependenciesDelta, type);
persist(cacheKey, new DeltaUpdate<T>(deltaStore), true);
}
@Override
public <T extends IndexCacheable> void update(IndexCacheKey cacheKey, String[] files, long[] lastModified,
List<T> generatedSymbols, Multimap<String, String> dependencies, Class<T> type) {
if (dependencies == null) {
dependencies = ImmutableMultimap.of();
}
// creating and storing delta
SortedMap<String, Long> timestampsDelta = new TreeMap<>();
Map<String, Collection<String>> dependenciesDelta = new HashMap<>();
for (int i = 0; i < files.length; i++) {
timestampsDelta.put(files[i], lastModified[i]);
dependenciesDelta.put(files[i], ImmutableSet.copyOf(dependencies.get(files[i])));
}
IndexCacheStore<T> deltaStore = new IndexCacheStore<T>(timestampsDelta, generatedSymbols, dependenciesDelta, type);
persist(cacheKey, new DeltaUpdate<T>(deltaStore), true);
}
@Override
public long getModificationTimestamp(IndexCacheKey cacheKey, String file) {
// IndexCacheStore<? extends IndexCacheable> cacheStore = this.stores.get(cacheKey);
//
// if (cacheStore != null) {
// Long result = cacheStore.getTimestampedFiles().get(file);
// if (result != null) {
// return result;
// }
// }
return 0;
}
private boolean isFileMatch(SortedMap<String, Long> files1, SortedMap<String, Long> files2) {
if (files1.size() != files2.size()) return false;
for (String file : files1.keySet()) {
if (!files2.containsKey(file)) return false;
if (!files1.get(file).equals(files2.get(file))) return false;
}
return true;
}
private void cleanupCache(IndexCacheKey cacheKey) {
File[] cacheFiles = this.cacheDirectory.listFiles();
for (int i = 0; i < cacheFiles.length; i++) {
String fileName = cacheFiles[i].getName();
IndexCacheKey key = IndexCacheKey.parse(fileName);
if (key != null && !key.equals(cacheKey)
&& key.getProject().equals(cacheKey.getProject())
&& key.getIndexer().equals(cacheKey.getIndexer())
&& key.getCategory().equals(cacheKey.getCategory())) {
cacheFiles[i].delete();
}
// cleanup old cache files without category information (pre 4.19.1 release)
else if (key != null && !key.equals(cacheKey)
&& key.getProject().equals(cacheKey.getProject())
&& key.getIndexer().equals(cacheKey.getIndexer())
&& key.getCategory().equals("")) {
cacheFiles[i].delete();
}
}
}
private <T extends IndexCacheable> void persist(IndexCacheKey cacheKey, DeltaElement<T> delta, boolean append) {
DeltaStorage<T> deltaStorage = new DeltaStorage<T>(delta);
try (FileWriter writer = new FileWriter(new File(cacheDirectory, cacheKey.toString() + ".json"), append))
{
Gson gson = createGson();
gson.toJson(deltaStorage, writer);
writer.write("\n");
cleanupCache(cacheKey);
}
catch (Exception e) {
log.error("cannot write symbol cache", e);
}
}
private <T extends IndexCacheable> IndexCacheStore<T> retrieveStoreFromIncrementalStorage(IndexCacheKey cacheKey, Class<T> type) {
IndexCacheStore<T> store = new IndexCacheStore<>(new TreeMap<>(), new ArrayList<T>(), new HashMap<>(), type);
File cacheStore = new File(cacheDirectory, cacheKey.toString() + ".json");
if (cacheStore.exists()) {
Gson gson = createGson();
try (JsonReader reader = new JsonReader(new FileReader(cacheStore))) {
DeltaStorage<T> readElement;
while ((readElement = gson.fromJson(reader, DeltaStorage.class)) != null) {
DeltaElement<T> delta = readElement.storedElement;
store = delta.apply(store);
}
}
catch (Exception e) {
log.error("error reading cached symbols", e);
}
}
return store;
}
public static Gson createGson() {
return new GsonBuilder()
.registerTypeAdapter(DeltaStorage.class, new DeltaStorageAdapter())
.registerTypeAdapter(SymbolAddOnInformation.class, new SymbolAddOnInformationAdapter())
.registerTypeAdapter(Bean.class, new BeanJsonAdapter())
.registerTypeAdapter(InjectionPoint.class, new InjectionPointJsonAdapter())
.registerTypeAdapter(IndexCacheStore.class, new IndexCacheStoreAdapter())
.create();
}
private static record DeltaStorage<T extends IndexCacheable> (DeltaElement<T> storedElement) {}
private static interface DeltaElement<T extends IndexCacheable> {
public IndexCacheStore<T> apply(IndexCacheStore<T> store);
}
private static class DeltaDelete<T extends IndexCacheable> implements DeltaElement<T> {
private final String[] files;
public DeltaDelete(String[] files) {
this.files = files;
}
@Override
public IndexCacheStore<T> apply(IndexCacheStore<T> store) {
SortedMap<String, Long> timestampedFiles = store.getTimestampedFiles();
Map<String, Collection<String>> changedDeps = store.getDependencies();
Set<String> 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<T> symbols = store.getSymbols();
for (Iterator<T> iterator = symbols.iterator(); iterator.hasNext();) {
T t = iterator.next();
if (docURIs.contains(t.getDocURI())) {
iterator.remove();
}
}
return store;
}
}
private static class DeltaUpdate<T extends IndexCacheable> implements DeltaElement<T> {
private IndexCacheStore<T> deltaStore;
public DeltaUpdate(IndexCacheStore<T> deltaStore) {
this.deltaStore = deltaStore;
}
@Override
public IndexCacheStore<T> apply(IndexCacheStore<T> store) {
SortedMap<String, Long> deltaTimestamps = deltaStore.getTimestampedFiles();
SortedMap<String, Long> storeTimestamps = store.getTimestampedFiles();
Map<String, Collection<String>> deltaDependencies = deltaStore.getDependencies();
Map<String, Collection<String>> storeDependencies = store.getDependencies();
List<T> deltaSymbols = deltaStore.getSymbols();
List<T> storeSymbols = store.getSymbols();
Set<String> allDocURIs = new HashSet<>();
for (Iterator<String> iterator = deltaTimestamps.keySet().iterator(); iterator.hasNext();) {
String file = iterator.next();
long timestamp = deltaTimestamps.get(file);
// update cache internal map of timestamps per file
String docURI = UriUtil.toUri(new File(file)).toASCIIString();
allDocURIs.add(docURI);
storeTimestamps.put(file, timestamp);
// update cache internal map of dependencies per file
Collection<String> updatedDependencies = deltaDependencies.get(file);
if (updatedDependencies == null || updatedDependencies.isEmpty()) {
storeDependencies.remove(file);
} else {
storeDependencies.put(file, ImmutableSet.copyOf(updatedDependencies));
}
// update cache internal list of cached symbols (by removing old ones and adding all new ones)
for (Iterator<T> symbols = storeSymbols.iterator(); symbols.hasNext();) {
if (allDocURIs.contains(symbols.next().getDocURI())) {
symbols.remove();
}
}
storeSymbols.addAll(deltaSymbols);
}
return store;
}
}
private static class DeltaSnapshot<T extends IndexCacheable> implements DeltaElement<T> {
private IndexCacheStore<T> store;
public DeltaSnapshot(IndexCacheStore<T> store) {
this.store = store;
}
@Override
public IndexCacheStore<T> apply(IndexCacheStore<T> store) {
return this.store;
}
}
/**
* internal storage structure
*/
private static class IndexCacheStore<T extends IndexCacheable> {
@SuppressWarnings("unused")
private final String elementType;
private final SortedMap<String, Long> timestampedFiles;
private final List<T> elements;
private final Map<String, Collection<String>> dependencies;
public IndexCacheStore(SortedMap<String, Long> timestampedFiles, List<T> elements, Map<String, Collection<String>> dependencies, Class<T> elementType) {
this.timestampedFiles = timestampedFiles;
this.elements = elements;
this.dependencies = dependencies;
this.elementType = elementType.getName();
}
public Map<String, Collection<String>> getDependencies() {
return dependencies;
}
public List<T> getSymbols() {
return elements;
}
public SortedMap<String, Long> getTimestampedFiles() {
return timestampedFiles;
}
}
private static class IndexCacheStoreAdapter implements JsonDeserializer<IndexCacheStore<?>> {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public IndexCacheStore<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String className = parsedObject.get("elementType").getAsString();
try {
Class<?> elementType = Class.forName(className);
JsonElement elementsObject = parsedObject.get("elements");
Type elementListType = TypeToken.getParameterized(List.class, elementType).getType();
List elements = context.deserialize(elementsObject, elementListType);
JsonElement timestampedFilesObject = parsedObject.get("timestampedFiles");
Type timestampsMapType = TypeToken.getParameterized(SortedMap.class, String.class, Long.class).getType();
SortedMap timestampedFiles = context.deserialize(timestampedFilesObject, timestampsMapType);
JsonElement dependenciesObject = parsedObject.get("dependencies");
Map dependencies = context.deserialize(dependenciesObject, HashMap.class);
return new IndexCacheStore(timestampedFiles, elements, dependencies, elementType);
} catch (ClassNotFoundException e) {
throw new JsonParseException("cannot parse data from index cache with element type: " + className, e);
}
}
}
/**
* gson adapter to store subtype information for symbol addon informations
*/
private static class DeltaStorageAdapter implements JsonSerializer<DeltaStorage>, JsonDeserializer<DeltaStorage> {
@Override
public JsonElement serialize(DeltaStorage deltaElement, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("type", new JsonPrimitive(deltaElement.storedElement.getClass().getName()));
result.add("data", context.serialize(deltaElement.storedElement));
return result;
}
@Override
public DeltaStorage deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String className = parsedObject.get("type").getAsString();
JsonElement element = parsedObject.get("data");
try {
return new DeltaStorage(context.deserialize(element, Class.forName(className)));
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException("cannot parse data from unknown SymbolAddOnInformation subtype: " + type, cnfe);
}
}
}
/**
* gson adapter to store subtype information for symbol addon informations
*/
private static class SymbolAddOnInformationAdapter implements JsonSerializer<SymbolAddOnInformation>, JsonDeserializer<SymbolAddOnInformation> {
@Override
public JsonElement serialize(SymbolAddOnInformation addonInfo, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("type", new JsonPrimitive(addonInfo.getClass().getName()));
result.add("data", context.serialize(addonInfo));
return result;
}
@Override
public SymbolAddOnInformation deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String className = parsedObject.get("type").getAsString();
JsonElement element = parsedObject.get("data");
try {
return context.deserialize(element, Class.forName(className));
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException("cannot parse data from unknown SymbolAddOnInformation subtype: " + type, cnfe);
}
}
}
/**
* gson adapter to store subtype information for beans
*/
private static class BeanJsonAdapter implements JsonDeserializer<Bean> {
@Override
public Bean deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String beanName = parsedObject.get("name").getAsString();
String beanType = parsedObject.get("type").getAsString();
JsonElement locationObject = parsedObject.get("location");
Location location = context.deserialize(locationObject, Location.class);
JsonElement injectionPointObject = parsedObject.get("injectionPoints");
InjectionPoint[] injectionPoints = context.deserialize(injectionPointObject, InjectionPoint[].class);
JsonElement supertypesObject = parsedObject.get("supertypes");
Set<String> supertypes = context.deserialize(supertypesObject, Set.class);
JsonElement annotationsObject = parsedObject.get("annotations");
AnnotationMetadata[] annotations = annotationsObject == null ? DefaultValues.EMPTY_ANNOTATIONS : context.deserialize(annotationsObject, AnnotationMetadata[].class);
return new Bean(beanName, beanType, location, injectionPoints, supertypes, annotations);
}
}
private static class InjectionPointJsonAdapter implements JsonDeserializer<InjectionPoint> {
@Override
public InjectionPoint deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String injectionPointName = parsedObject.get("name").getAsString();
String injectionPointType = parsedObject.get("type").getAsString();
JsonElement locationObject = parsedObject.get("location");
Location location = context.deserialize(locationObject, Location.class);
JsonElement annotationsObject = parsedObject.get("annotations");
AnnotationMetadata[] annotations = annotationsObject == null ? DefaultValues.EMPTY_ANNOTATIONS : context.deserialize(annotationsObject, AnnotationMetadata[].class);
return new InjectionPoint(injectionPointName, injectionPointType, location, annotations);
}
}
}

View File

@@ -0,0 +1,704 @@
/*******************************************************************************
* Copyright (c) 2024 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.index.cache.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.WorkspaceSymbol;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.index.cache.AbstractIndexCacheable;
import org.springframework.ide.vscode.boot.index.cache.IndexCacheKey;
import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDiscDeltaBased;
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxElementsInformation;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.commons.util.UriUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
public class IndexCacheOnDiscDeltaBasedTest {
private static final String STORAGE_FILE_EXTENSION = ".json";
private static final IndexCacheKey CACHE_KEY_VERSION_1 = new IndexCacheKey("someProject", "someIndexer", "someCategory", "1");
private Path tempDir;
private IndexCacheOnDiscDeltaBased cache;
@BeforeEach
public void setup() throws Exception {
tempDir = Files.createTempDirectory("cachetest");
cache = new IndexCacheOnDiscDeltaBased(tempDir.toFile());
}
@AfterEach
public void deleteTempDir() throws Exception {
FileUtils.deleteDirectory(tempDir.toFile());
}
@Test
void testEmptyCache() throws Exception {
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new IndexCacheKey("something", "someIndexer", "someCategory", "0"), new String[0], CachedSymbol.class);
assertNull(result);
}
@Test
void testSimpleValidCache() 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");
Files.createFile(file1);
Files.createFile(file2);
Files.createFile(file3);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toString(), file2.toString(), file3.toString()};
List<CachedSymbol> generatedSymbols = new ArrayList<>();
WorkspaceSymbol symbol = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location("docURI", new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null);
generatedSymbols.add(new CachedSymbol("", timeFile1.toMillis(), enhancedSymbol));
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, ImmutableMultimap.of(
file1.toString(), "file1dep1",
file2.toString(), "file2dep1",
file2.toString(), "file2dep2"
), CachedSymbol.class);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
CachedSymbol[] cachedSymbols = result.getLeft();
assertNotNull(cachedSymbols);
assertEquals(1, cachedSymbols.length);
assertEquals("symbol1", cachedSymbols[0].getEnhancedSymbol().getSymbol().getName());
assertEquals(SymbolKind.Field, cachedSymbols[0].getEnhancedSymbol().getSymbol().getKind());
assertEquals(new Location("docURI", new Range(new Position(3, 10), new Position(3, 20))), cachedSymbols[0].getEnhancedSymbol().getSymbol().getLocation().getLeft());
assertNull(cachedSymbols[0].getEnhancedSymbol().getAdditionalInformation());
Multimap<String, String> dependencies = result.getRight();
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(CACHE_KEY_VERSION_1, file1.toString()));
assertEquals(0, cache.getModificationTimestamp(CACHE_KEY_VERSION_1, "random-non-existing-file"));
}
@Test
void testDifferentCacheKey() 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");
Files.createFile(file1);
Files.createFile(file2);
Files.createFile(file3);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toString(), file2.toString(), file3.toString()};
List<CachedSymbol> generatedSymbols = new ArrayList<>();
WorkspaceSymbol symbol = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location("docURI", new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null);
generatedSymbols.add(new CachedSymbol("", timeFile1.toMillis(), enhancedSymbol));
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, null, CachedSymbol.class);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new IndexCacheKey("someOtherProject", "someOtherIndexer", "someOtherCategory", "1"), files, CachedSymbol.class);
assertNull(result);
}
@Test
void testFileTouched() 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");
Files.createFile(file1);
Files.createFile(file2);
Files.createFile(file3);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toString(), file2.toString(), file3.toString()};
List<CachedSymbol> generatedSymbols = new ArrayList<>();
WorkspaceSymbol symbol = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location("docURI", new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null);
generatedSymbols.add(new CachedSymbol("", timeFile1.toMillis(), enhancedSymbol));
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, ImmutableMultimap.of(
file1.toString(), "file1dep",
file2.toString(), "file2dep"
), CachedSymbol.class);
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 1000));
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
assertNull(result);
}
@Test
void testMoreFiles() 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");
Files.createFile(file1);
Files.createFile(file2);
Files.createFile(file3);
String[] files = {file1.toString(), file2.toString()};
cache.store(CACHE_KEY_VERSION_1, files, new ArrayList<>(), null, CachedSymbol.class);
String[] moreFiles = {file1.toString(), file2.toString(), file3.toString()};
assertNull(cache.retrieve(CACHE_KEY_VERSION_1, moreFiles, CachedSymbol.class));
}
@Test
void testFewerFiles() 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");
Files.createFile(file1);
Files.createFile(file2);
Files.createFile(file3);
String[] files = {file1.toString(), file2.toString(), file3.toString()};
cache.store(CACHE_KEY_VERSION_1, files, new ArrayList<>(), null, CachedSymbol.class);
String[] fewerFiles = {file1.toString(), file2.toString()};
assertNull(cache.retrieve(CACHE_KEY_VERSION_1, fewerFiles, CachedSymbol.class));
}
@Test
void testDeleteOldCacheFileIfNewOneIsStored() throws Exception {
IndexCacheKey key1 = CACHE_KEY_VERSION_1;
cache.store(key1, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
IndexCacheKey key2 = new IndexCacheKey("someProject", "someIndexer", "someCategory", "2");
cache.store(key2, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key2.toString() + STORAGE_FILE_EXTENSION))));
assertFalse(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
}
@Test
void testDoNotRetrieveOldCacheDataIfNewerVersionIsStored() throws Exception {
IndexCacheKey key1 = CACHE_KEY_VERSION_1;
IndexCacheKey key2 = new IndexCacheKey("someProject", "someIndexer", "someCategory", "2");
cache.store(key1, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertNotNull(cache.retrieve(key1, new String[0], CachedSymbol.class));
assertNull(cache.retrieve(key2, new String[0], CachedSymbol.class));
cache.store(key2, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertNull(cache.retrieve(key1, new String[0], CachedSymbol.class));
assertNotNull(cache.retrieve(key2, new String[0], CachedSymbol.class));
}
@Test
void testDeleteOldCacheFileFromPreviousReleasesIfNewOneIsStored() throws Exception {
IndexCacheKey key1 = new IndexCacheKey("someProject", "someIndexer", "", "2");
cache.store(key1, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
IndexCacheKey key2 = new IndexCacheKey("someProject", "someIndexer", "someCategory", "2");
cache.store(key2, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key2.toString() + STORAGE_FILE_EXTENSION))));
assertFalse(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
}
@Test
void testDoNotDeleteCacheFileFromOtherCategory() throws Exception {
IndexCacheKey key1 = new IndexCacheKey("someProject", "someIndexer", "someCategory", "2");
cache.store(key1, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
IndexCacheKey key2 = new IndexCacheKey("someProject", "someIndexer", "otherCategory", "2");
cache.store(key2, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key2.toString() + STORAGE_FILE_EXTENSION))));
assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
}
@Test
void testEnhancedInformationSubclasses() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Files.createFile(file1);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toString()};
String doc1URI = UriUtil.toUri(file1.toFile()).toString();
List<CachedSymbol> generatedSymbols = new ArrayList<>();
WorkspaceSymbol symbol = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20)))));
WebfluxElementsInformation addon = new WebfluxElementsInformation(new Range(new Position(4, 4), new Position(5, 5)), new Range(new Position(6, 6), new Position(7, 7)));
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, new SymbolAddOnInformation[]{addon});
generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol));
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, null, CachedSymbol.class);
CachedSymbol[] cachedSymbols = cache.retrieveSymbols(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
assertNotNull(cachedSymbols);
assertEquals(1, cachedSymbols.length);
assertEquals("symbol1", cachedSymbols[0].getEnhancedSymbol().getSymbol().getName());
assertEquals(SymbolKind.Field, cachedSymbols[0].getEnhancedSymbol().getSymbol().getKind());
assertEquals(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20))), cachedSymbols[0].getEnhancedSymbol().getSymbol().getLocation().getLeft());
SymbolAddOnInformation[] retrievedAddOns = cachedSymbols[0].getEnhancedSymbol().getAdditionalInformation();
assertNotNull(retrievedAddOns);
assertEquals(1, retrievedAddOns.length);
assertTrue(retrievedAddOns[0] instanceof WebfluxElementsInformation);
Range[] ranges = ((WebfluxElementsInformation) retrievedAddOns[0]).getRanges();
assertEquals(2, ranges.length);
assertEquals(new Range(new Position(4, 4), new Position(5, 5)), ranges[0]);
assertEquals(new Range(new Position(6, 6), new Position(7, 7)), ranges[1]);
}
@Test
void testSymbolAddedToExistingFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Files.createFile(file1);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toAbsolutePath().toString()};
String doc1URI = UriUtil.toUri(file1.toFile()).toString();
List<CachedSymbol> generatedSymbols1 = new ArrayList<>();
WorkspaceSymbol symbol1 = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location("docURI", new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation enhancedSymbol1 = new EnhancedSymbolInformation(symbol1, null);
generatedSymbols1.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols1, null, CachedSymbol.class);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
symbol1 = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20)))));
enhancedSymbol1 = new EnhancedSymbolInformation(symbol1, null);
WorkspaceSymbol symbol2 = new WorkspaceSymbol("symbol2", SymbolKind.Interface, Either.forLeft(new Location(doc1URI, new Range(new Position(5, 5), new Position(5, 10)))));
EnhancedSymbolInformation enhancedSymbol2 = new EnhancedSymbolInformation(symbol2, null);
generatedSymbols2.add(new CachedSymbol(doc1URI, timeFile1.toMillis() + 2000, enhancedSymbol1));
generatedSymbols2.add(new CachedSymbol(doc1URI, timeFile1.toMillis() + 2000, enhancedSymbol2));
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
cache.update(CACHE_KEY_VERSION_1, file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2, null, CachedSymbol.class);
AbstractIndexCacheable[] cachedSymbols = cache.retrieveSymbols(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
assertNotNull(cachedSymbols);
assertEquals(2, cachedSymbols.length);
assertEquals(timeFile1.toMillis() + 2000, cache.getModificationTimestamp(CACHE_KEY_VERSION_1, file1.toString()));
}
@Test
void testSymbolsAddedToMultipleFiles() throws Exception {
// create 3 files with one symbol each
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Path file2 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile2");
Path file3 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile3");
Files.createFile(file1);
Files.createFile(file2);
Files.createFile(file3);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
FileTime timeFile2 = Files.getLastModifiedTime(file2);
FileTime timeFile3 = Files.getLastModifiedTime(file3);
String[] files = {file1.toAbsolutePath().toString(), file2.toAbsolutePath().toString(), file3.toAbsolutePath().toString()};
String doc1URI = UriUtil.toUri(file1.toFile()).toString();
String doc2URI = UriUtil.toUri(file2.toFile()).toString();
String doc3URI = UriUtil.toUri(file3.toFile()).toString();
List<CachedSymbol> 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);
generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
WorkspaceSymbol symbol2 = new WorkspaceSymbol("symbol2", SymbolKind.Field, Either.forLeft(new Location(doc2URI, new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation enhancedSymbol2 = new EnhancedSymbolInformation(symbol2, null);
generatedSymbols.add(new CachedSymbol(doc2URI, timeFile2.toMillis(), enhancedSymbol2));
WorkspaceSymbol symbol3 = new WorkspaceSymbol("symbol3", SymbolKind.Field, Either.forLeft(new Location(doc3URI, new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation enhancedSymbol3 = new EnhancedSymbolInformation(symbol3, null);
generatedSymbols.add(new CachedSymbol(doc3URI, timeFile3.toMillis(), enhancedSymbol3));
// store original version of the symbols to the cache
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, null, CachedSymbol.class);
// create updated and new symbols
List<CachedSymbol> updatedSymbols = new ArrayList<>();
WorkspaceSymbol updatedSymbol1 = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation updatedEnhancedSymbol1 = new EnhancedSymbolInformation(updatedSymbol1, null);
WorkspaceSymbol newSymbol1 = new WorkspaceSymbol("symbol1-new", SymbolKind.Interface, Either.forLeft(new Location(doc1URI, new Range(new Position(5, 5), new Position(5, 10)))));
EnhancedSymbolInformation newEnhancedSymbol1 = new EnhancedSymbolInformation(newSymbol1, null);
updatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis() + 2000, updatedEnhancedSymbol1));
updatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis() + 2000, newEnhancedSymbol1));
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
WorkspaceSymbol updatedSymbol2 = new WorkspaceSymbol("symbol2-updated", SymbolKind.Field, Either.forLeft(new Location(doc2URI, new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation updatedEnhancedSymbol2 = new EnhancedSymbolInformation(updatedSymbol2, null);
updatedSymbols.add(new CachedSymbol(doc2URI, timeFile2.toMillis() + 3000, updatedEnhancedSymbol2));
assertTrue(file2.toFile().setLastModified(timeFile2.toMillis() + 3000));
String[] updatedFiles = new String[]{file1.toAbsolutePath().toString(), file2.toAbsolutePath().toString()};
long[] updatedModificationTimestamps = new long[]{timeFile1.toMillis() + 2000, timeFile2.toMillis() + 3000};
// update multiple files in the cache
cache.update(CACHE_KEY_VERSION_1, updatedFiles, updatedModificationTimestamps, updatedSymbols, null, CachedSymbol.class);
// double check whether all changes got stored and retrieved correctly
CachedSymbol[] cachedSymbols = cache.retrieveSymbols(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
assertNotNull(cachedSymbols);
assertEquals(4, cachedSymbols.length);
assertSymbol(updatedEnhancedSymbol1, cachedSymbols);
assertSymbol(newEnhancedSymbol1, cachedSymbols);
assertSymbol(updatedEnhancedSymbol2, cachedSymbols);
assertSymbol(enhancedSymbol3, cachedSymbols);
assertEquals(timeFile1.toMillis() + 2000, cache.getModificationTimestamp(CACHE_KEY_VERSION_1, file1.toString()));
assertEquals(timeFile2.toMillis() + 3000, cache.getModificationTimestamp(CACHE_KEY_VERSION_1, file2.toString()));
assertEquals(timeFile3.toMillis(), cache.getModificationTimestamp(CACHE_KEY_VERSION_1, file3.toString()));
}
private void assertSymbol(EnhancedSymbolInformation enhancedSymbol, CachedSymbol[] cachedSymbols) {
for (CachedSymbol cachedSymbol : cachedSymbols) {
WorkspaceSymbol symbol = cachedSymbol.getEnhancedSymbol().getSymbol();
if (symbol.toString().equals(enhancedSymbol.getSymbol().toString())) {
return;
}
}
fail("symbol not found: " + enhancedSymbol.getSymbol().toString());
}
@Test
void testDependencyAddedToExistingFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Files.createFile(file1);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toAbsolutePath().toString()};
List<CachedSymbol> generatedSymbols = ImmutableList.of();
Multimap<String, String> dependencies = ImmutableMultimap.of(file1.toString(), "dep1");
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, dependencies, CachedSymbol.class);
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
Set<String> dependencies2 = ImmutableSet.of("dep1", "dep2");
cache.update(CACHE_KEY_VERSION_1, file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols, dependencies2, CachedSymbol.class);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
assertNotNull(result);
assertEquals(ImmutableSet.of("dep1", "dep2"), result.getRight().get(file1.toString()));
}
@Test
void testSymbolRemovedFromExistingFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Files.createFile(file1);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toAbsolutePath().toString()};
String doc1URI = UriUtil.toUri(file1.toFile()).toString();
List<CachedSymbol> generatedSymbols1 = new ArrayList<>();
WorkspaceSymbol symbol1 = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location("docURI", new Range(new Position(3, 10), new Position(3, 20)))));
EnhancedSymbolInformation enhancedSymbol1 = new EnhancedSymbolInformation(symbol1, null);
generatedSymbols1.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols1, null, CachedSymbol.class);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
cache.update(CACHE_KEY_VERSION_1, file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2, null, CachedSymbol.class);
AbstractIndexCacheable[] cachedSymbols = cache.retrieveSymbols(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
assertNotNull(cachedSymbols);
assertEquals(0, cachedSymbols.length);
}
@Test
void testDependencyRemovedFromExistingFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Files.createFile(file1);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toAbsolutePath().toString()};
List<CachedSymbol> generatedSymbols1 = ImmutableList.of();
ImmutableMultimap<String, String> dependencies1 = ImmutableMultimap.of(
file1.toString(), "dep1",
file1.toString(), "dep2"
);
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols1, dependencies1, CachedSymbol.class);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
Set<String> dependencies2 = ImmutableSet.of("dep2");
cache.update(CACHE_KEY_VERSION_1, file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2, dependencies2, CachedSymbol.class);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
assertNotNull(result);
assertEquals(ImmutableSet.of("dep2"), result.getRight().get(file1.toString()));
}
@Test
void testSymbolAddedToNewFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Path file2 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile2");
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<CachedSymbol> generatedSymbols1 = 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);
generatedSymbols1.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols1, null, CachedSymbol.class);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
WorkspaceSymbol symbol2 = new WorkspaceSymbol("symbol2", SymbolKind.Interface, Either.forLeft(new Location(doc2URI, new Range(new Position(5, 5), new Position(5, 10)))));
EnhancedSymbolInformation enhancedSymbol2 = new EnhancedSymbolInformation(symbol2, null);
generatedSymbols2.add(new CachedSymbol(doc2URI, timeFile2.toMillis(), enhancedSymbol2));
cache.update(CACHE_KEY_VERSION_1, file2.toString(), timeFile2.toMillis(), generatedSymbols2, null, CachedSymbol.class);
AbstractIndexCacheable[] cachedSymbols = cache.retrieveSymbols(CACHE_KEY_VERSION_1, new String[]{file1.toString(), file2.toString()}, CachedSymbol.class);
assertNotNull(cachedSymbols);
assertEquals(2, cachedSymbols.length);
}
@Test
void testDependencyAddedToNewFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Path file2 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile2");
Files.createFile(file1);
Files.createFile(file2);
FileTime timeFile2 = Files.getLastModifiedTime(file2);
String[] files = {file1.toString()};
List<CachedSymbol> generatedSymbols1 = ImmutableList.of();
Multimap<String, String> dependencies1 = ImmutableMultimap.of(
file1.toString(), "dep1"
);
cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols1, dependencies1, CachedSymbol.class);
Set<String> dependencies2 = ImmutableSet.of("dep2");
cache.update(CACHE_KEY_VERSION_1, file2.toString(), timeFile2.toMillis(), generatedSymbols1, dependencies2, CachedSymbol.class);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(CACHE_KEY_VERSION_1, new String[]{file1.toString(), file2.toString()}, CachedSymbol.class);
assertNotNull(result);
assertEquals(ImmutableSet.of("dep2"), result.getRight().get(file2.toString()));
assertEquals(ImmutableSet.of("dep1"), result.getRight().get(file1.toString()));
}
@Test
void testProjectDeleted() throws Exception {
IndexCacheKey key1 = CACHE_KEY_VERSION_1;
cache.store(key1, new String[0], new ArrayList<>(), null, CachedSymbol.class);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
cache.remove(key1);
assertFalse(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION))));
}
@Test
void testFileDeleted() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Path file2 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile2");
Files.createFile(file1);
Files.createFile(file2);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
FileTime timeFile2 = Files.getLastModifiedTime(file2);
String[] files = {file1.toAbsolutePath().toString(), file2.toAbsolutePath().toString()};
String doc1URI = UriUtil.toUri(file1.toFile()).toASCIIString();
String doc2URI = UriUtil.toUri(file2.toFile()).toASCIIString();
List<CachedSymbol> 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);
generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
generatedSymbols.add(new CachedSymbol(doc2URI, timeFile2.toMillis(), enhancedSymbol2));
Multimap<String, String> 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);
files = new String[]{file2.toAbsolutePath().toString()};
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(CACHE_KEY_VERSION_1, files, CachedSymbol.class);
CachedSymbol[] cachedSymbols = result.getLeft();
assertNotNull(result);
assertEquals(1, 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());
Multimap<String, String> cachedDependencies = result.getRight();
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<CachedSymbol> 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<String, String> 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<CachedSymbol[], Multimap<String, String>> 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<String, String> cachedDependencies = result.getRight();
assertEquals(ImmutableSet.of(), cachedDependencies.get(file1.toString()));
assertEquals(ImmutableSet.of("dep2"), cachedDependencies.get(file2.toString()));
}
}