/*
* Copyright 2003-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 jetbrains.mps.idea.java.psi.impl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementFinder;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.search.DelegatingGlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.CollectConsumer;
import com.intellij.util.Consumer;
import com.intellij.util.indexing.FileBasedIndex;
import jetbrains.mps.extapi.persistence.FileDataSource;
import jetbrains.mps.extapi.persistence.FolderDataSource;
import jetbrains.mps.extapi.persistence.FolderSetDataSource;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.idea.core.psi.impl.MPSPsiProvider;
import jetbrains.mps.idea.core.usages.IdeaSearchScope;
import jetbrains.mps.idea.java.index.MPSFQNameJavaClassIndex;
import jetbrains.mps.idea.java.index.MPSJavaPackageIndex;
import jetbrains.mps.idea.java.util.ClassUtil;
import jetbrains.mps.smodel.FastNodeFinder;
import jetbrains.mps.smodel.FastNodeFinderManager;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.util.Computable;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.workbench.goTo.index.SNodeDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.EditableSModel;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.module.SearchScope;
import org.jetbrains.mps.openapi.persistence.DataSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* evgeny, 1/25/13
*/
public class MPSJavaClassFinder extends PsiElementFinder {
private final Project myProject;
public MPSJavaClassFinder(Project project) {
myProject = project;
}
@Nullable
@Override
public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
final PsiClass[] classes = findClasses(qualifiedName, scope);
if (classes.length == 1) return classes[0];
return null;
}
@NotNull
@Override
public PsiClass[] findClasses(@NotNull final String qualifiedName, @NotNull final GlobalSearchScope scope) {
ApplicationManager.getApplication().assertReadAccessAllowed();
Project project = scope.getProject();
if (project == null) {
return PsiClass.EMPTY_ARRAY;
}
return new ModelAccessHelper(ProjectHelper.getModelAccess(project)).runReadAction(new Computable<PsiClass[]>() {
@Override
public PsiClass[] compute() {
CollectConsumer<SNode> consumer = new CollectConsumer<SNode>(new ArrayList<SNode>());
findMPSClasses(qualifiedName, consumer, scope);
return toPsiClasses(consumer.getResult());
}
});
}
@NotNull
@Override
public PsiClass[] getClasses(@NotNull final PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) {
Project project = scope.getProject();
if (project == null) {
return PsiClass.EMPTY_ARRAY;
}
return new ModelAccessHelper(ProjectHelper.getModelAccess(project)).runReadAction(new Computable<PsiClass[]>() {
@Override
public PsiClass[] compute() {
CollectConsumer<SNode> consumer = new CollectConsumer<SNode>(new ArrayList<SNode>());
findMPSClasses(psiPackage, consumer, scope);
return toPsiClasses(consumer.getResult());
}
});
}
/**
* read access required
*/
private void findMPSClasses(PsiPackage psiPackage, Consumer<SNode> consumer, GlobalSearchScope scope) {
final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance();
String key = psiPackage.getQualifiedName();
List<Collection<SNodeDescriptor>> values = fileBasedIndex.getValues(MPSJavaPackageIndex.ID, key, scope);
collectNodes(consumer, values);
}
/**
* read access required
*/
private void findMPSClasses(String qname, Consumer<SNode> consumer, GlobalSearchScope scope) {
// first try changed models
SearchScope mpsSearchScope = new IdeaSearchScope(scope, true);
CollectConsumer<VirtualFile> processedFilesConsumer = new CollectConsumer<VirtualFile>();
for (SModel model : mpsSearchScope.getModels()) {
boolean changed = model instanceof EditableSModel && ((EditableSModel) model).isChanged();
if (!changed) continue;
findInModel(model, qname, processedFilesConsumer, consumer);
}
final Collection<VirtualFile> filesOfChangedModels = processedFilesConsumer.getResult();
// now index
final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance();
GlobalSearchScope truncatedScope = new DelegatingGlobalSearchScope(scope) {
@Override
public boolean contains(VirtualFile file) {
if (filesOfChangedModels.contains(file)) return false;
return super.contains(file);
}
};
List<Collection<SNodeDescriptor>> values = fileBasedIndex.getValues(MPSFQNameJavaClassIndex.ID, qname, truncatedScope);
collectNodes(consumer, values);
}
private void findInModel(SModel model, String qname, Consumer<VirtualFile> processedConsumer, Consumer<SNode> consumer) {
String packageName = model.getModelName();
if (!qname.startsWith(packageName + ".")) return;
// Fix for MPS-19687 Import of Mps class in Java class breaks after any editing of Mps class.
// It would be better to use some single interface like FileSystemBasedDataSource, but its method
// getAffectedFiles() gives us only the folder in case of FolderDataSource, not the actual files
// Should consider changing it to return all _files_ not folders.
DataSource dataSource = model.getSource();
List<IFile> dataSourceFiles = new ArrayList<IFile>();
if (dataSource instanceof FileDataSource) {
dataSourceFiles.add(((FileDataSource) dataSource).getFile());
} else if (dataSource instanceof FolderDataSource) {
FolderDataSource fds = (FolderDataSource) dataSource;
for (String stream : fds.getAvailableStreams()) {
dataSourceFiles.add(fds.getFile(stream));
}
} else if (dataSource instanceof FolderSetDataSource) {
for (IFile file : ((FolderSetDataSource) dataSource).getAffectedFiles()) {
for (IFile child : file.getChildren()) {
if (child.isDirectory()) continue;
dataSourceFiles.add(child);
}
}
}
for (IFile iFile : dataSourceFiles) {
VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(iFile.getPath());
if (vFile != null) {
processedConsumer.consume(vFile);
}
}
FastNodeFinder fastFinder = FastNodeFinderManager.get(model);
List<SNode> classes = fastFinder.getNodes(jetbrains.mps.smodel.SNodeUtil.concept_Classifier, true);
if (classes.isEmpty()) return;
for (SNode claz : classes) {
if (qname.equals(ClassUtil.getClassFQName(claz))) {
consumer.consume(claz);
}
}
}
private void collectNodes(Consumer<SNode> consumer, List<Collection<SNodeDescriptor>> values) {
for (Collection<SNodeDescriptor> value : values) {
for (SNodeDescriptor descriptor : value) {
SNode node = descriptor.getNodeReference().resolve(ProjectHelper.getProjectRepository(myProject));
if (node == null) continue;
consumer.consume(node);
}
}
}
private PsiClass[] toPsiClasses(Iterable<SNode> classes) {
List<PsiClass> result = new ArrayList<PsiClass>();
for (SNode n : classes) {
final PsiElement psi = MPSPsiProvider.getInstance(myProject).getPsi(n);
if (psi instanceof PsiClass) {
result.add((PsiClass) psi);
}
}
return result.isEmpty() ? PsiClass.EMPTY_ARRAY : result.toArray(new PsiClass[result.size()]);
}
}