/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.engine.services.internal.refactoring;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.InterpolationExpression;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationList;
import com.google.dart.engine.ast.VariableDeclarationStatement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.LocalVariableElement;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.engine.search.MatchKind;
import com.google.dart.engine.search.SearchMatch;
import com.google.dart.engine.services.assist.AssistContext;
import com.google.dart.engine.services.change.Change;
import com.google.dart.engine.services.change.Edit;
import com.google.dart.engine.services.change.SourceChange;
import com.google.dart.engine.services.internal.correction.CorrectionUtils;
import com.google.dart.engine.services.refactoring.InlineLocalRefactoring;
import com.google.dart.engine.services.refactoring.ProgressMonitor;
import com.google.dart.engine.services.status.RefactoringStatus;
import com.google.dart.engine.services.status.RefactoringStatusContext;
import com.google.dart.engine.utilities.source.SourceRange;
import static com.google.dart.engine.services.internal.correction.CorrectionUtils.getExpressionParentPrecedence;
import static com.google.dart.engine.services.internal.correction.CorrectionUtils.getExpressionPrecedence;
import java.text.MessageFormat;
import java.util.List;
/**
* Implementation of {@link InlineLocalRefactoring}.
*/
public class InlineLocalRefactoringImpl extends RefactoringImpl implements InlineLocalRefactoring {
private final AssistContext context;
private final CompilationUnit unitNode;
private final CorrectionUtils utils;
private VariableDeclaration variableNode;
private LocalVariableElement variableElement;
private List<SearchMatch> references;
public InlineLocalRefactoringImpl(AssistContext context) throws Exception {
this.context = context;
this.unitNode = context.getCompilationUnit();
this.utils = new CorrectionUtils(unitNode);
}
@Override
public RefactoringStatus checkFinalConditions(ProgressMonitor pm) throws Exception {
return new RefactoringStatus();
}
@Override
public RefactoringStatus checkInitialConditions(ProgressMonitor pm) throws Exception {
pm = checkProgressMonitor(pm);
pm.beginTask("Checking initial conditions", 5);
try {
RefactoringStatus result = new RefactoringStatus();
// prepare variable
variableElement = null;
{
AstNode coveringNode = context.getCoveringNode();
if (coveringNode instanceof SimpleIdentifier) {
SimpleIdentifier coveringIdentifier = (SimpleIdentifier) coveringNode;
Element element = coveringIdentifier.getBestElement();
if (element instanceof LocalVariableElement) {
variableElement = (LocalVariableElement) element;
AstNode variableElementNode = utils.findNode(variableElement.getNameOffset());
variableNode = variableElementNode != null
? variableElementNode.getAncestor(VariableDeclaration.class) : null;
}
}
}
if (variableNode == null) {
return RefactoringStatus.createFatalErrorStatus("Local variable declaration or reference must be selected to activate this refactoring.");
}
pm.worked(1);
// should be normal variable declaration statement
if (!(variableNode.getParent() instanceof VariableDeclarationList)
|| !(variableNode.getParent().getParent() instanceof VariableDeclarationStatement)
|| !(variableNode.getParent().getParent().getParent() instanceof Block)) {
return RefactoringStatus.createFatalErrorStatus("Local variable declared in statement should be selected to activate this refactoring.");
}
pm.worked(1);
// should have initializer at declaration
if (variableNode.getInitializer() == null) {
String message = MessageFormat.format(
"Local variable ''{0}'' is not initialized at declaration.",
variableElement.getDisplayName());
return RefactoringStatus.createFatalErrorStatus(message, new RefactoringStatusContext(
variableNode));
}
pm.worked(1);
// should not have assignments
references = context.getSearchEngine().searchReferences(variableElement, null, null);
for (SearchMatch reference : references) {
if (reference.getKind() != MatchKind.VARIABLE_READ) {
String message = MessageFormat.format(
"Local variable ''{0}'' is assigned more than once.",
variableElement.getDisplayName());
return RefactoringStatus.createFatalErrorStatus(
message,
RefactoringStatusContext.create(reference));
}
}
// done
return result;
} finally {
pm.done();
}
}
@Override
public Change createChange(ProgressMonitor pm) throws Exception {
pm = checkProgressMonitor(pm);
SourceChange change = new SourceChange(getRefactoringName(), context.getSource());
// remove declaration
{
Statement declarationStatement = variableNode.getAncestor(VariableDeclarationStatement.class);
change.addEdit(new Edit(utils.getLinesRange(declarationStatement), ""));
}
// prepare initializer
Expression initializer = variableNode.getInitializer();
String initializerSource = utils.getText(initializer);
int initializerPrecedence = getExpressionPrecedence(initializer);
// replace references
for (SearchMatch reference : references) {
SourceRange range = reference.getSourceRange();
String sourceForReference = getSourceForReference(
range,
initializerSource,
initializerPrecedence);
change.addEdit(new Edit(range, sourceForReference));
}
return change;
}
@Override
public String getRefactoringName() {
return "Inline Local Variable";
}
@Override
public int getReferenceCount() {
return references.size();
}
@Override
public String getVariableName() {
return variableElement.getDisplayName();
}
/**
* @return the source which should be used to replace reference with given {@link SourceRange}.
*/
private String getSourceForReference(SourceRange range, String source, int precedence) {
int offset = range.getOffset();
AstNode node = utils.findNode(offset);
AstNode parent = node.getParent();
if (isIdentifierStringInterpolation(parent)) {
return "{" + source + "}";
}
if (precedence < getExpressionParentPrecedence(node)) {
return "(" + source + ")";
}
return source;
}
/**
* @return <code>true</code> if the given node is a string interpolation in form
* <code>$name</code>.
*/
private boolean isIdentifierStringInterpolation(AstNode parent) {
if (parent instanceof InterpolationExpression) {
InterpolationExpression element = (InterpolationExpression) parent;
return element.getBeginToken().getType() == TokenType.STRING_INTERPOLATION_IDENTIFIER;
}
return false;
}
}