/*******************************************************************************
* Copyright (c) 2007, 2015 Zend Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Zend Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.php.refactoring.core.extract.variable;
import java.util.*;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.SourceRange;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ltk.core.refactoring.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.php.core.ast.nodes.*;
import org.eclipse.php.internal.core.ast.locator.PHPElementConciliator;
import org.eclipse.php.internal.core.ast.rewrite.ASTRewrite;
import org.eclipse.php.internal.core.ast.rewrite.ListRewrite;
import org.eclipse.php.internal.core.corext.dom.NodeFinder;
import org.eclipse.php.internal.ui.corext.dom.fragments.ASTFragmentFactory;
import org.eclipse.php.internal.ui.corext.dom.fragments.AssociativeInfixExpressionFragment;
import org.eclipse.php.internal.ui.corext.dom.fragments.IASTFragment;
import org.eclipse.php.internal.ui.corext.dom.fragments.IExpressionFragment;
import org.eclipse.php.refactoring.core.PhpRefactoringCoreMessages;
import org.eclipse.php.refactoring.core.RefactoringPlugin;
import org.eclipse.php.refactoring.core.SourceModuleSourceContext;
import org.eclipse.php.refactoring.core.changes.ProgramDocumentChange;
import org.eclipse.php.refactoring.core.utils.ASTUtils;
import org.eclipse.php.refactoring.core.utils.RefactoringUtility;
import org.eclipse.php.refactoring.core.visitor.ScopeSyntaxErrorsVisitor;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
public class ExtractVariableRefactoring extends Refactoring {
private static final String CHANGE_DESCRIPTION = PhpRefactoringCoreMessages
.getString("ExtractVariableRefactoring.4"); //$NON-NLS-1$
private ISourceModule sourceModule = null;
private IDocument document = null;
private IExpressionFragment fSelectedExpression;
private int selectionStartOffset;
private int selectionLength;
private Program astRoot;
private boolean fReplaceAllOccurrences;
private String newVariableName = null;
private String[] fGuessedTempNames;
private ASTNode enclosingBodyNode;
private DocumentChange textFileChange = null;
private IASTFragment[] allMatchingFragments = null;
private IASTFragment[] validMatchingFragments = null;
private RefactoringStatus matchingFragmentsStatus;
private boolean createVariableDeclaration = false;
/**
* The root change for all changes
*/
protected CompositeChange rootChange;
private ASTRewrite fRewriter;
private AST fAst;
public ExtractVariableRefactoring(ISourceModule source, IDocument document, int offset, int length)
throws CoreException {
this.sourceModule = source;
this.document = document;
this.selectionStartOffset = offset;
// for the case of place a cursor under an expression with no text
// selection
if (length == 0) {
recalculateLength(source, offset);
} else {
this.selectionLength = length;
}
fReplaceAllOccurrences = true; // default
}
private void recalculateLength(ISourceModule source, int offset) throws CoreException {
Program program = null;
try {
program = ASTUtils.createProgramFromSource(source);
} catch (Exception e) {
throw new CoreException(new Status(IStatus.ERROR, RefactoringPlugin.PLUGIN_ID, "Unexpected Error", e)); //$NON-NLS-1$
}
ASTNode selectedNode = NodeFinder.perform(program, offset, 0);
ASTNode parent = selectedNode.getParent();
// for the php 5.3 with name space case.
if (parent instanceof NamespaceName) {
parent = parent.getParent();
}
selectionStartOffset = parent.getStart();
selectionLength = parent.getLength();
}
/**
* Sets the new variable name (given by the user)
*
* @param newVariableName
*/
public void setNewVariableName(String newVariableName) {
this.newVariableName = newVariableName;
}
/**
* Sets the value for replace all occurrences (given by the user)
*
* @param replaceAllOccurrences
*/
public void setReplaceAllOccurrences(boolean replaceAllOccurrences) {
this.fReplaceAllOccurrences = replaceAllOccurrences;
}
public boolean getReplaceAllOccurrences() {
return fReplaceAllOccurrences;
}
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
try {
pm.beginTask("", 8); //$NON-NLS-1$
// check if the file is in sync
RefactoringStatus status = RefactoringUtility
.validateModifiesFiles(new IResource[] { sourceModule.getResource() }, getValidationContext());
if (status.hasFatalError())
return status;
try {
astRoot = ASTUtils.createProgramFromSource(sourceModule);
} catch (Exception e) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.0")); //$NON-NLS-1$
}
status.merge(checkSelection(status, new SubProgressMonitor(pm, 3)));
if (!status.hasFatalError() && isLiteralNodeSelected())
fReplaceAllOccurrences = false;
return status;
} finally {
pm.done();
}
}
private boolean isLiteralNodeSelected() {
IExpressionFragment fragment = getSelectedExpression();
if (fragment == null)
return false;
Expression expression = fragment.getAssociatedExpression();
if (expression == null)
return false;
return (expression.getType() == ASTNode.SCALAR);
}
/**
* Gets the current selection
*
* @param astRoot
* @return
*/
private RefactoringStatus checkSelection(RefactoringStatus status, IProgressMonitor pm) {
try {
pm.beginTask("", 8); //$NON-NLS-1$
IExpressionFragment selectedExpression = getSelectedExpression();
if (selectedExpression == null) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.2")); //$NON-NLS-1$
}
pm.worked(1);
enclosingBodyNode = getEnclosingBodyNode();
if (enclosingBodyNode == null)
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.3")); //$NON-NLS-1$
pm.worked(1);
if (scopeHasSyntaxErrors(enclosingBodyNode)) {
return RefactoringStatus
.createFatalErrorStatus("Unable to activate refactoring. Please fix syntax errors"); //$NON-NLS-1$
}
pm.worked(1);
Expression expression = getSelectedExpression().getAssociatedExpression();
status.merge(canExtract(expression));
pm.worked(1);
return status;
} finally {
pm.done();
}
}
/**
* Remove from the matching fragments, the invalid ones and look for non
* fatal errors/warnings
*
* @param recalculate
* @return
*/
@SuppressWarnings("restriction")
private IASTFragment[] retainOnlyReplacableMatches(boolean recalculate) {
// if (validMatchingFragments != null && !recalculate)
// return validMatchingFragments;
matchingFragmentsStatus = new RefactoringStatus();
IASTFragment[] allMatches = getMatchingFragments(recalculate);
matchingFragmentsStatus.merge(checkSemanticProblems(allMatches));
List<IASTFragment> result = new ArrayList<IASTFragment>(allMatches.length);
for (int i = 0; i < allMatches.length; i++) {
Expression associatedExpression = getExpressionFromFragment(allMatches[i]).getAssociatedExpression();
RefactoringStatus status = canExtract(associatedExpression);
// if the match has a fatal error, it is not added to the final
// array
if (!status.hasFatalError()) {
result.add(allMatches[i]);
}
}
validMatchingFragments = result.toArray(new IASTFragment[result.size()]);
matchingFragmentsStatus.merge(checkMatchingExpressions(validMatchingFragments));
return validMatchingFragments;
}
/**
* Look for semantic problems in the matching fragments
*
* @param allMatches
* @return RefactoringStatus
*/
private RefactoringStatus checkSemanticProblems(IASTFragment[] allMatches) {
boolean firstRighHandSideFlag = false;
boolean firstLeftHandSideFlag = false;
boolean hasSemanticProblem = false;
RefactoringStatus status = new RefactoringStatus();
// look for semantic problems.
// The user wants to extract $a. $a is assigned to variables twice,
// and between them it is assigned a new value
// warn from the following case:
// $a = 4;
// $c = $a;
// $a = 5;
// $b = $a;
ISourceRange region = null;
for (int i = 0; i < allMatches.length; i++) {
Expression associatedExpression = getExpressionFromFragment(allMatches[i]).getAssociatedExpression();
if (isExpressionRightHandSide(associatedExpression)) {
if (!firstRighHandSideFlag) {
firstRighHandSideFlag = true;
} else if (firstLeftHandSideFlag) {
hasSemanticProblem = true;
break;
}
} else if (isExpressionLeftHandSide(associatedExpression) && !firstLeftHandSideFlag) {
if (firstRighHandSideFlag) {
firstLeftHandSideFlag = true;
region = new org.eclipse.dltk.corext.SourceRange(associatedExpression.getStart(),
associatedExpression.getLength());
}
}
}
if (hasSemanticProblem) {
status.merge(RefactoringStatus.createWarningStatus(
PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.7"), //$NON-NLS-1$
new SourceModuleSourceContext(sourceModule, region)));
}
return status;
}
/**
* Look for non fatal errors /warning in the matching fragments
*
* @param allMatches
* @return RefactoringStatus
*/
private RefactoringStatus checkMatchingExpressions(IASTFragment[] allMatches) {
RefactoringStatus status = new RefactoringStatus();
IExpressionFragment selectedExpression = getSelectedExpression();
for (int i = 0; i < allMatches.length; i++) {
Expression associatedExpression = getExpressionFromFragment(allMatches[i]).getAssociatedExpression();
boolean matchAppearsBeforeVariableDeclaration = createVariableDeclaration
&& (associatedExpression.getStart() < selectedExpression.getStartPosition());
if (matchAppearsBeforeVariableDeclaration) {
status.merge(RefactoringStatus
.createErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.8"))); //$NON-NLS-1$
}
}
return status;
}
@SuppressWarnings("restriction")
private RefactoringStatus canExtract(Expression expression) {
if (expression.getType() == ASTNode.SCALAR) {
Scalar scalar = (Scalar) expression;
if (scalar.getScalarType() == Scalar.TYPE_STRING && scalar.getStringValue().equalsIgnoreCase("null")) { //$NON-NLS-1$
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.5")); //$NON-NLS-1$
}
}
if (isDispatch(expression)) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.9")); //$NON-NLS-1$
}
if (isFunctionName(expression)) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.9")); //$NON-NLS-1$
}
if (isClassName(expression)) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.9")); //$NON-NLS-1$
}
if (isExpressionLeftHandSide(expression)) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.10")); //$NON-NLS-1$
}
if (isUsedInForInitializerOrUpdaterOrIncrementor(expression))
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.11")); //$NON-NLS-1$
if (assignmentInStaticStatement(expression)) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.12")); //$NON-NLS-1$
}
if (expressionOfCatchVariable(expression)) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.13")); //$NON-NLS-1$
}
if (expression.isStaticScalar()) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.14")); //$NON-NLS-1$
}
if (expression.getType() == ASTNode.FORMAL_PARAMETER
|| (expression.getParent() != null && expression.getParent().getType() == ASTNode.FORMAL_PARAMETER)) {
return RefactoringStatus
.createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.15")); //$NON-NLS-1$
}
return new RefactoringStatus();
}
private boolean isClassName(Expression selectedExpression) {
ASTNode parent = selectedExpression.getParent();
if (parent == null) {
return false;
}
return parent instanceof TypeDeclaration;
}
private boolean isFunctionName(Expression selectedExpression) {
ASTNode parent = selectedExpression.getParent();
if (parent == null) {
return false;
}
return parent instanceof FunctionDeclaration;
}
/**
* Checks the case of try { } catch (Exception $a) { } and the $a is
* selected
*
* @param expression
* @return
*/
private boolean expressionOfCatchVariable(Expression expression) {
final ASTNode parent = expression.getParent();
if (parent.getType() == ASTNode.CATCH_CLAUSE) {
final CatchClause claue = (CatchClause) parent;
if (claue.getVariable() == expression) {
return true;
}
}
return false;
}
/**
* Checks if there is a syntax error in the scope of the selection
*
* @param enclosingBodyNode
* @return true in case the scope of the selection as a syntax error, false
* otherwise
*/
private boolean scopeHasSyntaxErrors(ASTNode enclosingBodyNode) {
ScopeSyntaxErrorsVisitor visitor = new ScopeSyntaxErrorsVisitor();
switch (enclosingBodyNode.getType()) {
case ASTNode.FUNCTION_DECLARATION:
((FunctionDeclaration) enclosingBodyNode).getBody().accept(visitor);
break;
case ASTNode.PROGRAM:
enclosingBodyNode.accept(visitor);
break;
default:
assert (false);
}
return visitor.hasSyntaxError;
}
/**
* Checks if the current selection is in a for statement In this case, the
* only valid area for a selection is the "action"/block of the for
* statement and not the initializer/updater/condition
*
* @param expression
* @return true, in case the selection is in an invalid position in the for
* statement, false otherwise
*/
private boolean isUsedInForInitializerOrUpdaterOrIncrementor(Expression expression) {
boolean isInForStatement = false;
boolean isInBlock = false;
ASTNode parent = expression.getParent();
while (parent != null) {
if (parent instanceof ForStatement) {
isInForStatement = true;
// the selection was in a block - therefore valid
if (isInBlock) {
return false;
}
// this for statement does not have a block (no parenthesis)
// in this case check if the selection is in the for action.
// if this is the case, return false. Otherwise it means the
// selection
// is one of the for initializers/updaters/incrementors
Statement action = ((ForStatement) parent).getBody();
return !(selectionIsInForAction(expression, action));
}
if (parent instanceof Block) {
isInBlock = true;
}
parent = parent.getParent();
}
return isInForStatement;
}
private boolean selectionIsInForAction(Expression expression, Statement action) {
ASTNode parent = expression.getParent();
while (parent != null && !(parent instanceof ForStatement)) {
if (parent == action) {
return true;
}
parent = parent.getParent();
}
return false;
}
/**
* @param expression
* @return whether the selected expression in an assignment in a static
* statement
*/
private boolean assignmentInStaticStatement(Expression expression) {
if (expression.getType() == ASTNode.ASSIGNMENT) {
ASTNode parent = expression.getParent();
if (parent.getType() == ASTNode.STATIC_STATEMENT) {
return true;
}
}
return false;
}
/**
* Checks if the expression is left hand side of assignment expression
*
* @param expression
* @return
*/
private boolean isExpressionLeftHandSide(Expression expression) {
final ASTNode parent = expression.getParent();
if (parent != null && parent.getType() == ASTNode.ASSIGNMENT) {
final Assignment assignment = (Assignment) parent;
return assignment.getLeftHandSide() == expression;
}
return false;
}
/**
* Checks if the expression is right hand side of assignment expression
*
* @param expression
* @return
*/
private boolean isExpressionRightHandSide(Expression expression) {
final ASTNode parent = expression.getParent();
if (parent != null && parent.getType() == ASTNode.ASSIGNMENT) {
final Assignment assignment = (Assignment) parent;
return assignment.getRightHandSide() == expression;
}
return false;
}
/**
*
* @param selectedExpression
* @return Whether the selection is the member of a dispatch
*/
private boolean isDispatch(Expression selectedExpression) {
ASTNode parent = selectedExpression.getParent();
if (parent == null) {
return false;
}
if (parent instanceof Dispatch) {
Dispatch dispatch = (Dispatch) parent;
return selectedExpression == dispatch.getMember();
}
if (parent instanceof StaticDispatch) {
StaticDispatch dispatch = (StaticDispatch) parent;
return selectedExpression == dispatch.getMember();
}
return false;
}
private ASTNode getEnclosingBodyNode() {
ASTNode node = getSelectedExpression().getAssociatedNode();
return node.getEnclosingBodyNode();
}
/**
* @return the fragment for the current selection
*/
private IExpressionFragment getSelectedExpression() {
if (fSelectedExpression != null)
return fSelectedExpression;
IASTFragment selectedFragment;
try {
selectedFragment = ASTFragmentFactory.createFragmentForSourceRange(
new SourceRange(selectionStartOffset, selectionLength), astRoot, document);
} catch (Exception e) {
// on bad region - return null
return null;
}
fSelectedExpression = getExpressionFromFragment(selectedFragment);
return fSelectedExpression;
}
/**
*
* @param selectedFragment
* @return the Expression for the given fragment
*/
private IExpressionFragment getExpressionFromFragment(IASTFragment selectedFragment) {
IExpressionFragment fragment = null;
if (selectedFragment instanceof IExpressionFragment) {
fragment = (IExpressionFragment) selectedFragment;
} else if (selectedFragment != null) {
ASTNode associatedNode = selectedFragment.getAssociatedNode();
if (associatedNode instanceof ExpressionStatement) {
ExpressionStatement exprStatement = (ExpressionStatement) associatedNode;
Expression expression = exprStatement.getExpression();
fragment = (IExpressionFragment) ASTFragmentFactory.createFragmentForFullSubtree(expression);
} else if (associatedNode instanceof Assignment) {
Assignment assignment = (Assignment) associatedNode;
fragment = (IExpressionFragment) ASTFragmentFactory.createFragmentForFullSubtree(assignment);
} else if (associatedNode instanceof ArrayElement) {
ArrayElement arrayElement = (ArrayElement) associatedNode;
if (arrayElement.getKey() == null) {
fragment = (IExpressionFragment) ASTFragmentFactory
.createFragmentForFullSubtree(arrayElement.getValue());
}
}
}
return fragment;
}
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
String[] guessTempNames = guessTempNames();
// in case the new variable name is empty, get the last suggestion in
// the list
if (newVariableName.trim().length() == 0) {
newVariableName = guessTempNames[0];
}
RefactoringStatus status = new RefactoringStatus();
createChange(pm);
status.merge(matchingFragmentsStatus);
status.merge(doesNameAlreadyExist(newVariableName));
return status;
}
private void replaceOccurances() throws CoreException {
IASTFragment[] fragmentsToReplace = retainOnlyReplacableMatches(false);
for (IASTFragment fragment : fragmentsToReplace) {
if (fragment.getAssociatedNode() != getSelectedExpression().getAssociatedNode()) {
ISourceRange range = getReplaceOffsets(fragment);
// replace the existing statement
replaceSelectedExpressionWithVariableDeclaration(getFullVariableName(), range.getOffset(),
range.getLength(), fragment.getAssociatedNode());
}
}
}
/**
* For a given fragment, returns the exact start offset and length for the
* replacement AssociativeInfixExpressionFragment have special replacements
* logic
*
* @param fragment
* @return Source range with the relevant start offset and length
*/
private ISourceRange getReplaceOffsets(IASTFragment fragment) {
ASTNode associatedNode = fragment.getAssociatedNode();
int start = associatedNode.getStart();
int length = associatedNode.getLength();
if (fragment instanceof AssociativeInfixExpressionFragment) {
AssociativeInfixExpressionFragment infixExpressionFragment = (AssociativeInfixExpressionFragment) fragment;
List<Expression> operands = infixExpressionFragment.getOperands();
start = operands.get(0).getStart();
length = operands.get(operands.size() - 1).getEnd() - start;
}
return new SourceRange(start, length);
}
/**
* Handle the creation of changes for extracting a variable for the current
* selection (not including the other occurrences)
*
* @throws CoreException
* @throws BadLocationException
*/
private void extractVariable() throws CoreException, BadLocationException {
IExpressionFragment selectedExpressionFragment = getSelectedExpression();
Expression selectedExpression = selectedExpressionFragment.getAssociatedExpression(); // whole
// expression
// selected
if (shouldReplaceSelectedExpressionWithVariableDeclaration()) {
createVariableDeclaration = true;
// the new text that will replace the selected expression
String replacement = getFullVariableName() + " = " //$NON-NLS-1$
+ getASTNodeValue(getSelectedExpression().getAssociatedNode()) + ";"; //$NON-NLS-1$
replaceSelectedExpressionWithTempDeclaration(replacement);
// addReplaceExpressionWithTemp();
} else {
ISourceRange range = getReplaceOffsets(selectedExpressionFragment);
createAndInsertVariableDeclaration(selectedExpression, range);
addReplaceExpressionWithTemp();
}
}
private void addReplaceExpressionWithTemp() {
IASTFragment[] fragmentsToReplace = retainOnlyReplacableMatches(true);
ASTRewrite rewrite = fRewriter;
HashSet seen = new HashSet();
for (int i = 0; i < fragmentsToReplace.length; i++) {
IASTFragment fragment = fragmentsToReplace[i];
if (!seen.add(fragment))
continue;
Identifier tempName = fRewriter.getAST().newIdentifier(getFullVariableName());
TextEditGroup description = new TextEditGroup("replace "); //$NON-NLS-1$
fragment.replace(rewrite, tempName, description);
// if (fLinkedProposalModel != null)
// fLinkedProposalModel.getPositionGroup(KEY_NAME,
// true).addPosition(rewrite.track(tempName), false);
}
}
private void createAndInsertVariableDeclaration(Expression selectedExpression, ISourceRange range)
throws CoreException, BadLocationException {
if ((!fReplaceAllOccurrences) || (retainOnlyReplacableMatches(true).length <= 1)) {
// insertVariableDeclaration(selectedExpression, selectedExpression,
// shouldWrapStatement, range);
insertAt(selectedExpression);
return;
}
ASTNode[] firstReplaceNodeParents = getParents(getFirstReplacedExpression().getAssociatedNode());
ASTNode[] commonPath = findDeepestCommonSuperNodePathForReplacedNodes();
Assert.isTrue(commonPath.length <= firstReplaceNodeParents.length);
ASTNode deepestCommonParent = firstReplaceNodeParents[commonPath.length - 1];
if (deepestCommonParent instanceof Block || deepestCommonParent instanceof Program) {
insertAt(firstReplaceNodeParents[commonPath.length]);
} else {
insertAt(deepestCommonParent);
}
}
private void insertAt(ASTNode target) throws BadLocationException {
ASTRewrite rewrite = fRewriter;
TextEditGroup groupDescription = new TextEditGroup("dec Variable"); //$NON-NLS-1$
ASTNode parent = target.getParent();
StructuralPropertyDescriptor locationInParent = target.getLocationInParent();
// the new text that will replace the selected expression
String insertionString = getFullVariableName() + " = " //$NON-NLS-1$
+ getASTNodeValue(getSelectedExpression().getAssociatedNode()) + ";"; //$NON-NLS-1$
// + System.getProperty("line.separator") + indentationBuffer;
// //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
ASTNode declaration = fRewriter.createStringPlaceholder(insertionString, ASTNode.EXPRESSION_STATEMENT);
while (locationInParent != Block.STATEMENTS_PROPERTY && locationInParent != SwitchStatement.BODY_PROPERTY) {
if (locationInParent == IfStatement.TRUE_STATEMENT_PROPERTY
|| locationInParent == IfStatement.FALSE_STATEMENT_PROPERTY
|| locationInParent == ForStatement.BODY_PROPERTY || locationInParent == DoStatement.BODY_PROPERTY
|| locationInParent == WhileStatement.BODY_PROPERTY) {
// create intermediate block if target was the body property of
// a control statement:
Block replacement = rewrite.getAST().newBlock();
ListRewrite replacementRewrite = rewrite.getListRewrite(replacement, Block.STATEMENTS_PROPERTY);
replacementRewrite.insertFirst(declaration, null);
replacementRewrite.insertLast(rewrite.createMoveTarget(target), null);
rewrite.replace(target, replacement, groupDescription);
return;
}
if (parent instanceof Program) {
break;
}
target = parent;
parent = parent.getParent();
locationInParent = target.getLocationInParent();
}
ListRewrite listRewrite = rewrite.getListRewrite(parent, (ChildListPropertyDescriptor) locationInParent);
listRewrite.insertBefore(declaration, target, groupDescription);
}
/**
* Add the needed TextChanges (Insert and Replace) for the extraction
*
* @param range
* @param selectedExpression
* @param change
* @throws CoreException
* @throws BadLocationException
*/
private void insertVariableDeclaration(ASTNode target, ASTNode expr, boolean shouldWrapStatement, SourceRange range)
throws CoreException, BadLocationException {
Block block = fAst.newBlock();
block.setIsCurly(true);
ListRewrite replacementRewrite = fRewriter.getListRewrite(block, Block.STATEMENTS_PROPERTY);
// replacementRewrite.insertFirst(expr, null);
ASTNode move = fRewriter.createMoveTarget(target.getParent());
// if (move.getType() == ASTNode.IF_STATEMENT) {
// IfStatement ifState = (IfStatement) move;
// IfStatement orgTarget = (IfStatement) target;
//
// Statement trueStatment = orgTarget.getTrueStatement();
//
// Scalar condition = (Scalar) orgTarget.getCondition();
//
// ASTNode copyCondition = fRewriter.createMoveTarget(condition);
//
// ExpressionStatement copyTrue = (ExpressionStatement) fRewriter
// .createMoveTarget(((ExpressionStatement) trueStatment));
// copyTrue.setExpression((Expression) fRewriter
// .createMoveTarget(((ExpressionStatement) trueStatment)
// .getExpression()));
//
// Statement falseStatment = orgTarget.getFalseStatement();
// ExpressionStatement copyFalse = (ExpressionStatement) fRewriter
// .createMoveTarget(((ExpressionStatement) falseStatment));
//
// copyFalse.setExpression((Expression) fRewriter
// .createMoveTarget(((ExpressionStatement) falseStatment)
// .getExpression()));
// // ConditionalExpression conditionExp =
// // fAst.newConditionalExpression(
// // (Expression) copyCondition, (Expression) copyTrue,
// // (Expression) copyFalse);
// // condition.setCondition(orgTarget.getCondition());
// // condition.setIfFalse(orgTarget.get);
// ifState.setCondition((Expression) copyCondition);
// ifState.setFalseStatement((Statement) copyFalse);
// ifState.setTrueStatement((Statement) copyTrue);
//
// // ifState.setParent(parent, location)
// }
String expressionBuffer = getASTNodeValue(range.getOffset(), range.getLength());
String insertionString = getFullVariableName() + " = " //$NON-NLS-1$
+ expressionBuffer + ";"; //$NON-NLS-1$
// + System.getProperty("line.separator") + indentationBuffer;
// //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
ASTNode declaration = fRewriter.createStringPlaceholder(insertionString, ASTNode.EXPRESSION_STATEMENT);
replacementRewrite.insertFirst(declaration, null);
replacementRewrite.insertLast(move, null);
TextEditGroup insertDesc = new TextEditGroup(
PhpRefactoringCoreMessages.getString("ExtractFunctionRefactoring.4")); //$NON-NLS-1$
textFileChange.addTextEditGroup(insertDesc);
fRewriter.replace(target.getParent(), block, insertDesc);
// if (shouldWrapStatement) {
// insertionString = "{" + System.getProperty("line.separator") +
// insertionString; //$NON-NLS-1$ //$NON-NLS-2$
// }
// insert the new statement
// InsertEdit insertEdit = new InsertEdit(statementOffset,
// insertionString);
// addTextEditChange(insertEdit);
// replace the existing statement
// String replacementStr = getFullVariableName();
// replaceSelectedExpressionWithVariableDeclaration(replacementStr,
// range,
// expr);
// if needed, close the wrapping block
// if (shouldWrapStatement) {
// String closeCurlyStr = "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
// insertEdit = new InsertEdit(parentStatement.getEnd(), closeCurlyStr);
// addTextEditChange(insertEdit);
// }
}
/**
* @param offset
* @return a string containing the indentation characters.
* @throws BadLocationException
*/
private String getLineIndentation(int offset) throws BadLocationException {
IRegion region = document.getLineInformationOfOffset(offset);
String lineContent = document.get(region.getOffset(), region.getLength());
StringBuilder buff = new StringBuilder();
for (int index = 0; index < lineContent.length(); index++) {
if (!Character.isWhitespace(lineContent.charAt(index))) {
break;
}
buff.append(lineContent.charAt(index));
}
return buff.toString();
}
private IExpressionFragment getFirstReplacedExpression() {
if (!fReplaceAllOccurrences)
return getSelectedExpression();
IASTFragment[] nodesToReplace = retainOnlyReplacableMatches(false);
if (nodesToReplace.length == 0) {
return getSelectedExpression();
}
return (IExpressionFragment) nodesToReplace[0];
}
private static ASTNode[] getParents(ASTNode node) {
ASTNode current = node;
List<ASTNode> parents = new ArrayList<ASTNode>();
do {
parents.add(current.getParent());
current = current.getParent();
} while (current.getParent() != null);
Collections.reverse(parents);
return parents.toArray(new ASTNode[parents.size()]);
}
private ASTNode[] findDeepestCommonSuperNodePathForReplacedNodes() {
ASTNode[] matchNodes = getMatchNodes();
ASTNode[][] matchingNodesParents = new ASTNode[matchNodes.length][];
for (int i = 0; i < matchNodes.length; i++) {
matchingNodesParents[i] = getParents(matchNodes[i]);
}
List<Object> l = Arrays.asList(getLongestArrayPrefix(matchingNodesParents));
return l.toArray(new ASTNode[l.size()]);
}
private ASTNode[] getMatchNodes() {
IASTFragment[] matches = retainOnlyReplacableMatches(false);
ASTNode[] result = new ASTNode[matches.length];
for (int i = 0; i < matches.length; i++)
result[i] = matches[i].getAssociatedNode();
return result;
}
private static Object[] getLongestArrayPrefix(Object[][] arrays) {
int length = -1;
if (arrays.length == 0)
return new Object[0];
int minArrayLength = arrays[0].length;
for (int i = 1; i < arrays.length; i++)
minArrayLength = Math.min(minArrayLength, arrays[i].length);
for (int i = 0; i < minArrayLength; i++) {
if (!allArraysEqual(arrays, i))
break;
length++;
}
if (length == -1)
return new Object[0];
return getArrayPrefix(arrays[0], length + 1);
}
private static boolean allArraysEqual(Object[][] arrays, int position) {
Object element = arrays[0][position];
for (int i = 0; i < arrays.length; i++) {
Object[] array = arrays[i];
if (!element.equals(array[position]))
return false;
}
return true;
}
private static Object[] getArrayPrefix(Object[] array, int prefixLength) {
Assert.isTrue(prefixLength <= array.length);
Assert.isTrue(prefixLength >= 0);
Object[] prefix = new Object[prefixLength];
for (int i = 0; i < prefix.length; i++) {
prefix[i] = array[i];
}
return prefix;
}
private boolean shouldReplaceSelectedExpressionWithVariableDeclaration() {
IExpressionFragment selectedFragment = getSelectedExpression();
return selectedFragment.getAssociatedNode().getParent().getType() == ASTNode.EXPRESSION_STATEMENT
&& selectedFragment
.matches(ASTFragmentFactory.createFragmentForFullSubtree(selectedFragment.getAssociatedNode()));
}
private void replaceSelectedExpressionWithTempDeclaration(String str) throws CoreException {
ASTRewrite rewrite = fRewriter;
Expression selectedExpression = getSelectedExpression().getAssociatedExpression(); // whole
// expression
// selected
ASTNode declaration = fRewriter.createStringPlaceholder(str, ASTNode.ASSIGNMENT);
ExpressionStatement parent = (ExpressionStatement) selectedExpression.getParent();
final TextEditGroup textEditGroup = new TextEditGroup(CHANGE_DESCRIPTION);
TextEditChangeGroup textEditChangeGroup = new TextEditChangeGroup(textFileChange, textEditGroup);
textFileChange.addTextEditChangeGroup(textEditChangeGroup);
rewrite.replace(parent, declaration, textEditGroup);
}
private void replaceSelectedExpressionWithVariableDeclaration(String replacement, int start, int length,
ASTNode astNode) throws CoreException {
// create replace change
ASTNode node = fRewriter.createStringPlaceholder(replacement, ASTNode.ASSIGNMENT);
final TextEditGroup textEditGroup = new TextEditGroup(CHANGE_DESCRIPTION);
TextEditChangeGroup textEditChangeGroup = new TextEditChangeGroup(textFileChange, textEditGroup);
textFileChange.addTextEditChangeGroup(textEditChangeGroup);
fRewriter.replace(astNode, node, textEditGroup);
}
private boolean shouldWrapWithBlock(ASTNode node) {
if (node.getParent() == null || !(node.getParent() instanceof Statement)) {
return false;
}
// in case the selected expression in the only element of a control
// statement
return (ASTNodes.isControlStatement(node.getParent()));
}
private Statement getParentStatement(ASTNode node) {
if (node instanceof Statement)
return (Statement) node;
ASTNode parent = node.getParent();
while (!(parent instanceof Statement)) {
parent = parent.getParent();
}
return (Statement) parent;
}
private String getASTNodeValue(ASTNode node) throws BadLocationException {
return getASTNodeValue(node.getStart(), node.getLength());
}
private String getASTNodeValue(int start, int length) throws BadLocationException {
return document.get(start, length);
}
/**
* Adds the text change to the root change
*
* @param change
* @param textEdit
*/
private void addTextEditChange(TextEdit textEdit) {
final TextEditGroup textEditGroup = new TextEditGroup(CHANGE_DESCRIPTION);
textEditGroup.addTextEdit(textEdit);
TextEditChangeGroup textEditChangeGroup = new TextEditChangeGroup(textFileChange, textEditGroup);
textFileChange.addTextEditChangeGroup(textEditChangeGroup);
textFileChange.addEdit(textEdit);
}
private String getFullVariableName() {
assert (newVariableName != null);
return "$" + newVariableName; //$NON-NLS-1$
}
private IASTFragment[] getMatchingFragments(boolean clean) {
if (fReplaceAllOccurrences) {
if (clean || allMatchingFragments == null) {
allMatchingFragments = ASTFragmentFactory.createFragmentForFullSubtree(getEnclosingBodyNode())
.getSubFragmentsMatching(getSelectedExpression());
Comparator<IASTFragment> comparator = new Comparator<IASTFragment>() {
public int compare(IASTFragment o1, IASTFragment o2) {
return o1.getStartPosition() - o2.getStartPosition();
}
};
Arrays.sort(allMatchingFragments, comparator);
}
return allMatchingFragments;
} else
return new IASTFragment[] { getSelectedExpression() };
}
/**
* Checks whether the user given name already exists in the visible scope
*
* @return the status including necessary warnings
*/
private RefactoringStatus doesNameAlreadyExist(String name) {
RefactoringStatus status = new RefactoringStatus();
// if the selection is enclosed by a function,
// check if the user given variable name already exists in the function
// scope
if (enclosingBodyNode.getType() == ASTNode.FUNCTION_DECLARATION) {
if (PHPElementConciliator.localVariableAlreadyExists((FunctionDeclaration) enclosingBodyNode, name)) {
status.addWarning(
NLS.bind(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.26"), name)); //$NON-NLS-1$
}
} else {
// check if the user given variable name already exists in the
// global scope
if (PHPElementConciliator.globalVariableAlreadyExists((Program) astRoot, name)) {
status.addWarning(
NLS.bind(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.27"), name)); //$NON-NLS-1$
}
}
return status;
}
@Override
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
try {
pm.beginTask(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.28"), 1); //$NON-NLS-1$
MultiTextEdit root = new MultiTextEdit();
rootChange = new CompositeChange(CHANGE_DESCRIPTION);
rootChange.markAsSynthetic();
textFileChange = new ProgramDocumentChange(CHANGE_DESCRIPTION, document, astRoot);
textFileChange.setEdit(root);
textFileChange.setTextType("php"); //$NON-NLS-1$
rootChange.add(textFileChange);
fAst = getSelectedExpression().getAssociatedNode().getAST();
fRewriter = ASTRewrite.create(fAst);
try {
extractVariable();
} catch (CoreException exception) {
RefactoringPlugin.logException(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.29"), //$NON-NLS-1$
exception);
} catch (BadLocationException e) {
RefactoringPlugin.logException(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.30"), //$NON-NLS-1$
e);
}
// handle matching occurrences
if (fReplaceAllOccurrences) {
replaceOccurances();
}
TextEdit edit = fRewriter.rewriteAST(document, null);
root.addChild(edit);
} finally {
pm.done();
}
return rootChange;
}
@Override
public String getName() {
return PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.6"); //$NON-NLS-1$
}
public Change getChange() {
return rootChange;
}
public TextChange getTextChange() {
return textFileChange;
}
/**
* @return proposed variable names (may be empty, but not null). The first
* proposal should be used as "best guess" (if it exists).
*/
public String[] guessTempNames() {
if (fGuessedTempNames == null) {
Expression expression = getSelectedExpression().getAssociatedExpression();
if (expression != null) {
fGuessedTempNames = RefactoringUtility.getVariableNameSuggestions(expression);
}
if (fGuessedTempNames == null || fGuessedTempNames.length == 0) {
fGuessedTempNames = new String[0];
} else {
adjustGuessList(fGuessedTempNames);
}
}
return fGuessedTempNames;
}
/**
* Go over the list of suggestions and adjust the names to the existing
* variable names
*
* @param guessedTempNames
*/
private void adjustGuessList(String[] guessedTempNames) {
for (int i = 0; i < guessedTempNames.length; i++) {
int idx = 2;
String suggestionStr = guessedTempNames[i];
while (doesNameAlreadyExist(guessedTempNames[i]).getSeverity() != IStatus.OK) {
guessedTempNames[i] = suggestionStr + idx;
idx++;
}
}
}
/**
* Validates the new variable name
*
* @param text
* @return
*/
public RefactoringStatus checkNewVariableName(String text) {
RefactoringStatus status = new RefactoringStatus();
status.merge(RefactoringUtility.checkNewElementName(text));
status.merge(doesNameAlreadyExist(text));
return status;
}
}