/* * 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.core.facet; import com.intellij.facet.FacetManager; import com.intellij.facet.ModifiableFacetModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.ModuleAdapter; import com.intellij.openapi.project.Project; import com.intellij.util.Function; import jetbrains.mps.ide.findusages.model.scopes.ProjectScope; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.progress.EmptyProgressMonitor; import jetbrains.mps.project.Solution; import jetbrains.mps.smodel.ModelImports; import jetbrains.mps.smodel.StaticReference; 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.SModelId; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeUtil; import org.jetbrains.mps.openapi.model.SReference; import org.jetbrains.mps.openapi.module.FindUsagesFacade; import org.jetbrains.mps.openapi.module.ModelAccess; import org.jetbrains.mps.openapi.module.SearchScope; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Created by danilla on 6/15/15. */ public class ModuleRenameHandler extends ModuleAdapter { @Override public void modulesRenamed(@NotNull final Project project, @NotNull List<Module> modules, @NotNull Function<Module, String> oldNameProvider) { for (final Module module : modules) { final FacetManager facetManager = FacetManager.getInstance(module); final MPSFacet facet = facetManager.getFacetByType(MPSFacetType.ID); if (facet == null) { continue; } ModelAccess modelAccess = ProjectHelper.getModelAccess(project); if (modelAccess == null) { return; } modelAccess.executeCommand(() -> { Set<SModelReference> renamedModelRefs = new HashSet<>(); // collecting all models of the renamed module, their SModelReferences will be changed for (SModel model : facet.getSolution().getModels()) { renamedModelRefs.add(model.getReference()); } // finding usages before we modify the solution SearchScope projectScope = new ProjectScope(ProjectHelper.fromIdeaProject(project)); Set<SModel> modelUsages = FindUsagesFacade.getInstance().findModelUsages(projectScope, renamedModelRefs, new EmptyProgressMonitor()); // now handling the case when there are usages within the renamed module itself // if we don't handle it, we'll end up patching refs in models which are already disposed, // because we patch after solution reload Iterator<SModel> usagesIterator = modelUsages.iterator(); // remembering ids of such in-module usages Set<SModelId> ids = new HashSet<>(); while (usagesIterator.hasNext()) { SModel usage = usagesIterator.next(); if (usage.getModule() == facet.getSolution() && usage instanceof EditableSModel) { // it's in the same module and it's not derived (it's editable) ids.add(usage.getModelId()); // don't want _this_instance_ of SModel in usages, as it will be disposed after we reload the module usagesIterator.remove(); } } // resetting facet => reloading solution final Solution newSolution = resetFacet(facet); // adding to usages re-created in-modules models: those models which were found as usages, // but which have been re-created by now as a result of solution reload for (SModelId id: ids) { modelUsages.add(newSolution.getModel(id)); } // actually fixing usages renameUsages(renamedModelRefs, modelUsages, mRef -> { String modelName = mRef.getModelName(); for (SModel m: newSolution.getModels()) { if (modelName.equals(m.getName().getValue())) { return m.getReference(); } } assert false : "Cannot recover model usages after idea module rename"; return null; }); }); } } private Solution resetFacet(MPSFacet facet) { Module module = facet.getModule(); FacetManager facetManager = FacetManager.getInstance(module); ModifiableFacetModel mm = facetManager.createModifiableModel(); mm.removeFacet(facet); mm.commit(); mm = facetManager.createModifiableModel(); facet.getConfiguration().getBean().setIdByModuleName(module.getName()); facet.getConfiguration().getBean().getSolutionDescriptor().setNamespace(module.getName()); final MPSFacet newFacet = new MPSFacet(new MPSFacetType(), module, MPSFacetConstants.MPS_FACET_NAME, facet.getConfiguration(), null); mm.addFacet(newFacet); mm.commit(); return newFacet.getSolution(); } // todo reuse from PackageRenameListener private void renameUsages(Set<SModelReference> renamedModels, Set<SModel> modelUsages, Function<SModelReference, SModelReference> renameFunc) { for (SModel model : modelUsages) { ModelImports modelImports = new ModelImports(model); Map<SModelReference, SModelReference> changes = new HashMap<>(); for (SModelReference modelReference : modelImports.getImportedModels()) { if (!renamedModels.contains(modelReference)) { continue; } SModelReference newModelRef = renameFunc.fun(modelReference); modelImports.removeModelImport(modelReference); modelImports.addModelImport(newModelRef); changes.put(modelReference, newModelRef); } updateSReferences(model, changes); } } private void updateSReferences(SModel model, Map<SModelReference, SModelReference> modelRefChange) { for (SNode node : SNodeUtil.getDescendants(model)) { for (SReference ref : node.getReferences()) { if (!(ref instanceof StaticReference)) { continue; } SModelReference modelRef = ref.getTargetSModelReference(); if (modelRefChange.containsKey(modelRef)) { ((StaticReference) ref).setTargetSModelReference(modelRefChange.get(modelRef)); } } } } }