/* * Copyright 2003-2013 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.usages; import com.intellij.openapi.application.QueryExecutorBase; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.impl.light.LightElement; import com.intellij.psi.search.DelegatingGlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch.SearchParameters; import com.intellij.util.CollectConsumer; import com.intellij.util.Consumer; import com.intellij.util.Processor; import com.intellij.util.indexing.FileBasedIndex; import com.intellij.util.indexing.FileBasedIndex.ValueProcessor; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.idea.core.psi.impl.MPSPsiNode; import jetbrains.mps.idea.core.psi.impl.MPSPsiNodeBase; import jetbrains.mps.idea.core.psi.impl.MPSPsiProvider; import jetbrains.mps.idea.core.refactoring.NodePtr; import jetbrains.mps.idea.core.usages.IdeaSearchScope; import jetbrains.mps.idea.java.index.ForeignIdReferenceIndex; import jetbrains.mps.idea.java.psiStubs.JavaForeignIdBuilder; import jetbrains.mps.smodel.SNodeId.Foreign; import jetbrains.mps.smodel.SNodePointer; import jetbrains.mps.smodel.StaticReference; import jetbrains.mps.util.Pair; import jetbrains.mps.workbench.goTo.index.SNodeDescriptor; import org.jetbrains.annotations.NotNull; 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.model.SNodeId; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.model.SReference; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.module.SearchScope; import java.util.Collection; import java.util.LinkedList; /** * danilla 3/24/13 */ public class IdPrefixSearch extends QueryExecutorBase<PsiReference, SearchParameters> { public IdPrefixSearch() { // flag: requires read action super(true); } @Override public void processQuery(@NotNull SearchParameters queryParameters, @NotNull final Processor<PsiReference> consumer) { if (!(queryParameters.getEffectiveSearchScope() instanceof GlobalSearchScope)) { return; } final GlobalSearchScope scope = (GlobalSearchScope) queryParameters.getEffectiveSearchScope(); final PsiElement target = queryParameters.getElementToSearch(); if (target instanceof MPSPsiNodeBase) return; // Only class names can be prefixes in foreign ids of other nodes if (!(target instanceof PsiClass)) return; final SRepository repository = ProjectHelper.getProjectRepository(scope.getProject()); if (repository == null) { return; } repository.getModelAccess().runReadAction(new Runnable() { @Override public void run() { if (target instanceof LightElement) { // we don't handle them by default return; } final NodePtr nodePtr = JavaForeignIdBuilder.computeNodePtr(target); if (nodePtr == null) return; final SNodeReference mpsTarget = new SNodePointer(nodePtr.getSModelReference(), nodePtr.getNodeId()); // do we have this node? if (mpsTarget.resolve(repository) == null) return; String prefix = nodePtr.getNodeId().toString(); final String prefixToSearch = (prefix.startsWith(Foreign.ID_PREFIX) ? prefix.substring(1) : prefix); final String prefixToSearchWithDot = prefixToSearch + "."; final Project project = target.getProject(); // first look into 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, prefixToSearch, processedFilesConsumer, new Consumer<SReference>() { @Override public void consume(SReference ref) { String role = ref.getRole(); SNode source = ref.getSourceNode(); PsiElement psiNode = MPSPsiProvider.getInstance(project).getPsi(source.getReference()); assert psiNode instanceof MPSPsiNode; consumer.process(new IdPrefixReference(mpsTarget, role, psiNode)); } }); } // now index final Collection<VirtualFile> filesOfChangedModels = processedFilesConsumer.getResult(); GlobalSearchScope truncatedScope = new DelegatingGlobalSearchScope(scope) { @Override public boolean contains(VirtualFile file) { if (filesOfChangedModels.contains(file)) return false; return super.contains(file); } }; ValueProcessor<Collection<Pair<SNodeDescriptor, String>>> sReferenceProcessor = new ValueProcessor<Collection<Pair<SNodeDescriptor, String>>>() { @Override public boolean process(VirtualFile file, Collection<Pair<SNodeDescriptor, String>> refs) { for (Pair<SNodeDescriptor, String> ref : refs) { SNodeReference nodeRef = ref.o1.getNodeReference(); String role = ref.o2; // index got out-of-date on this // unfortunately our indices are not always up-to-date, as we don't index yet-unsaved changes if (nodeRef.resolve(repository) == null) continue; PsiElement psiNode = MPSPsiProvider.getInstance(project).getPsi(nodeRef); // original node came from MPS index, it must be converted to our PSI element assert psiNode instanceof MPSPsiNode; consumer.process(new IdPrefixReference(mpsTarget, role, psiNode)); } return true; } }; FileBasedIndex.getInstance().processValues(ForeignIdReferenceIndex.ID, prefixToSearchWithDot, null, sReferenceProcessor, truncatedScope); } }); } private void findInModel(SModel model, String prefix, Consumer<VirtualFile> processedConsumer, Consumer<SReference> consumer) { for (SNode root : model.getRootNodes()) { findPrefixReferences(prefix, root, consumer); } } private void findPrefixReferences(String prefix, SNode node, Consumer<SReference> consumer) { LinkedList<SNode> queue = new LinkedList<SNode>(); queue.add(node); while (!queue.isEmpty()) { SNode n = queue.pop(); for (SReference ref : n.getReferences()) { processReference(prefix, ref, consumer); } for (SNode child : n.getChildren()) { queue.add(child); } } } private void processReference(String prefix, SReference ref, Consumer<SReference> consumer) { if (!(ref instanceof StaticReference)) return; SNodeId target = ref.getTargetNodeId(); if (!(target instanceof Foreign)) return; String targetNodeIdStr = target.toString(); boolean found = false; int idx = targetNodeIdStr.indexOf(prefix); while (idx >= 0) { if (idx == 0) { found = true; break; } char c = targetNodeIdStr.charAt(idx - 1); if (c == '.' || Character.isLetterOrDigit(c)) { idx = targetNodeIdStr.indexOf(prefix, idx + 1); continue; } found = true; break; } if (found) { consumer.consume(ref); } } }