/* * Copyright 2003-2012 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.refactoring; import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.InputValidatorEx; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.refactoring.rename.RenameHandler; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.idea.core.MPSBundle; import jetbrains.mps.idea.core.MPSDataKeys; import jetbrains.mps.idea.core.psi.impl.MPSPsiProvider; import jetbrains.mps.project.SModuleOperations; import jetbrains.mps.refactoring.Renamer; import jetbrains.mps.smodel.ModuleRepositoryFacade; import jetbrains.mps.smodel.SModelFileTracker; import jetbrains.mps.vfs.IFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.EditableSModel; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.module.SRepository; import javax.lang.model.SourceVersion; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; public class ModelRenameHandler implements RenameHandler { private static final Logger LOG = Logger.getInstance(ModelRenameHandler.class); private static final String CACHES_SUFFIX = ".caches"; @Override public boolean isAvailableOnDataContext(DataContext dataContext) { IFile modelFile = getModelFile(dataContext); PsiElement psiElement = PlatformDataKeys.PSI_ELEMENT.getData(dataContext); if (modelFile == null || modelFile.isDirectory() || psiElement == null) return false; SModel model = SModelFileTracker.getInstance(ProjectHelper.getProjectRepository(psiElement.getProject())).findModel(modelFile); return model instanceof EditableSModel; } @Override public boolean isRenaming(DataContext dataContext) { return isAvailableOnDataContext(dataContext); } @Override public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile, DataContext dataContext) { LOG.assertTrue(false); } @Override public void invoke(@NotNull final Project project, @NotNull PsiElement[] psiElements, DataContext dataContext) { IFile modelFile = getModelFile(dataContext); if (modelFile == null) return; SRepository repository = ProjectHelper.getProjectRepository(project); SModel sModel = SModelFileTracker.getInstance(repository).findModel(modelFile); if (!(sModel instanceof EditableSModel)) return; final EditableSModel editableSModel = (EditableSModel) sModel; final AtomicReference<String> targetFqName = new AtomicReference<>(null); Pair<String, Boolean> result = Messages.showInputDialogWithCheckBox( MPSBundle.message("rename.model.to", editableSModel.getName().getLongName()), MPSBundle.message("rename.model"), MPSBundle.message("update.all.references"), true, true, null, editableSModel.getName().getLongName(), new MyInputValidator() { @Override protected void doRename(String fqName) { targetFqName.set(fqName); } @Override protected SRepository getRepository() { return repository; } }); if (targetFqName.get() != null) { ApplicationManager.getApplication().runWriteAction(() -> deleteGeneratedFiles(editableSModel)); final ModelRenamer renamer = new ModelRenamer(editableSModel, targetFqName.get(), !(result.getSecond())); ProjectHelper.getModelAccess(project).executeCommand(renamer::rename); ProgressManager.getInstance().run(new Task.Modal(project, MPSBundle.message("model.rename.handler.init.progress"), false) { @Override public void run(@NotNull ProgressIndicator indicator) { indicator.pushState(); indicator.setIndeterminate(true); try { ProjectHelper.getModelAccess(project).runWriteAction(() -> renamer.updateReferencesIfNeeded(project)); } finally { indicator.popState(); } } }); //Get MPSPsiModel source file to select in project view. final AtomicReference<VirtualFile> psiModelSourceFile = new AtomicReference<>(); ProjectHelper.getModelAccess(project).runReadAction( () -> psiModelSourceFile.set(MPSPsiProvider.getInstance(project).getPsi(editableSModel.getReference()).getSourceVirtualFile())); //Navigate using SourceFile (pointing to real file) ProjectView.getInstance(project).select(editableSModel, psiModelSourceFile.get(), true); } } private IFile getModelFile(DataContext dataContext) { IFile modelFile = null; Set<IFile> modelFiles = MPSDataKeys.MODEL_FILES.getData(dataContext); if (modelFiles != null && modelFiles.size() == 1) { modelFile = modelFiles.iterator().next(); } return modelFile; } private void deleteGeneratedFiles(SModel sModel) { // TODO: find a way to safely delete generated files. Until then, let's not make a mess if (true) { return; } // TODO for 3.5: check rewritten code and remove if(true) final IFile outputRoot = SModuleOperations.getOutputRoot(sModel); if (outputRoot == null) { return; } outputRoot.delete(); outputRoot.getFileSystem().getFile(outputRoot.toPath() + CACHES_SUFFIX).delete(); } private static abstract class MyInputValidator implements InputValidatorEx { @Override public boolean checkInput(String text) { return text != null && isModelNameValid(text.trim()); } @Override public boolean canClose(String text) { if (text == null) return false; String targetName = text.trim(); if (!isModelNameValid(targetName)) return false; doRename(targetName); return true; } @Override public String getErrorText(String text) { if (text != null) { String[] errorText = new String[1]; if (!isModelNameValid(text.trim(), errorText)) { return errorText[0]; } } return null; } protected abstract void doRename(String fqName); protected abstract SRepository getRepository(); private boolean isModelNameValid(String modelFqName) { return isModelNameValid(modelFqName, new String[1]); } private boolean isModelNameValid(String modelName, String[] errorText) { if (modelName.length() == 0) { errorText[0] = MPSBundle.message("create.new.model.dialog.error.empty.name"); return false; } if (new ModuleRepositoryFacade(getRepository()).getModelByName(modelName) != null) { errorText[0] = MPSBundle.message("create.new.model.dialog.error.model.exists", modelName); return false; } if (modelName.lastIndexOf(".") == modelName.length()) { errorText[0] = MPSBundle.message("create.new.model.dialog.error.empty.short.name"); return false; } if (!(SourceVersion.isName(modelName))) { errorText[0] = MPSBundle.message("create.new.model.dialog.error.invalid.java", modelName); return false; } return true; } } private static class ModelRenamer { private final EditableSModel myModelDescriptor; private final String myNewName; private boolean myLazy; public ModelRenamer(EditableSModel modelDescriptor, String fqName, boolean lazy) { myModelDescriptor = modelDescriptor; myNewName = fqName; myLazy = lazy; } public void rename() { myModelDescriptor.rename(myNewName, true); } public void updateReferencesIfNeeded(Project project) { if (!myLazy) { Renamer.updateModelAndModuleReferences(ProjectHelper.fromIdeaProject(project).getRepository()); } } } }