From cc18337aad68dbe93bad8e175c3511a6698bb36e Mon Sep 17 00:00:00 2001 From: Anton Lobov Date: Thu, 14 Mar 2019 18:47:24 +0100 Subject: [PATCH] json schema performance: cache computed json schema for a psi file, don't recompute it every time this fixes performance tests after adding the injection feature injection checks are invoked for every matching-by-class psi element, and that is really expensive if to recompute --- .../json/JsonSpellcheckerStrategy.java | 2 +- .../jsonSchema/ide/JsonSchemaService.java | 3 +++ .../jsonSchema/impl/JsonCachedValues.java | 19 +++++++++++++++++++ .../impl/JsonSchemaBasedLanguageInjector.java | 7 +++---- .../impl/JsonSchemaCompletionContributor.java | 2 +- .../impl/JsonSchemaDocumentationProvider.java | 2 +- .../JsonSchemaGotoDeclarationHandler.java | 2 +- .../impl/JsonSchemaServiceImpl.java | 11 ++++++++++- .../JsonSchemaBasedInspectionBase.java | 2 +- .../JsonSchemaCrossReferencesTest.java | 2 +- .../jsonSchema/JsonSchemaPerformanceTest.java | 4 ++-- .../jsonSchema/JsonSchemaTestServiceImpl.java | 12 ++++++++++++ .../YamlJsonSchemaCompletionContributor.java | 2 +- .../YamlJsonSchemaDocumentationProvider.java | 8 ++------ .../YamlJsonSchemaGotoDeclarationHandler.java | 2 +- .../schema/YamlJsonSchemaInspectionBase.java | 9 ++------- 16 files changed, 61 insertions(+), 28 deletions(-) diff --git a/json/src/com/intellij/json/JsonSpellcheckerStrategy.java b/json/src/com/intellij/json/JsonSpellcheckerStrategy.java index c690a31ab9a0..ab4a397ac5bc 100644 --- a/json/src/com/intellij/json/JsonSpellcheckerStrategy.java +++ b/json/src/com/intellij/json/JsonSpellcheckerStrategy.java @@ -57,7 +57,7 @@ public class JsonSpellcheckerStrategy extends SpellcheckingStrategy { Project project = element.getProject(); final JsonSchemaService service = JsonSchemaService.Impl.get(project); if (!service.isApplicableToFile(file)) return false; - final JsonSchemaObject rootSchema = service.getSchemaObject(file); + final JsonSchemaObject rootSchema = service.getSchemaObject(element.getContainingFile()); if (rootSchema == null) return false; if (service.isSchemaFile(rootSchema)) { JsonProperty property = ObjectUtils.tryCast(element.getParent(), JsonProperty.class); diff --git a/json/src/com/jetbrains/jsonSchema/ide/JsonSchemaService.java b/json/src/com/jetbrains/jsonSchema/ide/JsonSchemaService.java index 73cbf1edd5c5..a68084160b1f 100644 --- a/json/src/com/jetbrains/jsonSchema/ide/JsonSchemaService.java +++ b/json/src/com/jetbrains/jsonSchema/ide/JsonSchemaService.java @@ -54,6 +54,9 @@ public interface JsonSchemaService { @Nullable JsonSchemaObject getSchemaObject(@NotNull VirtualFile file); + @Nullable + JsonSchemaObject getSchemaObject(@NotNull PsiFile file); + @Nullable JsonSchemaObject getSchemaObjectForSchemaFile(@NotNull VirtualFile schemaFile); diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonCachedValues.java b/json/src/com/jetbrains/jsonSchema/impl/JsonCachedValues.java index 8494622c57de..972a6886cff7 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonCachedValues.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonCachedValues.java @@ -1,6 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.jsonSchema.impl; +import com.intellij.codeInsight.completion.CompletionUtil; import com.intellij.json.navigation.JsonQualifiedNameKind; import com.intellij.json.navigation.JsonQualifiedNameProvider; import com.intellij.json.psi.*; @@ -20,6 +21,7 @@ import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.jsonSchema.JsonPointerUtil; import com.jetbrains.jsonSchema.JsonSchemaCatalogEntry; +import com.jetbrains.jsonSchema.ide.JsonSchemaService; import com.jetbrains.jsonSchema.remote.JsonFileResolver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -207,4 +209,21 @@ public class JsonCachedValues { @NotNull Key> key) { return CachedValuesManager.getCachedValue(psiFile, key, () -> CachedValueProvider.Result.create(eval.fun(psiFile), psiFile)); } + + public static final Key> OBJECT_FOR_FILE_KEY = new Key<>("JsonCachedValues.OBJ_KEY"); + @Nullable + static JsonSchemaObject computeSchemaForFile(@NotNull PsiFile file, @NotNull JsonSchemaService service) { + final PsiFile originalFile = CompletionUtil.getOriginalOrSelf(file); + JsonSchemaObject value = CachedValuesManager.getCachedValue(originalFile, OBJECT_FOR_FILE_KEY, () -> { + VirtualFile virtualFile = originalFile.getVirtualFile(); + JsonSchemaObject schemaObject = virtualFile == null ? null : service.getSchemaObject(virtualFile); + VirtualFile schemaFile = schemaObject == null ? null : service.resolveSchemaFile(schemaObject); + PsiFile psiFile = schemaFile == null ? null : originalFile.getManager().findFile(schemaFile); + JsonSchemaObject object = schemaObject == null ? JsonSchemaObject.NULL_OBJ : schemaObject; + return psiFile == null + ? CachedValueProvider.Result.create(object, originalFile) + : CachedValueProvider.Result.create(object, originalFile, psiFile); + }); + return value == JsonSchemaObject.NULL_OBJ ? null : value; + } } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaBasedLanguageInjector.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaBasedLanguageInjector.java index 82071a01d0e3..07fe55639355 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaBasedLanguageInjector.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaBasedLanguageInjector.java @@ -6,8 +6,8 @@ import com.intellij.json.psi.JsonStringLiteral; import com.intellij.lang.Language; import com.intellij.lang.injection.MultiHostRegistrar; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.util.ThreeState; import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker; import com.jetbrains.jsonSchema.ide.JsonSchemaService; @@ -28,9 +28,8 @@ public class JsonSchemaBasedLanguageInjector extends JsonSchemaInjectorBase { @Nullable public static Language getLanguageToInject(@NotNull PsiElement context) { Project project = context.getProject(); - VirtualFile file = context.getContainingFile().getVirtualFile(); - if (file == null) return null; - JsonSchemaObject schemaObject = JsonSchemaService.Impl.get(project).getSchemaObject(file); + PsiFile containingFile = context.getContainingFile(); + JsonSchemaObject schemaObject = JsonSchemaService.Impl.get(project).getSchemaObject(containingFile); if (schemaObject == null) return null; JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(context, schemaObject); if (walker == null || walker.isName(context) != ThreeState.NO) return null; diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java index 2e74fc52cabb..3e6968f38ffb 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java @@ -68,7 +68,7 @@ public class JsonSchemaCompletionContributor extends CompletionContributor { final JsonSchemaService service = JsonSchemaService.Impl.get(position.getProject()); if (!service.isApplicableToFile(file)) return; - final JsonSchemaObject rootSchema = service.getSchemaObject(file); + final JsonSchemaObject rootSchema = service.getSchemaObject(position.getContainingFile()); if (rootSchema == null) return; PsiElement positionParent = position.getParent(); if (positionParent != null) { diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaDocumentationProvider.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaDocumentationProvider.java index c2723ecc2fe6..133b5cdcb334 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaDocumentationProvider.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaDocumentationProvider.java @@ -59,7 +59,7 @@ public class JsonSchemaDocumentationProvider implements DocumentationProvider { final JsonSchemaService service = JsonSchemaService.Impl.get(element.getProject()); VirtualFile virtualFile = containingFile.getViewProvider().getVirtualFile(); if (!service.isApplicableToFile(virtualFile)) return null; - final JsonSchemaObject rootSchema = service.getSchemaObject(virtualFile); + final JsonSchemaObject rootSchema = service.getSchemaObject(containingFile); if (rootSchema == null) return null; return generateDoc(element, rootSchema, preferShort, forcedPropName); diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaGotoDeclarationHandler.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaGotoDeclarationHandler.java index 49da821fd817..bd61f2ff5f8d 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaGotoDeclarationHandler.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaGotoDeclarationHandler.java @@ -32,7 +32,7 @@ public class JsonSchemaGotoDeclarationHandler implements GotoDeclarationHandler if (file == null || !service.isApplicableToFile(file)) return null; final JsonPointerPosition steps = JsonOriginalPsiWalker.INSTANCE.findPosition(literal, true); if (steps == null) return null; - final JsonSchemaObject schemaObject = service.getSchemaObject(file); + final JsonSchemaObject schemaObject = service.getSchemaObject(containingFile); if (schemaObject != null) { final PsiElement target = new JsonSchemaResolver(sourceElement.getProject(), schemaObject, steps).findNavigationTarget(((JsonProperty)parent).getValue()); if (target != null) { diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java index ee0aba6283fb..0712c9de6bfe 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java @@ -14,6 +14,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.impl.http.HttpVirtualFile; +import com.intellij.psi.PsiFile; import com.intellij.util.containers.ConcurrentList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; @@ -285,6 +286,13 @@ public class JsonSchemaServiceImpl implements JsonSchemaService { return JsonCachedValues.getSchemaObject(replaceHttpFileWithBuiltinIfNeeded(schemaFile), myProject); } + + @Nullable + @Override + public JsonSchemaObject getSchemaObject(@NotNull PsiFile file) { + return JsonCachedValues.computeSchemaForFile(file, this); + } + public VirtualFile replaceHttpFileWithBuiltinIfNeeded(VirtualFile schemaFile) { // this hack is needed to handle user-defined mappings via urls // we cannot perform that inside corresponding provider, because it leads to recursive component dependency @@ -460,8 +468,9 @@ public class JsonSchemaServiceImpl implements JsonSchemaService { @NotNull @Override public MultiMap compute() { + MultiMap map = createFileProviderMap(myFactory.create(), myProject); myIsComputed.set(true); - return createFileProviderMap(myFactory.create(), myProject); + return map; } @NotNull diff --git a/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaBasedInspectionBase.java b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaBasedInspectionBase.java index cab54cb91d14..61e3474755bc 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaBasedInspectionBase.java +++ b/json/src/com/jetbrains/jsonSchema/impl/inspections/JsonSchemaBasedInspectionBase.java @@ -34,7 +34,7 @@ public abstract class JsonSchemaBasedInspectionBase extends LocalInspectionTool VirtualFile virtualFile = file.getViewProvider().getVirtualFile(); if (!service.isApplicableToFile(virtualFile)) return PsiElementVisitor.EMPTY_VISITOR; - return doBuildVisitor(root, service.getSchemaObject(virtualFile), service, holder, session); + return doBuildVisitor(root, service.getSchemaObject(file), service, holder, session); } protected abstract PsiElementVisitor doBuildVisitor(@NotNull JsonValue root, diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java index b43a7795cb65..a7e34abfe5cd 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaCrossReferencesTest.java @@ -819,11 +819,11 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest { @Override public void configureFiles() { configureByFiles(null, "completionWithRootRef.json", "cycledWithRootRefSchema.json"); - complete(); } @Override public void doCheck() { + complete(); checkCompletion("\"id\"", "\"testProp\""); } }); diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaPerformanceTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaPerformanceTest.java index e85295dddf2d..c06868da40f8 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaPerformanceTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaPerformanceTest.java @@ -28,11 +28,11 @@ public class JsonSchemaPerformanceTest extends JsonSchemaHeavyAbstractTest { } public void testSwaggerHighlighting() { - doPerformanceTest(10000, "swagger"); + doPerformanceTest(8000, "swagger"); } public void testTsLintSchema() { - doPerformanceTest(9000, "tslint-schema"); + doPerformanceTest(7500, "tslint-schema"); } private void doPerformanceTest(int expectedMs, String jsonFileNameWithoutExtension) { diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestServiceImpl.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestServiceImpl.java index 87b5093560d3..f3d560e9d92c 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestServiceImpl.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestServiceImpl.java @@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.List; +import java.util.Objects; public final class JsonSchemaTestServiceImpl extends JsonSchemaServiceImpl { private static JsonSchemaFileProvider provider; @@ -36,4 +37,15 @@ public final class JsonSchemaTestServiceImpl extends JsonSchemaServiceImpl { } }); } + + @Override + public int hashCode() { + return Objects.hash(JsonSchemaTestServiceImpl.class, "test"); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof JsonSchemaTestServiceImpl) return true; + return super.equals(obj); + } } diff --git a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaCompletionContributor.java b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaCompletionContributor.java index 936df7449498..bb46c886c9a9 100644 --- a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaCompletionContributor.java +++ b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaCompletionContributor.java @@ -15,7 +15,7 @@ public class YamlJsonSchemaCompletionContributor extends CompletionContributor { public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { final PsiElement position = parameters.getPosition(); final JsonSchemaService jsonSchemaService = JsonSchemaService.Impl.get(position.getProject()); - JsonSchemaObject object = jsonSchemaService.getSchemaObject(parameters.getOriginalFile().getVirtualFile()); + JsonSchemaObject object = jsonSchemaService.getSchemaObject(parameters.getOriginalFile()); if (object != null) { JsonSchemaCompletionContributor.doCompletion(parameters, result, object, false); } diff --git a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaDocumentationProvider.java b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaDocumentationProvider.java index 1afe32245af3..a4a7775c0af6 100644 --- a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaDocumentationProvider.java +++ b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaDocumentationProvider.java @@ -3,7 +3,6 @@ package org.jetbrains.yaml.schema; import com.intellij.lang.documentation.DocumentationProviderEx; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; @@ -39,9 +38,7 @@ public class YamlJsonSchemaDocumentationProvider extends DocumentationProviderEx final JsonSchemaService jsonSchemaService = JsonSchemaService.Impl.get(element.getProject()); PsiFile containingFile = element.getContainingFile(); if (containingFile == null) return null; - VirtualFile virtualFile = containingFile.getVirtualFile(); - if (virtualFile == null) return null; - JsonSchemaObject schemaObject = jsonSchemaService.getSchemaObject(virtualFile); + JsonSchemaObject schemaObject = jsonSchemaService.getSchemaObject(containingFile); if (schemaObject == null) return null; return JsonSchemaDocumentationProvider.generateDoc(element, schemaObject, preferShort, null); } @@ -64,8 +61,7 @@ public class YamlJsonSchemaDocumentationProvider extends DocumentationProviderEx @NotNull PsiFile file, @Nullable PsiElement contextElement) { JsonSchemaService service = JsonSchemaService.Impl.get(file.getProject()); - VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null || service == null || service.getSchemaObject(virtualFile) == null) return null; + if (service == null || service.getSchemaObject(file) == null) return null; return contextElement; } } diff --git a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaGotoDeclarationHandler.java b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaGotoDeclarationHandler.java index c7fba36cb7d0..1a9c6c9af3b4 100644 --- a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaGotoDeclarationHandler.java +++ b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaGotoDeclarationHandler.java @@ -31,7 +31,7 @@ public class YamlJsonSchemaGotoDeclarationHandler implements GotoDeclarationHand if (file == null || !service.isApplicableToFile(file)) return null; final JsonPointerPosition steps = YamlJsonPsiWalker.INSTANCE.findPosition(literal, true); if (steps == null) return null; - final JsonSchemaObject schemaObject = service.getSchemaObject(file); + final JsonSchemaObject schemaObject = service.getSchemaObject(containingFile); if (schemaObject != null) { final PsiElement target = new JsonSchemaResolver(sourceElement.getProject(), schemaObject, steps).findNavigationTarget(literal.getValue()); if (target != null) { diff --git a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaInspectionBase.java b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaInspectionBase.java index cd30d87ed6ec..4882b1aeb3b3 100644 --- a/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaInspectionBase.java +++ b/plugins/yaml/src/org/jetbrains/yaml/schema/YamlJsonSchemaInspectionBase.java @@ -33,16 +33,11 @@ public abstract class YamlJsonSchemaInspectionBase extends LocalInspectionTool { if (!service.isApplicableToFile(virtualFile)) { return PsiElementVisitor.EMPTY_VISITOR; } - final JsonSchemaObject rootSchema = service.getSchemaObject(virtualFile); + final JsonSchemaObject rootSchema = service.getSchemaObject(file); if (rootSchema == null) { return PsiElementVisitor.EMPTY_VISITOR; } - - JsonSchemaObject object = service.getSchemaObject(virtualFile); - if (object == null) { - return PsiElementVisitor.EMPTY_VISITOR; - } - return doBuildVisitor(holder, session, root, object); + return doBuildVisitor(holder, session, root, rootSchema); } protected abstract PsiElementVisitor doBuildVisitor(@NotNull ProblemsHolder holder,