/* * 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.workbench.findusages; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.vfs.VirtualFile; import jetbrains.mps.extapi.persistence.FileDataSource; import jetbrains.mps.findUsages.FindUsagesUtil; import jetbrains.mps.findUsages.NodeUsageFinder; import jetbrains.mps.ide.vfs.VirtualFileUtils; import jetbrains.mps.persistence.FilePerRootDataSource; import jetbrains.mps.persistence.PersistenceRegistry; import jetbrains.mps.smodel.DefaultSModelDescriptor; import jetbrains.mps.smodel.adapter.ids.MetaIdHelper; import jetbrains.mps.util.FileUtil; import jetbrains.mps.util.containers.ManyToManyMap; import jetbrains.mps.util.containers.MultiMap; import jetbrains.mps.util.containers.SetBasedMultiMap; import jetbrains.mps.vfs.IFile; import jetbrains.mps.workbench.findusages.UsageEntry.ConceptInstance; import jetbrains.mps.workbench.findusages.UsageEntry.ModelUse; import jetbrains.mps.workbench.findusages.UsageEntry.NodeUse; import org.apache.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.model.EditableSModel; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SReference; import org.jetbrains.mps.openapi.persistence.DataSource; import org.jetbrains.mps.openapi.persistence.FindUsagesParticipant; import org.jetbrains.mps.openapi.util.Consumer; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; public class MPSModelsFastFindSupport implements ApplicationComponent, FindUsagesParticipant { @Override public void initComponent() { PersistenceRegistry.getInstance().addFindUsagesParticipant(this); } @Override public void disposeComponent() { PersistenceRegistry.getInstance().removeFindUsagesParticipant(this); } @Override @NotNull public String getComponentName() { return MPSModelsFastFindSupport.class.getSimpleName(); } @Override public void findUsages(Collection<SModel> scope, Set<SNode> nodes, Consumer<SReference> consumer, Consumer<SModel> processedConsumer) { MultiMap<SModel, SNode> candidates = findCandidates(scope, nodes, processedConsumer, key -> new NodeUse(key.getNodeId())); for (Entry<SModel, Collection<SNode>> candidate : candidates.entrySet()) { new NodeUsageFinder(candidate.getValue(), consumer).collectUsages(candidate.getKey()); } } @Override public void findInstances(Collection<SModel> scope, Set<SAbstractConcept> concepts, Consumer<SNode> consumer, Consumer<SModel> processedConsumer) { MultiMap<SModel, SAbstractConcept> candidates = findCandidates(scope, concepts, processedConsumer, key -> new ConceptInstance(MetaIdHelper.getConcept(key))); for (Entry<SModel, Collection<SAbstractConcept>> candidate : candidates.entrySet()) { FindUsagesUtil.collectInstances(candidate.getKey(), candidate.getValue(), consumer); } } @Override public void findModelUsages(Collection<SModel> scope, Set<SModelReference> modelReferences, Consumer<SModel> consumer, Consumer<SModel> processedConsumer) { MultiMap<SModel, SModelReference> candidates = findCandidates(scope, modelReferences, processedConsumer, key -> new ModelUse(key)); for (Entry<SModel, Collection<SModelReference>> candidate : candidates.entrySet()) { if (FindUsagesUtil.hasModelUsages(candidate.getKey(), candidate.getValue())) { consumer.consume(candidate.getKey()); } } } private <T> MultiMap<SModel, T> findCandidates(Collection<SModel> models, Set<T> elems, Consumer<SModel> processedModels, Function<T, UsageEntry> id) { // get all files in scope final ManyToManyMap<SModel, VirtualFile> scopeFiles = new ManyToManyMap<SModel, VirtualFile>(); for (final SModel sm : models) { if (sm instanceof EditableSModel && ((EditableSModel) sm).isChanged()) { continue; } DataSource source = sm.getSource(); // these are data sources this participant knows about if (!(source instanceof FileDataSource || source instanceof FilePerRootDataSource)) { continue; } /* This is a tmp fix for MPS-24151. See the issue to learn about the correct fix */ if (!(sm instanceof DefaultSModelDescriptor)) { continue; } Collection<IFile> modelFiles = getDataSourceFiles(source); for (IFile modelFile : modelFiles) { String ext = FileUtil.getExtension(modelFile.getName()); if (ext == null || modelFile.isDirectory()) { continue; } VirtualFile vf = VirtualFileUtils.getOrCreateVirtualFile(modelFile); if (vf == null) { LogManager.getLogger(MPSModelsFastFindSupport.class).warn( String.format("Model %s: virtual file not found for model file. Model file: %s", sm.getName(), modelFile.getPath())); continue; } processedModels.consume(sm); scopeFiles.addLink(sm, vf); } } // filter files with usages ConcreteFilesGlobalSearchScope allFiles = new ConcreteFilesGlobalSearchScope(scopeFiles.getSecond()); // process indexes MultiMap<SModel, T> result = new SetBasedMultiMap<SModel, T>(); for (T elem : elems) { UsageEntry entry = id.apply(elem); Collection<VirtualFile> matchingFiles; try { matchingFiles = MPSModelsIndexer.getContainingFiles(entry, allFiles); } catch (ProcessCanceledException ce) { matchingFiles = Collections.emptyList(); } // back-transform for (VirtualFile file : matchingFiles) { for (SModel m : scopeFiles.getBySecond(file)) { result.putValue(m, elem); } } } return result; } private Collection<IFile> getDataSourceFiles(DataSource ds) { assert ds instanceof FileDataSource || ds instanceof FilePerRootDataSource; if (ds instanceof FileDataSource) { return Collections.singletonList(((FileDataSource) ds).getFile()); } else { FilePerRootDataSource fds = (FilePerRootDataSource) ds; Set<IFile> files = new HashSet<>(); for (String streamName : fds.getAvailableStreams()) { IFile file = fds.getFile(streamName); if (fds.isIncluded(file)) { files.add(file); } } return files; } } }