json schema, register indexable set in order to index embedded schemas; use enlarged search scope to find items defined in embedded files by index; test for navigation to global schema by id

NB: it is now possible to get JsonSchemaProviderFactory without project context, and it should not change
This commit is contained in:
irengrig
2016-04-15 15:57:48 +02:00
parent 0e3d5244fd
commit d241241357
14 changed files with 212 additions and 79 deletions
@@ -22,6 +22,7 @@ import com.intellij.openapi.fileTypes.ex.FileTypeIdentifiableByVirtualFile;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -64,6 +65,6 @@ public class JsonSchemaFileType extends LanguageFileType implements FileTypeIden
@Override
public boolean isMyFileType(@NotNull VirtualFile file) {
final Project project = ProjectUtil.guessProjectForFile(file);
return project != null && JsonSchemaMappingsProjectConfiguration.getInstance(project).isRegisteredSchemaFile(file);
return project != null && JsonSchemaService.Impl.get(project).isRegisteredSchemaFile(project, file);
}
}
@@ -1,11 +1,12 @@
package com.jetbrains.jsonSchema.extension;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
public interface JsonSchemaFileProvider {
boolean isAvailable(@NotNull VirtualFile file);
boolean isAvailable(@NotNull Project project, @NotNull VirtualFile file);
@NotNull
String getName();
@@ -135,7 +135,7 @@ public class JsonSchemaImportedProviderFactory implements JsonSchemaProviderFact
}
@Override
public boolean isAvailable(@NotNull VirtualFile file) {
public boolean isAvailable(@NotNull Project project, @NotNull VirtualFile file) {
if (myPatterns.isEmpty() || file.isDirectory() || !file.isValid() ||
myProject != null && JsonSchemaMappingsProjectConfiguration.getInstance(myProject).isRegisteredSchemaFile(file)) return false;
for (Processor<VirtualFile> pattern : myPatterns) {
@@ -15,7 +15,6 @@
*/
package com.jetbrains.jsonSchema.extension;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
@@ -23,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.jsonSchema.JsonSchemaFileType;
import com.jetbrains.jsonSchema.JsonSchemaMappingsProjectConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
@@ -30,35 +30,27 @@ import java.util.List;
/**
* @author Irina.Chernushina on 2/24/2016.
*/
public class JsonSchemaProjectSelfProviderFactory {
public class JsonSchemaProjectSelfProviderFactory implements JsonSchemaProviderFactory {
private static final Logger LOG = Logger.getInstance("#com.jetbrains.jsonSchema.extension.JsonSchemaProjectSelfProviderFactory");
public static final String SCHEMA_JSON_FILE_NAME = "schema.json";
private final List<JsonSchemaFileProvider> myProviders;
public static JsonSchemaProjectSelfProviderFactory getInstance(final Project project) {
return ServiceManager.getService(project, JsonSchemaProjectSelfProviderFactory.class);
public JsonSchemaProjectSelfProviderFactory() {
myProviders = Collections.singletonList(new MyJsonSchemaFileProvider());
}
public JsonSchemaProjectSelfProviderFactory(final Project project) {
myProviders = Collections.singletonList(new MyJsonSchemaFileProvider(project));
}
public List<JsonSchemaFileProvider> getProviders() {
@Override
public List<JsonSchemaFileProvider> getProviders(@Nullable Project project) {
return myProviders;
}
private static class MyJsonSchemaFileProvider implements JsonSchemaFileProvider {
public static final Pair<SchemaType, Object> KEY = Pair.create(SchemaType.schema, SchemaType.schema);
private final Project myProject;
public MyJsonSchemaFileProvider(Project project) {
myProject = project;
}
@Override
public boolean isAvailable(@NotNull VirtualFile file) {
if (myProject == null || !JsonSchemaFileType.INSTANCE.equals(file.getFileType())) return false;
return JsonSchemaMappingsProjectConfiguration.getInstance(myProject).isRegisteredSchemaFile(file);
public boolean isAvailable(@NotNull Project project, @NotNull VirtualFile file) {
if (!JsonSchemaFileType.INSTANCE.equals(file.getFileType())) return false;
return JsonSchemaMappingsProjectConfiguration.getInstance(project).isRegisteredSchemaFile(file);
}
@NotNull
@@ -69,29 +61,12 @@ public class JsonSchemaProjectSelfProviderFactory {
@Override
public VirtualFile getSchemaFile() {
return JsonSchemaProviderFactory.getResourceFile(JsonSchemaSelfProviderFactory.class, "jsonSchema/schema.json");
return JsonSchemaProviderFactory.getResourceFile(JsonSchemaProjectSelfProviderFactory.class, "jsonSchema/schema.json");
}
@Override
public SchemaType getSchemaType() {
return SchemaType.schema;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyJsonSchemaFileProvider provider = (MyJsonSchemaFileProvider)o;
if (myProject != null ? !myProject.equals(provider.myProject) : provider.myProject != null) return false;
return true;
}
@Override
public int hashCode() {
return myProject != null ? myProject.hashCode() : 0;
}
}
}
@@ -1,31 +0,0 @@
/*
* 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.extension;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author Irina.Chernushina on 2/16/2016.
*/
public class JsonSchemaSelfProviderFactory implements JsonSchemaProviderFactory {
@Override
public List<JsonSchemaFileProvider> getProviders(@Nullable Project project) {
return JsonSchemaProjectSelfProviderFactory.getInstance(project).getProviders();
}
}
@@ -16,6 +16,7 @@
package com.jetbrains.jsonSchema.extension.schema;
import com.intellij.json.psi.JsonValue;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
@@ -25,6 +26,7 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.ProcessingContext;
import com.intellij.util.indexing.FileBasedIndex;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import com.jetbrains.jsonSchema.impl.JsonSchemaResourcesRootsProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -63,15 +65,16 @@ public class JsonSchemaRefReferenceProvider extends PsiReferenceProvider {
ref = text.substring(idx + 1);
}
final Project project = getElement().getProject();
final Ref<Pair<VirtualFile, Integer>> reference = new Ref<>();
final GlobalSearchScope filter = id == null ? GlobalSearchScope.fileScope(getElement().getContainingFile()) :
GlobalSearchScope.allScope(getElement().getProject());
JsonSchemaResourcesRootsProvider.enlarge(project, GlobalSearchScope.allScope(project));
String finalId = id;
index.processValues(JsonSchemaFileIndex.PROPERTIES_INDEX, ref, null, new FileBasedIndex.ValueProcessor<Integer>() {
@Override
public boolean process(VirtualFile file, Integer value) {
if (finalId != null) {
if (!JsonSchemaService.Impl.getEx(getElement().getProject()).checkFileForId(finalId, file)) {
if (!JsonSchemaService.Impl.getEx(project).checkFileForId(finalId, file)) {
return true;
}
}
@@ -34,6 +34,8 @@ public interface JsonSchemaService {
boolean isSchemaFile(@NotNull VirtualFile file, @NotNull Consumer<String> errorConsumer);
boolean isRegisteredSchemaFile(Project project, @NotNull VirtualFile file);
@Nullable
DocumentationProvider getDocumentationProvider(@Nullable VirtualFile file);
@@ -0,0 +1,93 @@
/*
* 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.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.indexing.IndexableSetContributor;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory;
import com.jetbrains.jsonSchema.extension.SchemaType;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Irina.Chernushina on 4/14/2016.
*/
public class JsonSchemaResourcesRootsProvider extends IndexableSetContributor {
private static final NotNullLazyValue<Set<VirtualFile>> ourFiles = new AtomicNotNullLazyValue<Set<VirtualFile>>() {
@NotNull
@Override
protected Set<VirtualFile> compute() {
final Set<VirtualFile> set = new HashSet<>();
final JsonSchemaProviderFactory[] extensions = Extensions.getExtensions(JsonSchemaProviderFactory.EP_NAME);
for (JsonSchemaProviderFactory extension : extensions) {
final List<JsonSchemaFileProvider> providers = extension.getProviders(null);
for (JsonSchemaFileProvider provider : providers) {
if (!SchemaType.userSchema.equals(provider.getSchemaType())) {
set.add(provider.getSchemaFile());
}
}
}
return set;
}
};
@NotNull
@Override
public Set<VirtualFile> getAdditionalRootsToIndex() {
return ourFiles.getValue();
}
@NotNull
public static GlobalSearchScope enlarge(@NotNull final Project project, @NotNull final GlobalSearchScope scope) {
// we need to create the scope manually since files are outside project root (embedded)
return scope.union(new GlobalSearchScope() {
@Override
public boolean contains(@NotNull final VirtualFile file) {
return ourFiles.getValue().contains(file);
}
@Override
public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
return scope.compare(file1, file2);
}
@Override
public boolean isSearchInModuleContent(@NotNull final Module aModule) {
return scope.isSearchInModuleContent(aModule);
}
@Override
public boolean isSearchInLibraries() {
return scope.isSearchInLibraries();
}
@Override
public boolean isSearchOutsideRootModel() {
return true;
}
});
}
}
@@ -43,6 +43,7 @@ public class JsonSchemaServiceImpl implements JsonSchemaServiceEx {
private final Project myProject;
private final Object myLock;
private final Map<VirtualFile, JsonSchemaObjectCodeInsightWrapper> myWrappers = new HashMap<>();
private final Set<VirtualFile> mySchemaFiles = new HashSet<>();
private final JsonSchemaExportedDefinitions myDefinitions;
public JsonSchemaServiceImpl(@Nullable Project project) {
@@ -84,6 +85,27 @@ public class JsonSchemaServiceImpl implements JsonSchemaServiceEx {
return wrapper != null;
}
@Override
public boolean isRegisteredSchemaFile(@NotNull Project project, @NotNull VirtualFile file) {
synchronized (myLock) {
ensureSchemaFiles(project);
return mySchemaFiles.contains(file);
}
}
private void ensureSchemaFiles(@NotNull final Project project) {
synchronized (myLock) {
if (!mySchemaFiles.isEmpty()) return;
final JsonSchemaProviderFactory[] factories = getProviderFactories();
for (JsonSchemaProviderFactory factory : factories) {
final List<JsonSchemaFileProvider> providers = factory.getProviders(project);
for (JsonSchemaFileProvider provider : providers) {
mySchemaFiles.add(provider.getSchemaFile());
}
}
}
}
@Override
public boolean isSchemaFile(@NotNull VirtualFile file, @NotNull final Consumer<String> errorConsumer) {
final String text;
@@ -179,6 +201,7 @@ public class JsonSchemaServiceImpl implements JsonSchemaServiceEx {
synchronized (myLock) {
myWrappers.clear();
myDefinitions.reset();
mySchemaFiles.clear();
}
}
@@ -225,14 +248,16 @@ public class JsonSchemaServiceImpl implements JsonSchemaServiceEx {
@Nullable
private List<JsonSchemaObjectCodeInsightWrapper> getWrappers(@Nullable VirtualFile file) {
if (file == null) return null;
if (file == null || myProject == null) return null;
final List<JsonSchemaObjectCodeInsightWrapper> wrappers = new ArrayList<>();
JsonSchemaProviderFactory[] factories = getProviderFactories();
for (JsonSchemaProviderFactory factory : factories) {
for (JsonSchemaFileProvider provider : factory.getProviders(myProject)) {
if (provider.isAvailable(file)) {
synchronized (myLock) {
final Set<VirtualFile> files = mySchemaFiles.isEmpty() ? new HashSet<>() : null;
for (JsonSchemaProviderFactory factory : factories) {
for (JsonSchemaFileProvider provider : factory.getProviders(myProject)) {
final VirtualFile key = provider.getSchemaFile();
synchronized (myLock) {
if (files != null) files.add(key);
if (provider.isAvailable(myProject, file)) {
JsonSchemaObjectCodeInsightWrapper wrapper = myWrappers.get(key);
if (wrapper == null) {
wrapper = createWrapper(provider);
@@ -243,6 +268,7 @@ public class JsonSchemaServiceImpl implements JsonSchemaServiceEx {
}
}
}
if (files != null) mySchemaFiles.addAll(files);
}
return wrappers;
}
@@ -247,4 +247,58 @@ public class JsonSchemaCrossReferencesTest extends CompletionTestCase {
instance.removeSchema(inherited);
instance.removeSchema(base);
}
public void testJsonSchemaGlobalRefsCrossResolve() throws Exception {
configureByFiles(null, BASE_SCHEMA_RESOLVE_PATH + "/referencingGlobalSchema.json");
String moduleDir = null;
VirtualFile moduleFile = null;
VirtualFile[] children = getProject().getBaseDir().getChildren();
for (VirtualFile child : children) {
if (child.isDirectory()) {
moduleDir = child.getName();
moduleFile = child;
break;
}
}
Assert.assertNotNull(moduleDir);
AreaPicoContainer container = Extensions.getArea(getProject()).getPicoContainer();
final String key = JsonSchemaMappingsProjectConfiguration.class.getName();
container.unregisterComponent(key);
container.registerComponentImplementation(key, TestJsonSchemaMappingsProjectConfiguration.class);
final JsonSchemaMappingsProjectConfiguration instance = JsonSchemaMappingsProjectConfiguration.getInstance(getProject());
final JsonSchemaMappingsConfigurationBase.SchemaInfo inherited
= new JsonSchemaMappingsConfigurationBase.SchemaInfo("inherited", "/" + moduleDir + "/referencingGlobalSchema.json", false, Collections.emptyList());
instance.addSchema(inherited);
try {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
myFileTypeManager.associatePattern(JsonSchemaFileType.INSTANCE, "*Schema.json");
}
});
int offset = myEditor.getCaretModel().getPrimaryCaret().getOffset();
final PsiReference referenceAt = myFile.findReferenceAt(offset);
Assert.assertNotNull(referenceAt);
final PsiElement resolve = referenceAt.resolve();
Assert.assertNotNull(resolve);
Assert.assertEquals("\"enum\"", resolve.getText());
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
myFileTypeManager.removeAssociatedExtension(JsonSchemaFileType.INSTANCE, "*Schema.json");
}
});
} finally {
container.unregisterComponent(key);
container.registerComponentImplementation(key, JsonSchemaMappingsProjectConfiguration.class);
}
instance.removeSchema(inherited);
}
}
@@ -3,6 +3,7 @@ package com.jetbrains.jsonSchema;
import com.intellij.json.JsonLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
import com.jetbrains.jsonSchema.extension.SchemaType;
@@ -16,7 +17,7 @@ public class JsonSchemaTestProvider implements JsonSchemaFileProvider {
}
@Override
public boolean isAvailable(@NotNull VirtualFile file) {
public boolean isAvailable(@NotNull Project project, @NotNull VirtualFile file) {
return file.getFileType() instanceof LanguageFileType && ((LanguageFileType)file.getFileType()).getLanguage().isKindOf(JsonLanguage.INSTANCE);
}
@@ -0,0 +1,7 @@
{
"properties": {
"smth": {
"$ref" : "http://json-schema.org/draft-04/schema#/properties/enum<caret>"
}
}
}
@@ -60,7 +60,7 @@ public abstract class IndexableSetContributor {
* the returned set should not contain nulls or invalid files
*/
@NotNull
public abstract Set<VirtualFile> getAdditionalRootsToIndex();
public abstract Set getAdditionalRootsToIndex();
@NotNull
private static Set<VirtualFile> filterOutNulls(@NotNull IndexableSetContributor contributor,
@@ -77,10 +77,11 @@
<fileBasedIndex implementation="com.jetbrains.jsonSchema.extension.schema.JsonSchemaFileIndex"/>
<psi.referenceContributor language="JSON" implementation="com.jetbrains.jsonSchema.extension.schema.JsonSchemaReferenceContributor"/>
<indexedRootsProvider implementation="com.jetbrains.jsonSchema.impl.JsonSchemaResourcesRootsProvider"/>
</extensions>
<extensions defaultExtensionNs="JavaScript">
<JsonSchema.ProviderFactory implementation="com.jetbrains.jsonSchema.extension.JsonSchemaImportedProviderFactory"/>
<JsonSchema.ProviderFactory implementation="com.jetbrains.jsonSchema.extension.JsonSchemaSelfProviderFactory"/>
<JsonSchema.ProviderFactory implementation="com.jetbrains.jsonSchema.extension.JsonSchemaProjectSelfProviderFactory"/>
</extensions>
<extensionPoints>