From 1103799e6207b8eb684f757a76ad3b5c6d4ff769 Mon Sep 17 00:00:00 2001 From: irengrig Date: Thu, 18 Feb 2016 15:13:28 +0100 Subject: [PATCH] WEB-20367 JSON Schema: provide validation warning for the file under several schemas Use all schemas available for the file; display warning message with quick fix to edit settings --- .../JsonSchemaMappingsConfigurable.java | 2 + .../extension/JsonSchemaFileProvider.java | 3 + .../JsonSchemaImportedProviderFactory.java | 15 +- .../JsonSchemaSelfProviderFactory.java | 6 + .../jsonSchema/impl/CodeInsightProviders.java | 35 +++++ .../JsonSchemaObjectCodeInsightWrapper.java | 24 ++- .../impl/JsonSchemaServiceImpl.java | 143 ++++++++++++++++-- .../jsonSchema/JsonSchemaTestProvider.java | 6 + 8 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 json/src/com/jetbrains/jsonSchema/impl/CodeInsightProviders.java diff --git a/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsConfigurable.java b/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsConfigurable.java index 80d2c957ea5c..8380614c7500 100644 --- a/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsConfigurable.java +++ b/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsConfigurable.java @@ -1,5 +1,6 @@ package com.jetbrains.jsonSchema; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.json.JsonBundle; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnAction; @@ -185,6 +186,7 @@ public class JsonSchemaMappingsConfigurable extends MasterDetailsComponent imple final JsonSchemaService service = JsonSchemaService.Impl.get(project); if (service != null) service.reset(); } + if (myProject != null) DaemonCodeAnalyzer.getInstance(myProject).restart(); } private static void validate(@NotNull List list) throws ConfigurationException { diff --git a/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaFileProvider.java b/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaFileProvider.java index a3c9d42d32d5..fcd53a9aba3d 100644 --- a/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaFileProvider.java +++ b/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaFileProvider.java @@ -12,4 +12,7 @@ public interface JsonSchemaFileProvider { @Nullable Reader getSchemaReader(); + + @NotNull + String getName(); } diff --git a/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaImportedProviderFactory.java b/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaImportedProviderFactory.java index c68c556301e7..b940856bb524 100644 --- a/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaImportedProviderFactory.java +++ b/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaImportedProviderFactory.java @@ -43,16 +43,21 @@ public class JsonSchemaImportedProviderFactory implements JsonSchemaProviderFact final Map map = configuration.getStateMap(); for (JsonSchemaMappingsConfigurationBase.SchemaInfo info : map.values()) { if (!info.getPatterns().isEmpty()) { - list.add(new MyProvider(project, configuration.convertToAbsoluteFile(info.getRelativePathToSchema()), info.getPatterns())); + list.add(new MyProvider(project, info.getName(), configuration.convertToAbsoluteFile(info.getRelativePathToSchema()), info.getPatterns())); } } } private static class MyProvider implements JsonSchemaFileProvider, JsonSchemaImportedProviderMarker { + @NotNull private final String myName; @NotNull private final File myFile; @NotNull private final List> myPatterns; - public MyProvider(@Nullable final Project project, @NotNull File file, @NotNull List patterns) { + public MyProvider(@Nullable final Project project, + @NotNull String name, + @NotNull File file, + @NotNull List patterns) { + myName = name; myFile = file; myPatterns = new ArrayList>(); for (final JsonSchemaMappingsConfigurationBase.Item pattern : patterns) { @@ -95,6 +100,12 @@ public class JsonSchemaImportedProviderFactory implements JsonSchemaProviderFact } } + @NotNull + @Override + public String getName() { + return myName; + } + @Override public boolean isAvailable(@NotNull VirtualFile file) { if (file.isDirectory() || !file.isValid()) return false; diff --git a/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaSelfProviderFactory.java b/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaSelfProviderFactory.java index c33ca8030d0a..0fa7c73def78 100644 --- a/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaSelfProviderFactory.java +++ b/json/src/com/jetbrains/jsonSchema/extension/JsonSchemaSelfProviderFactory.java @@ -52,6 +52,12 @@ public class JsonSchemaSelfProviderFactory implements JsonSchemaProviderFactory return content == null ? null : new StringReader(content); } + @NotNull + @Override + public String getName() { + return "schema.json"; + } + @Nullable private String getContent() { ClassLoader loader = JsonSchemaSelfProviderFactory.class.getClassLoader(); diff --git a/json/src/com/jetbrains/jsonSchema/impl/CodeInsightProviders.java b/json/src/com/jetbrains/jsonSchema/impl/CodeInsightProviders.java new file mode 100644 index 000000000000..3ba9f221d6a2 --- /dev/null +++ b/json/src/com/jetbrains/jsonSchema/impl/CodeInsightProviders.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.jsonSchema.impl; + +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.lang.annotation.Annotator; +import com.intellij.lang.documentation.DocumentationProvider; +import org.jetbrains.annotations.NotNull; + +/** + * @author Irina.Chernushina on 2/18/2016. + */ +public interface CodeInsightProviders { + @NotNull + CompletionContributor getContributor(); + + @NotNull + Annotator getAnnotator(); + + @NotNull + DocumentationProvider getDocumentationProvider(); +} diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObjectCodeInsightWrapper.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObjectCodeInsightWrapper.java index 975962ed7896..2ccc2cd1c13d 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObjectCodeInsightWrapper.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObjectCodeInsightWrapper.java @@ -6,7 +6,9 @@ import com.intellij.lang.annotation.Annotator; import com.intellij.lang.documentation.DocumentationProvider; import org.jetbrains.annotations.NotNull; -class JsonSchemaObjectCodeInsightWrapper { +class JsonSchemaObjectCodeInsightWrapper implements CodeInsightProviders { + @NotNull private final String myName; + private boolean myIsUserSchema; @NotNull private final CompletionContributor myContributor; @@ -16,24 +18,42 @@ class JsonSchemaObjectCodeInsightWrapper { @NotNull private final DocumentationProvider myDocumentationProvider; - public JsonSchemaObjectCodeInsightWrapper(@NotNull JsonSchemaObject schemaObject) { + public JsonSchemaObjectCodeInsightWrapper(@NotNull String name, @NotNull JsonSchemaObject schemaObject) { + myName = name; myContributor = new JsonBySchemaObjectCompletionContributor(schemaObject); myAnnotator = new JsonBySchemaObjectAnnotator(schemaObject); myDocumentationProvider = new JsonBySchemaDocumentationProvider(schemaObject); } + @Override @NotNull public CompletionContributor getContributor() { return myContributor; } + @Override @NotNull public Annotator getAnnotator() { return myAnnotator; } + @NotNull + public String getName() { + return myName; + } + + @Override @NotNull public DocumentationProvider getDocumentationProvider() { return myDocumentationProvider; } + + public JsonSchemaObjectCodeInsightWrapper setUserSchema(boolean userSchema) { + myIsUserSchema = userSchema; + return this; + } + + public boolean isUserSchema() { + return myIsUserSchema; + } } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java index 16d15962078c..f0958257592e 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaServiceImpl.java @@ -2,21 +2,38 @@ package com.jetbrains.jsonSchema.impl; import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.idea.RareLogger; +import com.intellij.lang.annotation.Annotation; +import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; +import com.intellij.lang.documentation.CompositeDocumentationProvider; import com.intellij.lang.documentation.DocumentationProvider; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.jsonSchema.JsonSchemaMappingsConfigurable; import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider; import com.jetbrains.jsonSchema.extension.JsonSchemaImportedProviderMarker; import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory; import com.jetbrains.jsonSchema.ide.JsonSchemaService; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.Reader; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ConcurrentMap; public class JsonSchemaServiceImpl implements JsonSchemaService { @@ -38,25 +55,25 @@ public class JsonSchemaServiceImpl implements JsonSchemaService { @Nullable public Annotator getAnnotator(@Nullable VirtualFile file) { - JsonSchemaObjectCodeInsightWrapper wrapper = getWrapper(file); + CodeInsightProviders wrapper = getWrapper(file); return wrapper != null ? wrapper.getAnnotator() : null; } @Nullable public CompletionContributor getCompletionContributor(@Nullable VirtualFile file) { - JsonSchemaObjectCodeInsightWrapper wrapper = getWrapper(file); + CodeInsightProviders wrapper = getWrapper(file); return wrapper != null ? wrapper.getContributor() : null; } public boolean hasSchema(@Nullable VirtualFile file) { - JsonSchemaObjectCodeInsightWrapper wrapper = getWrapper(file); + CodeInsightProviders wrapper = getWrapper(file); return wrapper != null; } @Nullable @Override public DocumentationProvider getDocumentationProvider(@Nullable VirtualFile file) { - JsonSchemaObjectCodeInsightWrapper wrapper = getWrapper(file); + CodeInsightProviders wrapper = getWrapper(file); return wrapper != null ? wrapper.getDocumentationProvider() : null; } @@ -66,7 +83,7 @@ public class JsonSchemaServiceImpl implements JsonSchemaService { try { if (reader != null) { JsonSchemaObject resultObject = new JsonSchemaReader().read(reader); - return new JsonSchemaObjectCodeInsightWrapper(resultObject); + return new JsonSchemaObjectCodeInsightWrapper(provider.getName(), resultObject).setUserSchema(provider instanceof JsonSchemaImportedProviderMarker); } } catch (Exception e) { @@ -86,8 +103,9 @@ public class JsonSchemaServiceImpl implements JsonSchemaService { } @Nullable - private JsonSchemaObjectCodeInsightWrapper getWrapper(@Nullable VirtualFile file) { + private CodeInsightProviders getWrapper(@Nullable VirtualFile file) { if (file == null) return null; + final List wrappers = new ArrayList<>(); JsonSchemaProviderFactory[] factories = getProviderFactories(); for (JsonSchemaProviderFactory factory : factories) { for (JsonSchemaFileProvider provider : factory.getProviders(myProject)) { @@ -99,13 +117,120 @@ public class JsonSchemaServiceImpl implements JsonSchemaService { myWrappers.putIfAbsent(provider, newWrapper); wrapper = myWrappers.get(provider); } - if (wrapper != null) { - return wrapper; + wrappers.add(wrapper); } } } } - return null; + + return wrappers.isEmpty() ? null : (wrappers.size() == 1 ? wrappers.get(0) : new CompositeCodeInsightProviderWithWarning(wrappers)); + } + + private static class CompositeCodeInsightProviderWithWarning implements CodeInsightProviders { + private final List myWrappers; + private CompletionContributor myContributor; + private Annotator myAnnotator; + private DocumentationProvider myDocumentationProvider; + + public CompositeCodeInsightProviderWithWarning(List wrappers) { + myWrappers = wrappers; + myContributor = new CompletionContributor() { + @Override + public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { + for (JsonSchemaObjectCodeInsightWrapper wrapper : myWrappers) { + wrapper.getContributor().fillCompletionVariants(parameters, result); + } + } + }; + myAnnotator = new Annotator() { + private String message; + { + boolean haveSystemSchemas = false; + for (JsonSchemaObjectCodeInsightWrapper wrapper : myWrappers) { + haveSystemSchemas |= !wrapper.isUserSchema(); + } + boolean withTypes = haveSystemSchemas; + final List names = new ArrayList<>(); + for (JsonSchemaObjectCodeInsightWrapper wrapper : myWrappers) { + if (withTypes) { + names.add((wrapper.isUserSchema() ? "user" : "system") + " schema '" + wrapper.getName() + "'"); + } else { + names.add(wrapper.getName()); + } + } + message = "There are several JSON Schemas mapped to this file: " + StringUtil.join(names, "; ") + ""; + } + + @Override + public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { + if (element instanceof PsiFile) { + addFileLevelWarning(element, holder); + } + for (JsonSchemaObjectCodeInsightWrapper wrapper : myWrappers) { + wrapper.getAnnotator().annotate(element, holder); + } + } + + private void addFileLevelWarning(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { + final Annotation annotation = holder.createErrorAnnotation(element, message); + annotation.setFileLevelAnnotation(true); + annotation.registerFix(new IntentionAction() { + @Nls + @NotNull + @Override + public String getText() { + return "Edit JSON Schema Mappings"; + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return "JSON Schema"; + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return true; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + ShowSettingsUtil.getInstance().editConfigurable(project, new JsonSchemaMappingsConfigurable(project)); + DaemonCodeAnalyzer.getInstance(project).restart(file); + } + + @Override + public boolean startInWriteAction() { + return false; + } + }); + } + }; + final List list = new ArrayList<>(); + for (JsonSchemaObjectCodeInsightWrapper wrapper : myWrappers) { + list.add(wrapper.getDocumentationProvider()); + } + myDocumentationProvider = CompositeDocumentationProvider.wrapProviders(list); + } + + @NotNull + @Override + public CompletionContributor getContributor() { + return myContributor; + } + + @NotNull + @Override + public Annotator getAnnotator() { + return myAnnotator; + } + + @NotNull + @Override + public DocumentationProvider getDocumentationProvider() { + return myDocumentationProvider; + } } } diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestProvider.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestProvider.java index 204adff96326..17d037553082 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestProvider.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaTestProvider.java @@ -28,4 +28,10 @@ public class JsonSchemaTestProvider implements JsonSchemaFileProvider { public Reader getSchemaReader() { return new StringReader(mySchemaText); } + + @NotNull + @Override + public String getName() { + return "test"; + } }