blob: 4989b17880b2875cead2587268428c4df3f6a029 [file] [log] [blame]
/*=============================================================================#
# 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));
}
}
}