| /*=============================================================================# |
| # Copyright (c) 2008, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.r.core.refactoring; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.Refactoring; |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| import org.eclipse.ltk.core.refactoring.TextFileChange; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.text.edits.ReplaceEdit; |
| |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| |
| import org.eclipse.statet.internal.r.core.refactoring.Messages; |
| import org.eclipse.statet.ltk.core.LTK; |
| import org.eclipse.statet.ltk.model.core.ElementSet; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| import org.eclipse.statet.ltk.refactoring.core.CommonRefactoringDescriptor; |
| import org.eclipse.statet.ltk.refactoring.core.RefactoringChange; |
| import org.eclipse.statet.ltk.refactoring.core.RefactoringMessages; |
| import org.eclipse.statet.ltk.refactoring.core.SourceUnitChange; |
| import org.eclipse.statet.ltk.refactoring.core.TextChangeCompatibility; |
| import org.eclipse.statet.ltk.refactoring.core.TextChangeManager; |
| import org.eclipse.statet.r.core.RProject; |
| import org.eclipse.statet.r.core.RUtil; |
| import org.eclipse.statet.r.core.model.IRFrame; |
| import org.eclipse.statet.r.core.model.IRFrameInSource; |
| import org.eclipse.statet.r.core.model.IRModelInfo; |
| import org.eclipse.statet.r.core.model.IRModelManager; |
| import org.eclipse.statet.r.core.model.IRSourceUnit; |
| import org.eclipse.statet.r.core.model.IRWorkspaceSourceUnit; |
| import org.eclipse.statet.r.core.model.RElementAccess; |
| import org.eclipse.statet.r.core.model.RElementName; |
| import org.eclipse.statet.r.core.model.RModel; |
| import org.eclipse.statet.r.core.refactoring.RElementSearchProcessor.Mode; |
| import org.eclipse.statet.r.core.rlang.RTerminal; |
| import org.eclipse.statet.r.core.rsource.ast.NodeType; |
| import org.eclipse.statet.r.core.rsource.ast.RAst; |
| import org.eclipse.statet.r.core.rsource.ast.RAstNode; |
| |
| |
| public class RenameInWorkspaceRefactoring extends Refactoring { |
| |
| |
| private static final int FOUND_NONE= 0; |
| private static final int FOUND_READ= 1; |
| private static final int FOUND_WRITE= 2; |
| |
| |
| private class SearchProcessor extends RElementSearchProcessor { |
| |
| |
| TextChangeManager changeManager; |
| |
| |
| public SearchProcessor(final RElementName elementName, |
| final IRSourceUnit sourceUnit, final RElementAccess mainAccess) { |
| super(elementName, sourceUnit, mainAccess, null, WARN_NO_DEFINITION); |
| } |
| |
| |
| @Override |
| public void begin(final SubMonitor m) { |
| this.changeManager= new TextChangeManager(); |
| } |
| |
| @Override |
| public void beginFinalProcessing(final SubMonitor m) { |
| m.subTask(RefactoringMessages.Common_CreateChanges_label); |
| |
| final IRSourceUnit initialSu= getInitialSourceUnit(); |
| final TextFileChange textFileChange= this.changeManager.get(initialSu); |
| if (initialSu.getWorkingContext() == LTK.EDITOR_CONTEXT) { |
| textFileChange.setSaveMode(TextFileChange.LEAVE_DIRTY); |
| } |
| } |
| |
| @Override |
| public void process(final RProject project, final List<ISourceUnit> sus, |
| final SubMonitor m) throws BadLocationException { |
| if (sus != null) { |
| int workRemaining= sus.size(); |
| for (final ISourceUnit su : sus) { |
| m.setWorkRemaining(workRemaining--); |
| final TextFileChange change= this.changeManager.get(su); |
| createChanges(su, change, this.definitionFrameIds, m.newChild(1)); |
| } |
| } |
| } |
| |
| } |
| |
| |
| private final RRefactoringAdapter adapter= new RRefactoringAdapter(); |
| private final ElementSet elementSet; |
| |
| private TextRegion selectionRegion; |
| |
| private final IRWorkspaceSourceUnit sourceUnit; |
| |
| private RAstNode initialSymbolNode; |
| private RElementAccess initialAccess; |
| private SearchProcessor searchProcessor; |
| |
| private String newName; |
| private Change[] changes; |
| |
| |
| /** |
| * Creates a new rename refactoring |
| * |
| * @param su the source unit |
| * @param region (selected) region of an occurrence of the variable |
| */ |
| public RenameInWorkspaceRefactoring(final IRWorkspaceSourceUnit su, final TextRegion region) { |
| this.sourceUnit= su; |
| this.elementSet= new ElementSet(new Object[] { su }); |
| |
| if (region != null && region.getStartOffset() >= 0 && region.getLength() >= 0) { |
| this.selectionRegion= region; |
| } |
| } |
| |
| /** |
| * Creates a new rename refactoring |
| * |
| * @param su the source unit |
| * @param region (selected) region of an occurrence of the variable |
| */ |
| public RenameInWorkspaceRefactoring(final IRWorkspaceSourceUnit su, final RAstNode node) { |
| this.sourceUnit= su; |
| this.elementSet= new ElementSet(new Object[] { su }); |
| |
| if (node.getNodeType() == NodeType.SYMBOL || node.getNodeType() == NodeType.STRING_CONST) { |
| this.initialSymbolNode= node; |
| } |
| } |
| |
| |
| @Override |
| public String getName() { |
| return Messages.RenameInWorkspace_label; |
| } |
| |
| public String getIdentifier() { |
| return RRefactoring.RENAME_IN_WORKSPACE_REFACTORING_ID; |
| } |
| |
| @Override |
| public RefactoringStatus checkInitialConditions(final IProgressMonitor monitor) throws CoreException { |
| final SubMonitor m= SubMonitor.convert(monitor, 6); |
| try { |
| if (this.selectionRegion != null) { |
| this.initialSymbolNode= this.adapter.searchPotentialNameNode(this.sourceUnit, |
| this.selectionRegion, false, m.newChild(4) ); |
| } |
| if (this.initialSymbolNode == null) { |
| return RefactoringStatus.createFatalErrorStatus(Messages.RenameInWorkspace_error_InvalidSelection_message); |
| } |
| if (this.sourceUnit.getResource() == null || this.sourceUnit.getResource().getProject() == null) { |
| return RefactoringStatus.createFatalErrorStatus("The file is not in the workspace"); |
| } |
| final RefactoringStatus result= new RefactoringStatus(); |
| this.adapter.checkInitialToModify(result, this.elementSet); |
| if (result.hasFatalError()) { |
| return result; |
| } |
| |
| checkVariable(result); |
| return result; |
| } |
| finally { |
| m.done(); |
| } |
| } |
| |
| private void checkVariable(final RefactoringStatus result) { |
| this.initialAccess= RElementAccess.getMainElementAccessOfNameNode(this.initialSymbolNode); |
| final RElementAccess subAccess= RElementAccess.getElementAccessOfNameNode(this.initialSymbolNode); |
| final RElementName fullName= (this.initialAccess != null && subAccess != null) ? |
| RElementName.create(this.initialAccess, subAccess.getNextSegment(), false) : null; |
| this.searchProcessor= new SearchProcessor(fullName, this.sourceUnit, this.initialAccess); |
| if (this.searchProcessor.getStatus().getSeverity() >= IStatus.ERROR) { |
| result.merge(RefactoringStatus.create(this.searchProcessor.getStatus())); |
| return; |
| } |
| |
| this.newName= fullName.getDisplayName(); |
| } |
| |
| public String getCurrentName() { |
| return this.searchProcessor.getElementName().getDisplayName(); |
| } |
| |
| public String getNewName() { |
| return this.newName; |
| } |
| |
| public RefactoringStatus checkNewName(final String name) { |
| final String message= this.adapter.validateIdentifier(name, "The variable name"); |
| if (message != null) { |
| return RefactoringStatus.createFatalErrorStatus(message); |
| } |
| return new RefactoringStatus(); |
| } |
| |
| public void setNewName(final String name) { |
| this.newName= name; |
| } |
| |
| public List<Mode> getAvailableModes() { |
| return this.searchProcessor.getAvailableModes(); |
| } |
| |
| public Mode getMode() { |
| return this.searchProcessor.getMode(); |
| } |
| |
| public void setMode(final Mode mode) { |
| this.searchProcessor.setMode(mode); |
| } |
| |
| @Override |
| public RefactoringStatus checkFinalConditions(final IProgressMonitor monitor) throws CoreException { |
| final SubMonitor m= SubMonitor.convert(monitor, RefactoringMessages.Common_FinalCheck_label, 20 + 3); |
| try { |
| final RefactoringStatus status= checkNewName(this.newName); |
| if (status.getSeverity() >= IStatus.ERROR) { |
| return status; |
| } |
| |
| if (getMode() == Mode.LOCAL_FRAME) { |
| this.changes= createLocalChanges(m.newChild(20)); |
| |
| this.adapter.checkFinalToModify(status, this.elementSet, m.newChild(2)); |
| return status; |
| } |
| else { |
| this.searchProcessor.run(m.newChild(20)); |
| if (this.searchProcessor.getStatus() != Status.OK_STATUS) { |
| status.merge(RefactoringStatus.create(this.searchProcessor.getStatus())); |
| } |
| final TextChangeManager changeManager= this.searchProcessor.changeManager; |
| this.changes= changeManager.getAllChanges(); |
| m.worked(1); |
| |
| final ElementSet elements= new ElementSet(changeManager.getAllSourceUnits()); |
| this.adapter.checkFinalToModify(status, elements, m.newChild(2)); |
| return status; |
| } |
| } |
| finally { |
| m.done(); |
| } |
| } |
| |
| @Override |
| public Change createChange(final IProgressMonitor monitor) throws CoreException { |
| final SubMonitor m= SubMonitor.convert(monitor, RefactoringMessages.Common_CreateChanges_label, 3); |
| try { |
| final Map<String, String> arguments= new HashMap<>(); |
| final String description= NLS.bind(Messages.RenameInWorkspace_Descriptor_description, |
| RUtil.formatVarName(getNewName()) ); |
| final IProject resource= this.elementSet.getSingleProject(); |
| final String project= (resource != null) ? resource.getName() : null; |
| final String source= (project != null) ? NLS.bind(RefactoringMessages.Common_Source_Project_label, project) : RefactoringMessages.Common_Source_Workspace_label; |
| final int flags= 0; |
| final String comment= ""; //$NON-NLS-1$ |
| final CommonRefactoringDescriptor descriptor= new CommonRefactoringDescriptor( |
| getIdentifier(), project, description, comment, arguments, flags); |
| |
| return new RefactoringChange(descriptor, |
| Messages.RenameInWorkspace_label, |
| this.changes); |
| } |
| finally { |
| m.done(); |
| } |
| } |
| |
| |
| private Change[] createLocalChanges(final SubMonitor m) { |
| m.setWorkRemaining(2 + 4); |
| |
| final TextFileChange change= new SourceUnitChange(this.sourceUnit); |
| if (this.sourceUnit.getWorkingContext() == LTK.EDITOR_CONTEXT) { |
| change.setSaveMode(TextFileChange.LEAVE_DIRTY); |
| } |
| |
| this.sourceUnit.connect(m.newChild(1)); |
| try { |
| final ImList<? extends RElementAccess> accessList= this.initialAccess.getAllInUnit(false); |
| |
| final String unquoted= RRefactoringAdapter.getUnquotedIdentifier(this.newName); |
| final String quoted= RRefactoringAdapter.getQuotedIdentifier(this.newName); |
| final boolean isQuoted= (this.newName.charAt(0) == '`'); |
| |
| for (final RElementAccess aAccess : accessList) { |
| final RAstNode nameNode= aAccess.getNameNode(); |
| final String text= (isQuoted && nameNode.getNodeType() == NodeType.SYMBOL && nameNode.getOperator(0) == RTerminal.SYMBOL) ? |
| this.newName : unquoted; |
| final TextRegion nameRegion= RAst.getElementNameRegion(nameNode); |
| TextChangeCompatibility.addTextEdit(change, Messages.RenameInWorkspace_Changes_ReplaceOccurrence_name, |
| new ReplaceEdit(nameRegion.getStartOffset(), nameRegion.getLength(), text)); |
| } |
| m.worked(4); |
| |
| return new Change[] { change }; |
| } |
| finally { |
| this.sourceUnit.disconnect(m.newChild(1)); |
| } |
| } |
| |
| |
| private int createChanges(final ISourceUnit su, final TextFileChange change, |
| final Set<String> definitionFrameIds, final SubMonitor m) throws BadLocationException { |
| m.setWorkRemaining(3 + 6); |
| |
| int found= FOUND_NONE; |
| su.connect(m.newChild(1)); |
| try { |
| final IRModelInfo modelInfo= (IRModelInfo) su.getModelInfo(RModel.R_TYPE_ID, |
| IRModelManager.MODEL_FILE, m.newChild(1) ); |
| |
| final String unquoted= RRefactoringAdapter.getUnquotedIdentifier(this.newName); |
| final String quoted= RRefactoringAdapter.getQuotedIdentifier(this.newName); |
| final boolean isQuoted= (this.newName.charAt(0) == '`'); |
| |
| final List<List<? extends RElementAccess>> allFrameAccess= new ArrayList<>(); |
| for (final String frameId : definitionFrameIds) { |
| final IRFrame frame; |
| if (frameId == null) { |
| frame= modelInfo.getTopFrame(); |
| } |
| else { |
| frame= modelInfo.getReferencedFrames().get(frameId); |
| } |
| if (frame instanceof IRFrameInSource) { |
| final List<? extends RElementAccess> allAccess= ((IRFrameInSource) frame).getAllAccessOf( |
| this.searchProcessor.mainName.getSegmentName(), false ); |
| if (allAccess != null && allAccess.size() > 0) { |
| allFrameAccess.add(allAccess); |
| } |
| } |
| } |
| m.worked(2); |
| |
| for (final List<? extends RElementAccess> allAccess : allFrameAccess) { |
| for (final RElementAccess access : allAccess) { |
| found|= (access.isWriteAccess() && access.getNextSegment() == null) ? FOUND_WRITE : FOUND_READ; |
| final RAstNode nameNode= access.getNameNode(); |
| final String text= (isQuoted && nameNode.getNodeType() == NodeType.SYMBOL && nameNode.getOperator(0) == RTerminal.SYMBOL) ? |
| this.newName : unquoted; |
| final TextRegion nameRegion= RAst.getElementNameRegion(nameNode); |
| TextChangeCompatibility.addTextEdit(change, Messages.RenameInWorkspace_Changes_ReplaceOccurrence_name, |
| new ReplaceEdit(nameRegion.getStartOffset(), nameRegion.getLength(), text)); |
| } |
| } |
| m.worked(4); |
| |
| return found; |
| } |
| finally { |
| su.disconnect(m.newChild(1)); |
| } |
| } |
| |
| } |