blob: 167691efd92af62036d26a0ba7e2512402cde657 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.internal.corext.refactoring.code;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
import org.eclipse.wst.jsdt.core.IJavaScriptElement;
import org.eclipse.wst.jsdt.core.IJavaScriptProject;
import org.eclipse.wst.jsdt.core.JavaScriptModelException;
import org.eclipse.wst.jsdt.core.compiler.IProblem;
import org.eclipse.wst.jsdt.core.dom.AST;
import org.eclipse.wst.jsdt.core.dom.ASTNode;
import org.eclipse.wst.jsdt.core.dom.ASTVisitor;
import org.eclipse.wst.jsdt.core.dom.ArrayInitializer;
import org.eclipse.wst.jsdt.core.dom.Assignment;
import org.eclipse.wst.jsdt.core.dom.Block;
import org.eclipse.wst.jsdt.core.dom.BodyDeclaration;
import org.eclipse.wst.jsdt.core.dom.CatchClause;
import org.eclipse.wst.jsdt.core.dom.ClassInstanceCreation;
import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
import org.eclipse.wst.jsdt.core.dom.ConstructorInvocation;
import org.eclipse.wst.jsdt.core.dom.DoStatement;
import org.eclipse.wst.jsdt.core.dom.EnhancedForStatement;
import org.eclipse.wst.jsdt.core.dom.Expression;
import org.eclipse.wst.jsdt.core.dom.ExpressionStatement;
import org.eclipse.wst.jsdt.core.dom.FieldAccess;
import org.eclipse.wst.jsdt.core.dom.ForInStatement;
import org.eclipse.wst.jsdt.core.dom.ForStatement;
import org.eclipse.wst.jsdt.core.dom.IBinding;
import org.eclipse.wst.jsdt.core.dom.IFunctionBinding;
import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
import org.eclipse.wst.jsdt.core.dom.IVariableBinding;
import org.eclipse.wst.jsdt.core.dom.IfStatement;
import org.eclipse.wst.jsdt.core.dom.Initializer;
import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
import org.eclipse.wst.jsdt.core.dom.Name;
import org.eclipse.wst.jsdt.core.dom.NullLiteral;
import org.eclipse.wst.jsdt.core.dom.ParenthesizedExpression;
import org.eclipse.wst.jsdt.core.dom.PostfixExpression;
import org.eclipse.wst.jsdt.core.dom.PrefixExpression;
import org.eclipse.wst.jsdt.core.dom.QualifiedName;
import org.eclipse.wst.jsdt.core.dom.SimpleName;
import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
import org.eclipse.wst.jsdt.core.dom.Statement;
import org.eclipse.wst.jsdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.wst.jsdt.core.dom.SuperConstructorInvocation;
import org.eclipse.wst.jsdt.core.dom.SwitchCase;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement;
import org.eclipse.wst.jsdt.core.dom.WhileStatement;
import org.eclipse.wst.jsdt.core.dom.WithStatement;
import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.wst.jsdt.core.dom.rewrite.ListRewrite;
import org.eclipse.wst.jsdt.core.refactoring.IJavaScriptRefactorings;
import org.eclipse.wst.jsdt.internal.corext.Corext;
import org.eclipse.wst.jsdt.internal.corext.SourceRange;
import org.eclipse.wst.jsdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes;
import org.eclipse.wst.jsdt.internal.corext.dom.ScopeAnalyzer;
import org.eclipse.wst.jsdt.internal.corext.dom.fragments.ASTFragmentFactory;
import org.eclipse.wst.jsdt.internal.corext.dom.fragments.IASTFragment;
import org.eclipse.wst.jsdt.internal.corext.dom.fragments.IExpressionFragment;
import org.eclipse.wst.jsdt.internal.corext.fix.LinkedProposalModel;
import org.eclipse.wst.jsdt.internal.corext.fix.LinkedProposalPositionGroup;
import org.eclipse.wst.jsdt.internal.corext.refactoring.Checks;
import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptor;
import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
import org.eclipse.wst.jsdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.wst.jsdt.internal.corext.refactoring.base.JavaStatusContext;
import org.eclipse.wst.jsdt.internal.corext.refactoring.base.JavaStringStatusContext;
import org.eclipse.wst.jsdt.internal.corext.refactoring.base.RefactoringStatusCodes;
import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.RefactoringDescriptorChange;
import org.eclipse.wst.jsdt.internal.corext.refactoring.rename.RefactoringAnalyzeUtil;
import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.wst.jsdt.internal.corext.refactoring.util.NoCommentSourceRangeComputer;
import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.wst.jsdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.wst.jsdt.internal.corext.util.Messages;
import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
import org.eclipse.wst.jsdt.internal.ui.viewsupport.BindingLabelProvider;
import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
/**
* Extract Local Variable (from selected expression inside method or initializer).
*/
public class ExtractTempRefactoring extends ScriptableRefactoring {
private static final String ATTRIBUTE_REPLACE= "replace"; //$NON-NLS-1$
private static final String ATTRIBUTE_FINAL= "final"; //$NON-NLS-1$
private static final class ForStatementChecker extends ASTVisitor {
private final Collection fForInitializerVariables;
private boolean fReferringToForVariable= false;
public ForStatementChecker(Collection forInitializerVariables) {
Assert.isNotNull(forInitializerVariables);
fForInitializerVariables= forInitializerVariables;
}
public boolean isReferringToForVariable() {
return fReferringToForVariable;
}
public boolean visit(SimpleName node) {
IBinding binding= node.resolveBinding();
if (binding != null && fForInitializerVariables.contains(binding)) {
fReferringToForVariable= true;
}
return false;
}
}
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 boolean canReplace(IASTFragment fragment) {
ASTNode node= fragment.getAssociatedNode();
ASTNode parent= node.getParent();
if (parent instanceof VariableDeclarationFragment) {
VariableDeclarationFragment vdf= (VariableDeclarationFragment) parent;
if (node.equals(vdf.getName()))
return false;
}
if (isMethodParameter(node))
return false;
if (isThrowableInCatchBlock(node))
return false;
if (parent instanceof ExpressionStatement)
return false;
if (isLeftValue(node))
return false;
if (isReferringToLocalVariableFromFor((Expression) node))
return false;
if (isUsedInForInitializerOrUpdater((Expression) node))
return false;
if (parent instanceof SwitchCase)
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;
}
// return List<IVariableBinding>
private static List getForInitializedVariables(VariableDeclarationExpression variableDeclarations) {
List forInitializerVariables= new ArrayList(1);
for (Iterator iter= variableDeclarations.fragments().iterator(); iter.hasNext();) {
VariableDeclarationFragment fragment= (VariableDeclarationFragment) iter.next();
IVariableBinding binding= fragment.resolveBinding();
if (binding != null)
forInitializerVariables.add(binding);
}
return forInitializerVariables;
}
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 ASTNode[] getParents(ASTNode node) {
ASTNode current= node;
List parents= new ArrayList();
do {
parents.add(current.getParent());
current= current.getParent();
} while (current.getParent() != null);
Collections.reverse(parents);
return (ASTNode[]) parents.toArray(new ASTNode[parents.size()]);
}
private static boolean isLeftValue(ASTNode node) {
ASTNode parent= node.getParent();
if (parent instanceof Assignment) {
Assignment assignment= (Assignment) parent;
if (assignment.getLeftHandSide() == node)
return true;
}
if (parent instanceof PostfixExpression)
return true;
if (parent instanceof PrefixExpression) {
PrefixExpression.Operator op= ((PrefixExpression) parent).getOperator();
if (op.equals(PrefixExpression.Operator.DECREMENT))
return true;
if (op.equals(PrefixExpression.Operator.INCREMENT))
return true;
return false;
}
return false;
}
private static boolean isMethodParameter(ASTNode node) {
return (node instanceof SimpleName) && (node.getParent() instanceof SingleVariableDeclaration) && (node.getParent().getParent() instanceof FunctionDeclaration);
}
private static boolean isReferringToLocalVariableFromFor(Expression expression) {
ASTNode current= expression;
ASTNode parent= current.getParent();
while (parent != null && !(parent instanceof BodyDeclaration)) {
if (parent instanceof ForStatement) {
ForStatement forStmt= (ForStatement) parent;
if (forStmt.initializers().contains(current) || forStmt.updaters().contains(current) || forStmt.getExpression() == current) {
List initializers= forStmt.initializers();
if (initializers.size() == 1 && initializers.get(0) instanceof VariableDeclarationExpression) {
List forInitializerVariables= getForInitializedVariables((VariableDeclarationExpression) initializers.get(0));
ForStatementChecker checker= new ForStatementChecker(forInitializerVariables);
expression.accept(checker);
if (checker.isReferringToForVariable())
return true;
}
}
}
else if (parent instanceof ForInStatement) {
ForInStatement forInStmt= (ForInStatement) parent;
if (forInStmt.getIterationVariable().equals(current) || forInStmt.getCollection() == current) {
if (forInStmt.getIterationVariable() instanceof VariableDeclarationExpression) {
List forInitializerVariables= new ArrayList(1);
VariableDeclarationExpression vde = (VariableDeclarationExpression) forInStmt.getIterationVariable();
for(int i = 0; i < vde.fragments().size(); i++){
VariableDeclarationFragment fragment = (VariableDeclarationFragment) vde.fragments().get(i);
forInitializerVariables.add( fragment.resolveBinding());
}
ForStatementChecker checker= new ForStatementChecker(forInitializerVariables);
expression.accept(checker);
if (checker.isReferringToForVariable())
return true;
}
}
}
current= parent;
parent= current.getParent();
}
return false;
}
private static boolean isThrowableInCatchBlock(ASTNode node) {
return (node instanceof SimpleName) && (node.getParent() instanceof SingleVariableDeclaration) && (node.getParent().getParent() instanceof CatchClause);
}
private static boolean isUsedInForInitializerOrUpdater(Expression expression) {
ASTNode parent= expression.getParent();
if (parent instanceof ForStatement) {
ForStatement forStmt= (ForStatement) parent;
return forStmt.initializers().contains(expression) || forStmt.updaters().contains(expression);
}
return false;
}
private static IASTFragment[] retainOnlyReplacableMatches(IASTFragment[] allMatches) {
List result= new ArrayList(allMatches.length);
for (int i= 0; i < allMatches.length; i++) {
if (canReplace(allMatches[i]))
result.add(allMatches[i]);
}
return (IASTFragment[]) result.toArray(new IASTFragment[result.size()]);
}
private JavaScriptUnit fCompilationUnitNode;
private CompilationUnitRewrite fCURewrite;
private IJavaScriptUnit fCu;
private boolean fDeclareFinal;
private String[] fExcludedVariableNames;
private boolean fReplaceAllOccurrences;
// caches:
private IExpressionFragment fSelectedExpression;
private int fSelectionLength;
private int fSelectionStart;
private String fTempName;
private String[] fGuessedTempNames;
private TextChange fChange;
private LinkedProposalModel fLinkedProposalModel;
private static final String KEY_NAME= "name"; //$NON-NLS-1$
// private static final String KEY_TYPE= "type"; //$NON-NLS-1$
/**
* Creates a new extract temp refactoring
* @param unit the compilation unit, or <code>null</code> if invoked by scripting
* @param selectionStart
* @param selectionLength
*/
public ExtractTempRefactoring(IJavaScriptUnit unit, int selectionStart, int selectionLength) {
Assert.isTrue(selectionStart >= 0);
Assert.isTrue(selectionLength >= 0);
fSelectionStart= selectionStart;
fSelectionLength= selectionLength;
fCu= unit;
fCompilationUnitNode= null;
fReplaceAllOccurrences= true; // default
fDeclareFinal= false; // default
fTempName= ""; //$NON-NLS-1$
fLinkedProposalModel= null;
}
public ExtractTempRefactoring(JavaScriptUnit astRoot, int selectionStart, int selectionLength) {
Assert.isTrue(selectionStart >= 0);
Assert.isTrue(selectionLength >= 0);
Assert.isTrue(astRoot.getTypeRoot() instanceof IJavaScriptUnit);
fSelectionStart= selectionStart;
fSelectionLength= selectionLength;
fCu= (IJavaScriptUnit) astRoot.getTypeRoot();
fCompilationUnitNode= astRoot;
fReplaceAllOccurrences= true; // default
fDeclareFinal= false; // default
fTempName= ""; //$NON-NLS-1$
fLinkedProposalModel= null;
}
public void setLinkedProposalModel(LinkedProposalModel linkedProposalModel) {
fLinkedProposalModel= linkedProposalModel;
}
private void addReplaceExpressionWithTemp() throws JavaScriptModelException {
IASTFragment[] fragmentsToReplace= retainOnlyReplacableMatches(getMatchingFragments());
//TODO: should not have to prune duplicates here...
ASTRewrite rewrite= fCURewrite.getASTRewrite();
HashSet seen= new HashSet();
for (int i= 0; i < fragmentsToReplace.length; i++) {
IASTFragment fragment= fragmentsToReplace[i];
if (! seen.add(fragment))
continue;
SimpleName tempName= fCURewrite.getAST().newSimpleName(fTempName);
TextEditGroup description= fCURewrite.createGroupDescription(RefactoringCoreMessages.ExtractTempRefactoring_replace);
fragment.replace(rewrite, tempName, description);
if (fLinkedProposalModel != null)
fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(rewrite.track(tempName), false);
}
}
private RefactoringStatus checkExpression() throws JavaScriptModelException {
Expression selectedExpression= getSelectedExpression().getAssociatedExpression();
if (selectedExpression != null) {
final ASTNode parent= selectedExpression.getParent();
if (selectedExpression instanceof NullLiteral) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_null_literals);
} else if (selectedExpression instanceof ArrayInitializer) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_array_initializer);
} else if (selectedExpression instanceof Assignment) {
if (parent instanceof Expression && !(parent instanceof ParenthesizedExpression))
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_assignment);
else
return null;
} else if (selectedExpression instanceof SimpleName) {
if ((((SimpleName) selectedExpression)).isDeclaration())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_names_in_declarations);
if (parent instanceof QualifiedName && selectedExpression.getLocationInParent() == QualifiedName.NAME_PROPERTY || parent instanceof FieldAccess && selectedExpression.getLocationInParent() == FieldAccess.NAME_PROPERTY)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_select_expression);
}
}
return null;
}
// !! Same as in ExtractConstantRefactoring
private RefactoringStatus checkExpressionFragmentIsRValue() throws JavaScriptModelException {
switch (Checks.checkExpressionIsRValue(getSelectedExpression().getAssociatedExpression())) {
case Checks.NOT_RVALUE_MISC:
return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractTempRefactoring_select_expression, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE, null);
case Checks.NOT_RVALUE_VOID:
return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractTempRefactoring_no_void, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE_VOID, null);
case Checks.IS_RVALUE:
return new RefactoringStatus();
default:
Assert.isTrue(false);
return null;
}
}
public TextChange createTextChange(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.ExtractTempRefactoring_checking_preconditions, 3);
fCURewrite= new CompilationUnitRewrite(fCu, fCompilationUnitNode);
fCURewrite.getASTRewrite().setTargetSourceRangeComputer(new NoCommentSourceRangeComputer());
doCreateChange(new SubProgressMonitor(pm, 2));
return fCURewrite.createChange(RefactoringCoreMessages.ExtractTempRefactoring_change_name, true, new SubProgressMonitor(pm, 1));
} finally {
pm.done();
}
}
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
fChange= createTextChange(pm);
RefactoringStatus result= new RefactoringStatus();
if (Arrays.asList(getExcludedVariableNames()).contains(fTempName))
result.addWarning(Messages.format(RefactoringCoreMessages.ExtractTempRefactoring_another_variable, fTempName));
result.merge(checkMatchingFragments());
fChange.setKeepPreviewEdits(true);
checkNewSource(result);
return result;
}
private final JDTRefactoringDescriptor createRefactoringDescriptor() {
final Map arguments= new HashMap();
String project= null;
IJavaScriptProject javaProject= fCu.getJavaScriptProject();
if (javaProject != null)
project= javaProject.getElementName();
final String description= Messages.format(RefactoringCoreMessages.ExtractTempRefactoring_descriptor_description_short, fTempName);
final String expression= ASTNodes.asString(fSelectedExpression.getAssociatedExpression());
final String header= Messages.format(RefactoringCoreMessages.ExtractTempRefactoring_descriptor_description, new String[] { fTempName, expression});
final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractTempRefactoring_name_pattern, fTempName));
final BodyDeclaration decl= (BodyDeclaration) ASTNodes.getParent(fSelectedExpression.getAssociatedExpression(), BodyDeclaration.class);
if (decl instanceof FunctionDeclaration) {
final IFunctionBinding method= ((FunctionDeclaration) decl).resolveBinding();
final String label= method != null ? BindingLabelProvider.getBindingLabel(method, JavaScriptElementLabels.ALL_FULLY_QUALIFIED) : '{' + JavaScriptElementLabels.ELLIPSIS_STRING + '}';
comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractTempRefactoring_destination_pattern, label));
}
comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractTempRefactoring_expression_pattern, expression));
if (fReplaceAllOccurrences)
comment.addSetting(RefactoringCoreMessages.ExtractTempRefactoring_replace_occurrences);
if (fDeclareFinal)
comment.addSetting(RefactoringCoreMessages.ExtractTempRefactoring_declare_final);
final JDTRefactoringDescriptor descriptor= new JDTRefactoringDescriptor(IJavaScriptRefactorings.EXTRACT_LOCAL_VARIABLE, project, description, comment.asString(), arguments, RefactoringDescriptor.NONE);
arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fCu));
arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_NAME, fTempName);
arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_SELECTION, Integer.valueOf(fSelectionStart).toString() + " " + Integer.valueOf(fSelectionLength).toString()); //$NON-NLS-1$
arguments.put(ATTRIBUTE_REPLACE, Boolean.valueOf(fReplaceAllOccurrences).toString());
arguments.put(ATTRIBUTE_FINAL, Boolean.valueOf(fDeclareFinal).toString());
return descriptor;
}
private void doCreateChange(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.ExtractTempRefactoring_checking_preconditions, 1);
try {
createTempDeclaration();
} catch (CoreException exception) {
JavaScriptPlugin.log(exception);
}
addReplaceExpressionWithTemp();
} finally {
pm.done();
}
}
private void checkNewSource(RefactoringStatus result) throws CoreException {
String newCuSource= fChange.getPreviewContent(new NullProgressMonitor());
JavaScriptUnit newCUNode= new RefactoringASTParser(AST.JLS3).parse(newCuSource, fCu, true, true, null);
IProblem[] newProblems= RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, fCompilationUnitNode);
for (int i= 0; i < newProblems.length; i++) {
IProblem problem= newProblems[i];
if (problem.isError())
result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource, new SourceRange(problem))));
}
}
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask("", 6); //$NON-NLS-1$
RefactoringStatus result= Checks.validateModifiesFiles(ResourceUtil.getFiles(new IJavaScriptUnit[] { fCu}), getValidationContext());
if (result.hasFatalError())
return result;
if (fCompilationUnitNode == null) {
fCompilationUnitNode= RefactoringASTParser.parseWithASTProvider(fCu, true, new SubProgressMonitor(pm, 3));
} else {
pm.worked(3);
}
result.merge(checkSelection(new SubProgressMonitor(pm, 3)));
if (!result.hasFatalError() && isLiteralNodeSelected())
fReplaceAllOccurrences= false;
return result;
} finally {
pm.done();
}
}
private RefactoringStatus checkMatchingFragments() throws JavaScriptModelException {
RefactoringStatus result= new RefactoringStatus();
IASTFragment[] matchingFragments= getMatchingFragments();
for (int i= 0; i < matchingFragments.length; i++) {
ASTNode node= matchingFragments[i].getAssociatedNode();
if (isLeftValue(node) && !isReferringToLocalVariableFromFor((Expression) node)) {
String msg= RefactoringCoreMessages.ExtractTempRefactoring_assigned_to;
result.addWarning(msg, JavaStatusContext.create(fCu, node));
}
}
return result;
}
private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaScriptModelException {
try {
pm.beginTask("", 8); //$NON-NLS-1$
IExpressionFragment selectedExpression= getSelectedExpression();
if (selectedExpression == null) {
String message= RefactoringCoreMessages.ExtractTempRefactoring_select_expression;
return CodeRefactoringUtil.checkMethodSyntaxErrors(fSelectionStart, fSelectionLength, fCompilationUnitNode, message);
}
pm.worked(1);
if (isUsedInExplicitConstructorCall())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_explicit_constructor);
pm.worked(1);
if (getEnclosingBodyNode() == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_expr_in_method_or_initializer);
pm.worked(1);
ASTNode associatedNode= selectedExpression.getAssociatedNode();
if (associatedNode instanceof Name && associatedNode.getParent() instanceof ClassInstanceCreation && associatedNode.getLocationInParent() == ClassInstanceCreation.TYPE_PROPERTY)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_name_in_new);
pm.worked(1);
RefactoringStatus result= new RefactoringStatus();
result.merge(checkExpression());
if (result.hasFatalError())
return result;
pm.worked(1);
result.merge(checkExpressionFragmentIsRValue());
if (result.hasFatalError())
return result;
pm.worked(1);
if (isUsedInForInitializerOrUpdater(getSelectedExpression().getAssociatedExpression()))
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_for_initializer_updater);
pm.worked(1);
if (isReferringToLocalVariableFromFor(getSelectedExpression().getAssociatedExpression()))
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractTempRefactoring_refers_to_for_variable);
pm.worked(1);
return result;
} finally {
pm.done();
}
}
public RefactoringStatus checkTempName(String newName) {
RefactoringStatus status= Checks.checkTempName(newName);
if (Arrays.asList(getExcludedVariableNames()).contains(newName))
status.addWarning(Messages.format(RefactoringCoreMessages.ExtractTempRefactoring_another_variable, newName));
return status;
}
private void createAndInsertTempDeclaration() throws CoreException {
Expression initializer= getSelectedExpression().createCopyTarget(fCURewrite.getASTRewrite());
VariableDeclarationStatement vds= createTempDeclaration(initializer);
if ((!fReplaceAllOccurrences) || (retainOnlyReplacableMatches(getMatchingFragments()).length <= 1)) {
insertAt(getSelectedExpression().getAssociatedNode(), vds);
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)
insertAt(firstReplaceNodeParents[commonPath.length], vds);
else
insertAt(deepestCommonParent, vds);
}
private VariableDeclarationStatement createTempDeclaration(Expression initializer) throws CoreException {
AST ast= fCURewrite.getAST();
VariableDeclarationFragment vdf= ast.newVariableDeclarationFragment();
vdf.setName(ast.newSimpleName(fTempName));
vdf.setInitializer(initializer);
VariableDeclarationStatement vds= ast.newVariableDeclarationStatement(vdf);
// if (fDeclareFinal) {
// vds.modifiers().add(ast.newModifier(ModifierKeyword.FINAL_KEYWORD));
// }
// vds.setType(createTempType());
if (fLinkedProposalModel != null) {
ASTRewrite rewrite= fCURewrite.getASTRewrite();
LinkedProposalPositionGroup nameGroup= fLinkedProposalModel.getPositionGroup(KEY_NAME, true);
nameGroup.addPosition(rewrite.track(vdf.getName()), true);
String[] nameSuggestions= guessTempNames();
if (nameSuggestions.length > 0 && !nameSuggestions[0].equals(fTempName)) {
nameGroup.addProposal(fTempName, null, nameSuggestions.length + 1);
}
for (int i= 0; i < nameSuggestions.length; i++) {
nameGroup.addProposal(nameSuggestions[i], null, nameSuggestions.length - i);
}
}
return vds;
}
private void insertAt(ASTNode target, Statement declaration) throws JavaScriptModelException {
ASTRewrite rewrite= fCURewrite.getASTRewrite();
TextEditGroup groupDescription= fCURewrite.createGroupDescription(RefactoringCoreMessages.ExtractTempRefactoring_declare_local_variable);
ASTNode parent= target.getParent();
while (! (parent instanceof Block)) {
StructuralPropertyDescriptor locationInParent= target.getLocationInParent();
if (locationInParent == IfStatement.THEN_STATEMENT_PROPERTY
|| locationInParent == IfStatement.ELSE_STATEMENT_PROPERTY
|| locationInParent == ForStatement.BODY_PROPERTY
|| locationInParent == ForInStatement.BODY_PROPERTY
|| locationInParent == EnhancedForStatement.BODY_PROPERTY
|| locationInParent == DoStatement.BODY_PROPERTY
|| locationInParent == WhileStatement.BODY_PROPERTY
|| locationInParent == WithStatement.BODY_PROPERTY
) {
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;
}
target= parent;
parent= parent.getParent();
}
ListRewrite listRewrite= rewrite.getListRewrite(parent, Block.STATEMENTS_PROPERTY);
listRewrite.insertBefore(declaration, target, groupDescription);
}
public Change createChange(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.ExtractTempRefactoring_checking_preconditions, 1);
JDTRefactoringDescriptor descriptor= createRefactoringDescriptor();
return new RefactoringDescriptorChange(descriptor, RefactoringCoreMessages.ExtractTempRefactoring_extract_temp, new Change[] { fChange});
} finally {
pm.done();
}
}
private void createTempDeclaration() throws CoreException {
if (shouldReplaceSelectedExpressionWithTempDeclaration())
replaceSelectedExpressionWithTempDeclaration();
else
createAndInsertTempDeclaration();
}
public boolean declareFinal() {
return fDeclareFinal;
}
private ASTNode[] findDeepestCommonSuperNodePathForReplacedNodes() throws JavaScriptModelException {
ASTNode[] matchNodes= getMatchNodes();
ASTNode[][] matchingNodesParents= new ASTNode[matchNodes.length][];
for (int i= 0; i < matchNodes.length; i++) {
matchingNodesParents[i]= getParents(matchNodes[i]);
}
List l= Arrays.asList(getLongestArrayPrefix(matchingNodesParents));
return (ASTNode[]) l.toArray(new ASTNode[l.size()]);
}
private Block getEnclosingBodyNode() throws JavaScriptModelException {
ASTNode node= getSelectedExpression().getAssociatedNode();
do {
switch (node.getNodeType()) {
case ASTNode.FUNCTION_DECLARATION:
return ((FunctionDeclaration) node).getBody();
case ASTNode.INITIALIZER:
return ((Initializer) node).getBody();
}
node= node.getParent();
} while (node != null);
return null;
}
private String[] getExcludedVariableNames() {
if (fExcludedVariableNames == null) {
try {
IBinding[] bindings= new ScopeAnalyzer(fCompilationUnitNode).getDeclarationsInScope(getSelectedExpression().getStartPosition(), ScopeAnalyzer.VARIABLES);
fExcludedVariableNames= new String[bindings.length];
for (int i= 0; i < bindings.length; i++) {
fExcludedVariableNames[i]= bindings[i].getName();
}
} catch (JavaScriptModelException e) {
fExcludedVariableNames= new String[0];
}
}
return fExcludedVariableNames;
}
private IExpressionFragment getFirstReplacedExpression() throws JavaScriptModelException {
if (!fReplaceAllOccurrences)
return getSelectedExpression();
IASTFragment[] nodesToReplace= retainOnlyReplacableMatches(getMatchingFragments());
if (nodesToReplace.length == 0)
return getSelectedExpression();
Comparator comparator= new Comparator() {
public int compare(Object o1, Object o2) {
return ((IASTFragment) o1).getStartPosition() - ((IASTFragment) o2).getStartPosition();
}
};
Arrays.sort(nodesToReplace, comparator);
return (IExpressionFragment) nodesToReplace[0];
}
private IASTFragment[] getMatchingFragments() throws JavaScriptModelException {
if (fReplaceAllOccurrences) {
IASTFragment[] allMatches= ASTFragmentFactory.createFragmentForFullSubtree(getEnclosingBodyNode()).getSubFragmentsMatching(getSelectedExpression());
return allMatches;
} else
return new IASTFragment[] { getSelectedExpression()};
}
private ASTNode[] getMatchNodes() throws JavaScriptModelException {
IASTFragment[] matches= retainOnlyReplacableMatches(getMatchingFragments());
ASTNode[] result= new ASTNode[matches.length];
for (int i= 0; i < matches.length; i++)
result[i]= matches[i].getAssociatedNode();
return result;
}
public String getName() {
return RefactoringCoreMessages.ExtractTempRefactoring_name;
}
private IExpressionFragment getSelectedExpression() throws JavaScriptModelException {
if (fSelectedExpression != null)
return fSelectedExpression;
IASTFragment selectedFragment= ASTFragmentFactory.createFragmentForSourceRange(new SourceRange(fSelectionStart, fSelectionLength), fCompilationUnitNode, fCu);
if (selectedFragment instanceof IExpressionFragment && !Checks.isInsideJavadoc(selectedFragment.getAssociatedNode())) {
fSelectedExpression= (IExpressionFragment) selectedFragment;
} else if (selectedFragment != null) {
if (selectedFragment.getAssociatedNode() instanceof ExpressionStatement) {
ExpressionStatement exprStatement= (ExpressionStatement) selectedFragment.getAssociatedNode();
Expression expression= exprStatement.getExpression();
fSelectedExpression= (IExpressionFragment) ASTFragmentFactory.createFragmentForFullSubtree(expression);
} else if (selectedFragment.getAssociatedNode() instanceof Assignment) {
Assignment assignment= (Assignment) selectedFragment.getAssociatedNode();
fSelectedExpression= (IExpressionFragment) ASTFragmentFactory.createFragmentForFullSubtree(assignment);
}
}
return fSelectedExpression;
}
// private Type createTempType() throws CoreException {
// Expression expression= getSelectedExpression().getAssociatedExpression();
//
// Type resultingType= null;
// ITypeBinding typeBinding= expression.resolveTypeBinding();
//
// ASTRewrite rewrite= fCURewrite.getASTRewrite();
// AST ast= rewrite.getAST();
//
// if (expression instanceof ClassInstanceCreation) {
// resultingType= (Type) rewrite.createCopyTarget(((ClassInstanceCreation) expression).getType());
// } else if (expression instanceof CastExpression) {
// resultingType= (Type) rewrite.createCopyTarget(((CastExpression) expression).getType());
// } else {
// if (typeBinding == null) {
// typeBinding= ASTResolving.guessBindingForReference(expression);
// }
// if (typeBinding != null) {
// typeBinding= Bindings.normalizeForDeclarationUse(typeBinding, ast);
// resultingType= fCURewrite.getImportRewrite().addImport(typeBinding, ast);
// } else {
// resultingType= ast.newSimpleType(ast.newSimpleName("Object")); //$NON-NLS-1$
// }
// }
// if (fLinkedProposalModel != null) {
// LinkedProposalPositionGroup typeGroup= fLinkedProposalModel.getPositionGroup(KEY_TYPE, true);
// typeGroup.addPosition(rewrite.track(resultingType), false);
// if (typeBinding != null) {
// ITypeBinding[] relaxingTypes= ASTResolving.getNarrowingTypes(ast, typeBinding);
// for (int i= 0; i < relaxingTypes.length; i++) {
// typeGroup.addProposal(relaxingTypes[i], fCURewrite.getCu(), relaxingTypes.length - i);
// }
// }
// }
// return resultingType;
// }
public String guessTempName() {
String[] proposals= guessTempNames();
if (proposals.length == 0)
return fTempName;
else
return proposals[0];
}
/**
* @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) {
try {
Expression expression= getSelectedExpression().getAssociatedExpression();
if (expression != null) {
ITypeBinding binding= expression.resolveTypeBinding();
fGuessedTempNames= StubUtility.getVariableNameSuggestions(StubUtility.LOCAL, fCu.getJavaScriptProject(), binding, expression, Arrays.asList(getExcludedVariableNames()));
}
} catch (JavaScriptModelException e) {
}
if (fGuessedTempNames == null)
fGuessedTempNames= new String[0];
}
return fGuessedTempNames;
}
private boolean isLiteralNodeSelected() throws JavaScriptModelException {
IExpressionFragment fragment= getSelectedExpression();
if (fragment == null)
return false;
Expression expression= fragment.getAssociatedExpression();
if (expression == null)
return false;
switch (expression.getNodeType()) {
case ASTNode.BOOLEAN_LITERAL:
case ASTNode.CHARACTER_LITERAL:
case ASTNode.NULL_LITERAL:
case ASTNode.NUMBER_LITERAL:
case ASTNode.REGULAR_EXPRESSION_LITERAL:
case ASTNode.UNDEFINED_LITERAL:
return true;
default:
return false;
}
}
private boolean isUsedInExplicitConstructorCall() throws JavaScriptModelException {
Expression selectedExpression= getSelectedExpression().getAssociatedExpression();
if (ASTNodes.getParent(selectedExpression, ConstructorInvocation.class) != null)
return true;
if (ASTNodes.getParent(selectedExpression, SuperConstructorInvocation.class) != null)
return true;
return false;
}
public boolean replaceAllOccurrences() {
return fReplaceAllOccurrences;
}
private void replaceSelectedExpressionWithTempDeclaration() throws CoreException {
ASTRewrite rewrite= fCURewrite.getASTRewrite();
Expression selectedExpression= getSelectedExpression().getAssociatedExpression(); // whole expression selected
Expression initializer= (Expression) rewrite.createMoveTarget(selectedExpression);
ASTNode replacement= createTempDeclaration(initializer); // creates a VariableDeclarationStatement
ExpressionStatement parent= (ExpressionStatement) selectedExpression.getParent();
if (ASTNodes.isControlStatementBody(parent.getLocationInParent())) {
Block block= rewrite.getAST().newBlock();
block.statements().add(replacement);
replacement= block;
}
rewrite.replace(parent, replacement, fCURewrite.createGroupDescription(RefactoringCoreMessages.ExtractTempRefactoring_declare_local_variable));
}
public void setDeclareFinal(boolean declareFinal) {
fDeclareFinal= declareFinal;
}
public void setReplaceAllOccurrences(boolean replaceAllOccurrences) {
fReplaceAllOccurrences= replaceAllOccurrences;
}
public void setTempName(String newName) {
fTempName= newName;
}
private boolean shouldReplaceSelectedExpressionWithTempDeclaration() throws JavaScriptModelException {
IExpressionFragment selectedFragment= getSelectedExpression();
return selectedFragment.getAssociatedNode().getParent() instanceof ExpressionStatement
&& selectedFragment.matches(ASTFragmentFactory.createFragmentForFullSubtree(selectedFragment.getAssociatedNode()));
}
public RefactoringStatus initialize(final RefactoringArguments arguments) {
if (arguments instanceof JavaRefactoringArguments) {
final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
final String selection= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_SELECTION);
if (selection != null) {
int offset= -1;
int length= -1;
final StringTokenizer tokenizer= new StringTokenizer(selection);
if (tokenizer.hasMoreTokens())
offset= Integer.valueOf(tokenizer.nextToken()).intValue();
if (tokenizer.hasMoreTokens())
length= Integer.valueOf(tokenizer.nextToken()).intValue();
if (offset >= 0 && length >= 0) {
fSelectionStart= offset;
fSelectionLength= length;
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JDTRefactoringDescriptor.ATTRIBUTE_SELECTION}));
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_SELECTION));
final String handle= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_INPUT);
if (handle != null) {
final IJavaScriptElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaScriptElement.JAVASCRIPT_UNIT)
return createInputFatalStatus(element, IJavaScriptRefactorings.EXTRACT_LOCAL_VARIABLE);
else
fCu= (IJavaScriptUnit) element;
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_INPUT));
final String name= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_NAME);
if (name != null && !"".equals(name)) //$NON-NLS-1$
fTempName= name;
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_NAME));
final String replace= extended.getAttribute(ATTRIBUTE_REPLACE);
if (replace != null) {
fReplaceAllOccurrences= Boolean.valueOf(replace).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_REPLACE));
final String declareFinal= extended.getAttribute(ATTRIBUTE_FINAL);
if (declareFinal != null) {
fDeclareFinal= Boolean.valueOf(declareFinal).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_FINAL));
} else
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
return new RefactoringStatus();
}
}