From 8f6b5a619a8f307eb768d2b82945e1afa7a73cb9 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 7 Feb 2025 14:33:21 +0100 Subject: [PATCH] GH-1473: fixing deserialization of index cache issue with duplicated type name attribute Fixes GH-1473 --- .../vscode/commons/protocol/spring/Bean.java | 4 ++ .../boot/index/cache/IndexCacheOnDisc.java | 2 + .../cache/IndexCacheOnDiscDeltaBased.java | 52 ++++++++++++++----- .../java/utils/SpringFactoriesIndexer.java | 2 +- .../boot/java/utils/SpringIndexerJava.java | 2 +- .../boot/java/utils/SpringIndexerXML.java | 2 +- .../index/test/SpringMetamodelIndexTest.java | 29 +++++++++-- 7 files changed, 73 insertions(+), 20 deletions(-) diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java index 790c06ca3..d2db058cf 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java @@ -98,5 +98,9 @@ public class Bean extends AbstractSpringIndexElement { Gson gson = new Gson(); return gson.toJson(this); } + + public Set getSupertypes() { + return supertypes; + } } 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 48f4b69c3..28fd50602 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 @@ -56,6 +56,8 @@ import com.google.gson.stream.JsonReader; /** * @author Martin Lippert + * + * @deprecated Use IndexCacheOnDiscDeltaBased - this class is no longer maintained and up-to-date with the latest index changes */ public class IndexCacheOnDisc implements IndexCache { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java index 2fb47574d..ac80f15a3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java @@ -367,17 +367,6 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache { - public static Gson createGson() { - return new GsonBuilder() - .registerTypeAdapter(DeltaStorage.class, new DeltaStorageAdapter()) - .registerTypeAdapter(Bean.class, new BeanJsonAdapter()) - .registerTypeAdapter(InjectionPoint.class, new InjectionPointJsonAdapter()) - .registerTypeAdapter(IndexCacheStore.class, new IndexCacheStoreAdapter()) - .registerTypeAdapter(SpringIndexElement.class, new SpringIndexElementAdapter()) - .create(); - } - - /** * just keep a md5 hash internally for identifying files to save memory */ @@ -577,6 +566,17 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache { // + public static Gson createGson() { + return new GsonBuilder() + .registerTypeAdapter(DeltaStorage.class, new DeltaStorageAdapter()) + .registerTypeAdapter(Bean.class, new BeanJsonAdapter()) + .registerTypeAdapter(InjectionPoint.class, new InjectionPointJsonAdapter()) + .registerTypeAdapter(IndexCacheStore.class, new IndexCacheStoreAdapter()) + .registerTypeAdapter(SpringIndexElement.class, new SpringIndexElementAdapter()) + .create(); + } + + private static class IndexCacheStoreAdapter implements JsonDeserializer> { @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -635,7 +635,7 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache { } } - private static class BeanJsonAdapter implements JsonDeserializer { + private static class BeanJsonAdapter implements JsonSerializer, JsonDeserializer { @Override public Bean deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { @@ -670,6 +670,30 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache { return bean; } + + @Override + public JsonElement serialize(Bean src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject bean = new JsonObject(); + + bean.addProperty("name", src.getName()); + bean.addProperty("type", src.getType()); + + bean.add("location", context.serialize(src.getLocation())); + bean.add("injectionPoints", context.serialize(src.getInjectionPoints())); + + bean.add("supertypes", context.serialize(src.getSupertypes())); + bean.add("annotations", context.serialize(src.getAnnotations())); + + bean.addProperty("isConfiguration", src.isConfiguration()); + + Type childrenListType = TypeToken.getParameterized(List.class, SpringIndexElement.class).getType(); + bean.add("children", context.serialize(src.getChildren(), childrenListType)); + + bean.addProperty("_internal_node_type", src.getClass().getName()); + + return bean; + } + } private static class InjectionPointJsonAdapter implements JsonDeserializer { @@ -696,14 +720,14 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache { @Override public JsonElement serialize(SpringIndexElement element, Type typeOfSrc, JsonSerializationContext context) { JsonElement elem = context.serialize(element); - elem.getAsJsonObject().addProperty("type", element.getClass().getName()); + elem.getAsJsonObject().addProperty("_internal_node_type", element.getClass().getName()); return elem; } @Override public SpringIndexElement deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); - String typeName = jsonObject.get("type").getAsString(); + String typeName = jsonObject.get("_internal_node_type").getAsString(); try { return context.deserialize(jsonObject, (Class) Class.forName(typeName)); 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 9826ad84a..2f08785f7 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 @@ -57,7 +57,7 @@ public class SpringFactoriesIndexer implements SpringIndexer { // whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid, // we need to change the generation - this will result in a re-indexing due to no up-to-date cache data being found - private static final String GENERATION = "GEN-10"; + private static final String GENERATION = "GEN-11"; private static final String FILE_PATTERN = "**/META-INF/spring/*.factories"; 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 746398149..f0b5ca9ba 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 @@ -92,7 +92,7 @@ public class SpringIndexerJava implements SpringIndexer { // whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid, // we need to change the generation - this will result in a re-indexing due to no up-to-date cache data being found - private static final String GENERATION = "GEN-14"; + private static final String GENERATION = "GEN-15"; private static final String INDEX_FILES_TASK_ID = "index-java-source-files-task-"; private static final String SYMBOL_KEY = "symbols"; 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 d365687fa..ca7d350db 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 @@ -54,7 +54,7 @@ public class SpringIndexerXML implements SpringIndexer { // whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid, // we need to change the generation - this will result in a re-indexing due to no up-to-date cache data being found - private static final String GENERATION = "GEN-9"; + private static final String GENERATION = "GEN-10"; private static final String SYMBOL_KEY = "symbols"; private static final String BEANS_KEY = "beans"; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java index f6f988d97..ba1f0d7c2 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java @@ -13,7 +13,6 @@ package org.springframework.ide.vscode.boot.index.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.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -250,9 +249,9 @@ public class SpringMetamodelIndexTest { InjectionPoint point2 = new InjectionPoint("point2", "point2-type", locationForDoc1, null); Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, new InjectionPoint[] {point1, point2}, Set.of("supertype1", "supertype2"), emptyAnnotations, true); - String serialized = bean1.toString(); Gson gson = IndexCacheOnDiscDeltaBased.createGson(); + String serialized = gson.toJson(bean1); Bean deserializedBean = gson.fromJson(serialized, Bean.class); assertEquals("beanName1", deserializedBean.getName()); @@ -301,9 +300,9 @@ public class SpringMetamodelIndexTest { @Test void testEmptyInjectionPointsOptimizationWithSerializeDeserializeBeans() { Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes, emptyAnnotations, false); - String serialized = bean1.toString(); Gson gson = IndexCacheOnDiscDeltaBased.createGson(); + String serialized = gson.toJson(bean1); Bean deserializedBean = gson.fromJson(serialized, Bean.class); assertEquals("beanName1", deserializedBean.getName()); @@ -446,6 +445,30 @@ public class SpringMetamodelIndexTest { void testSerializeDeserializeBeansWithChildElements() { Gson gson = IndexCacheOnDiscDeltaBased.createGson(); + + Bean bean1 = new Bean("beanName1", "beanType1", locationForDoc1, emptyInjectionPoints, Set.of("supertype1", "supertype2"), emptyAnnotations, false); + Bean bean2 = new Bean("beanName2", "beanType2", locationForDoc1, emptyInjectionPoints, Set.of("supertype3", "supertype4, supertype5"), emptyAnnotations, false); + + Bean bean3 = new Bean("beanName3", "beanType1", locationForDoc1, emptyInjectionPoints, Set.of("supertype1", "supertype2"), emptyAnnotations, false); + Bean bean4 = new Bean("beanName4", "beanType2", locationForDoc1, emptyInjectionPoints, Set.of("supertype3", "supertype4, supertype5"), emptyAnnotations, false); + + bean1.addChild(bean2); + bean2.addChild(bean3); + bean3.addChild(bean4); + + String serialized = gson.toJson(bean1); + Bean newBean = gson.fromJson(serialized, Bean.class); + + assertEquals("beanName1", newBean.getName()); + assertEquals("beanName2", ((Bean) newBean.getChildren().get(0)).getName()); + assertEquals("beanName3", ((Bean) newBean.getChildren().get(0).getChildren().get(0)).getName()); + assertEquals("beanName4", ((Bean) newBean.getChildren().get(0).getChildren().get(0).getChildren().get(0)).getName()); + } + + @Test + void testSerializeDeserializeIndexElementsWithChildElements() { + + Gson gson = IndexCacheOnDiscDeltaBased.createGson(); SubType2 childOfChild = new SubType2(); SubType1 child1 = new SubType1();