/******************************************************************************* * Copyright (c) 2005, 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.function; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.php.core.ast.nodes.*; import org.eclipse.php.internal.core.corext.dom.LocalVariableIndex; import org.eclipse.php.internal.core.corext.dom.Selection; import org.eclipse.php.refactoring.core.PhpRefactoringCoreMessages; import org.eclipse.php.refactoring.core.code.flow.FlowContext; import org.eclipse.php.refactoring.core.code.flow.FlowInfo; import org.eclipse.php.refactoring.core.code.flow.InOutFlowAnalyzer; import org.eclipse.php.refactoring.core.code.flow.InputFlowAnalyzer; import org.eclipse.php.refactoring.core.visitor.CodeAnalyzer; import org.eclipse.php.refactoring.core.visitor.ScopeSyntaxErrorsVisitor; /* package */class ExtractFunctionAnalyzer extends CodeAnalyzer { public static final int ERROR = -2; public static final int UNDEFINED = -1; public static final int NO = 0; public static final int EXPRESSION = 1; public static final int ACCESS_TO_LOCAL = 2; public static final int RETURN_STATEMENT_VOID = 3; public static final int RETURN_STATEMENT_VALUE = 4; public static final int MULTIPLE = 5; private ASTNode fEnclosingBodyDeclaration; private FlowInfo fInputFlowInfo; private FlowContext fInputFlowContext; private int fMaxVariableId; // private IVariableBinding[] fMethodLocals; private int fReturnKind; private IFunctionBinding fEnclosingMethodBinding; private boolean fIsLastStatementSelected; private IVariableBinding fReturnValue; private IVariableBinding[] fArguments; // private Object fTypeVariables; public ExtractFunctionAnalyzer(Program cunit, ISourceModule sourceModule, IDocument document, Selection selection) throws CoreException, IOException { super(cunit, sourceModule, document, selection, false); } // ---- Activation checking // --------------------------------------------------------------------------- public RefactoringStatus checkInitialConditions() { RefactoringStatus result = getStatus(); checkExpression(result); if (result.hasFatalError()) return result; fReturnKind = UNDEFINED; fMaxVariableId = LocalVariableIndex.perform(getEnclosingBodyDeclaration()); if (analyzeSelection(result).hasFatalError()) return result; int returns = fReturnKind == NO ? 0 : 1; if (fReturnValue != null) { fReturnKind = ACCESS_TO_LOCAL; returns++; } if (isExpressionSelected()) { fReturnKind = EXPRESSION; returns++; } if (returns > 1) { // result.addFatalError(RefactoringCoreMessages.ExtractMethodAnalyzer_ambiguous_return_value, // JavaStatusContext.create(fCUnit, getSelection())); fReturnKind = MULTIPLE; return result; } // initReturnType(rewriter); return result; } /** * Gets the current selection * * @param astRoot * @return */ RefactoringStatus checkSelection(RefactoringStatus status, IProgressMonitor pm) { try { pm.beginTask("", 8); //$NON-NLS-1$ ASTNode[] selectedNodes = getSelectedNodes(); if (selectedNodes == null || selectedNodes.length == 0) { return RefactoringStatus .createFatalErrorStatus(PhpRefactoringCoreMessages.getString("ExtractVariableRefactoring.2")); //$NON-NLS-1$ } pm.worked(1); ASTNode enclosingBodyNode = getFirstSelectedNode().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); checkExpression(status); pm.worked(1); return status; } finally { pm.done(); } } private RefactoringStatus analyzeSelection(RefactoringStatus status) { fInputFlowContext = new FlowContext(0, fMaxVariableId + 1); fInputFlowContext.setConsiderAccessMode(true); fInputFlowContext.setComputeMode(FlowContext.ARGUMENTS); InOutFlowAnalyzer flowAnalyzer = new InOutFlowAnalyzer(fInputFlowContext); fInputFlowInfo = flowAnalyzer.perform(getSelectedNodes()); if (fInputFlowInfo.branches()) { status.addFatalError(PhpRefactoringCoreMessages.getString("ExtractFunctionAnalyzer.0")); //$NON-NLS-1$ fReturnKind = ERROR; return status; } if (fInputFlowInfo.isValueReturn()) { fReturnKind = RETURN_STATEMENT_VALUE; } else if (fInputFlowInfo.isVoidReturn() || (fInputFlowInfo.isPartialReturn() && isVoidMethod() && isLastStatementSelected())) { fReturnKind = RETURN_STATEMENT_VOID; } else if (fInputFlowInfo.isNoReturn() || fInputFlowInfo.isThrow() || fInputFlowInfo.isUndefined()) { fReturnKind = NO; } // // if (fReturnKind == UNDEFINED) { // status.addError(RefactoringCoreMessages.FlowAnalyzer_execution_flow, // JavaStatusContext.create(fCUnit, getSelection())); // fReturnKind= NO; // } computeInput(); // computeExceptions(); computeOutput(status); // if (status.hasFatalError()) // return status; // // adjustArgumentsAndMethodLocals(); // compressArrays(); return status; } private void computeOutput(RefactoringStatus status) { // First find all writes inside the selection. FlowContext flowContext = new FlowContext(0, fMaxVariableId + 1); flowContext.setConsiderAccessMode(true); flowContext.setComputeMode(FlowContext.RETURN_VALUES); FlowInfo returnInfo = new InOutFlowAnalyzer(flowContext).perform(getSelectedNodes()); IVariableBinding[] returnValues = returnInfo.get(flowContext, FlowInfo.WRITE | FlowInfo.WRITE_POTENTIAL | FlowInfo.UNKNOWN); // Compute a selection that exactly covers the selected nodes IRegion region = getSelectedNodeRange(); Selection selection = Selection.createFromStartLength(region.getOffset(), region.getLength()); int counter = 0; flowContext.setComputeMode(FlowContext.ARGUMENTS); FlowInfo argInfo = null; if (fEnclosingBodyDeclaration instanceof MethodDeclaration) { argInfo = new InputFlowAnalyzer(flowContext, selection, true) .perform(((MethodDeclaration) fEnclosingBodyDeclaration).getFunction()); } else if (fEnclosingBodyDeclaration instanceof FunctionDeclaration) { argInfo = new InputFlowAnalyzer(flowContext, selection, true).perform(fEnclosingBodyDeclaration); } else { argInfo = new InputFlowAnalyzer(flowContext, selection, true).perform(fEnclosingBodyDeclaration); } IVariableBinding[] reads = argInfo.get(flowContext, FlowInfo.READ | FlowInfo.READ_POTENTIAL | FlowInfo.UNKNOWN); outer: for (int i = 0; i < returnValues.length && counter <= 1; i++) { IVariableBinding binding = returnValues[i]; for (int x = 0; x < reads.length; x++) { if (reads[x].equals(binding)) { counter++; fReturnValue = binding; continue outer; } } } switch (counter) { case 0: fReturnValue = null; break; case 1: break; default: fReturnValue = null; status.addFatalError(PhpRefactoringCoreMessages.getString("ExtractFunctionAnalyzer.1")); //$NON-NLS-1$ return; } // List callerLocals= new ArrayList(5); // FlowInfo localInfo= new InputFlowAnalyzer(flowContext, selection, // false).perform(fEnclosingBodyDeclaration); // IVariableBinding[] writes= localInfo.get(flowContext, FlowInfo.WRITE // | FlowInfo.WRITE_POTENTIAL | FlowInfo.UNKNOWN); // for (int i= 0; i < writes.length; i++) { // IVariableBinding write= writes[i]; // if (getSelection().covers(ASTNodes.findDeclaration(write, // fEnclosingBodyDeclaration))) // callerLocals.add(write); // } // fCallerLocals= (IVariableBinding[])callerLocals.toArray(new // IVariableBinding[callerLocals.size()]); // if (fReturnValue != null && // getSelection().covers(ASTNodes.findDeclaration(fReturnValue, // fEnclosingBodyDeclaration))) // fReturnLocal= fReturnValue; } private boolean isVoidMethod() { // if we have an initializer if (fEnclosingMethodBinding == null) return true; ITypeBinding[] binding = fEnclosingMethodBinding.getReturnType(); for (ITypeBinding currentBinding : binding) { if (fEnclosingBodyDeclaration.getAST().resolveWellKnownType("void").equals(currentBinding)) //$NON-NLS-1$ return true; } return false; } public boolean isLastStatementSelected() { return fIsLastStatementSelected; } private void checkExpression(RefactoringStatus status) { ASTNode[] nodes = getSelectedNodes(); if (nodes != null && nodes.length == 1) { ASTNode node = nodes[0]; if (!(node instanceof ExpressionStatement) && !(node instanceof EchoStatement) && !(node instanceof InfixExpression) && !(node instanceof ForStatement) && !(node instanceof DoStatement) && !(node instanceof ForEachStatement) && !(node instanceof IfStatement) && !(node instanceof SwitchStatement) && !(node instanceof TryStatement) && !(node instanceof WhileStatement)) { status.addFatalError(PhpRefactoringCoreMessages.getString("ExtractFunctionAnalyzer.2")); //$NON-NLS-1$ } } if (nodes == null || nodes.length == 0) { status.addFatalError(PhpRefactoringCoreMessages.getString("ExtractFunctionAnalyzer.3")); //$NON-NLS-1$ } } /** * 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; } public ASTNode getEnclosingBodyDeclaration() { // Class method case fEnclosingBodyDeclaration = (BodyDeclaration) ASTNodes.getParent(getFirstSelectedNode(), BodyDeclaration.class); // Global Function case if (fEnclosingBodyDeclaration == null) { fEnclosingBodyDeclaration = (Statement) ASTNodes.getParent(getFirstSelectedNode(), FunctionDeclaration.class); } // Global scope case if (fEnclosingBodyDeclaration == null) { fEnclosingBodyDeclaration = ASTNodes.getParent(getFirstSelectedNode(), Program.class); } return fEnclosingBodyDeclaration; } private void computeInput() { int argumentMode = FlowInfo.READ | FlowInfo.READ_POTENTIAL | FlowInfo.WRITE_POTENTIAL | FlowInfo.UNKNOWN; fArguments = removeSelectedDeclarations(fInputFlowInfo.get(fInputFlowContext, argumentMode)); // IVariableBinding[] bindings = fInputFlowInfo.get(fInputFlowContext, // FlowInfo.WRITE | FlowInfo.WRITE_POTENTIAL); // fTypeVariables = // computeTypeVariables(fInputFlowInfo.getTypeVariables()); } // private ITypeBinding[] computeTypeVariables(ITypeBinding[] bindings) { // Selection selection = getSelection(); // Set result = new HashSet(); // // first remove all type variables that come from outside of the method // // or are covered by the selection // // Program compilationUnit= (Program)fEnclosingBodyDeclaration.getRoot(); // for (int i = 0; i < bindings.length; i++) { // ASTNode decl = ((VariableBinding) bindings[i]).getVarialbe(); // if (decl == null || (!selection.covers(decl) && decl.getParent() // instanceof MethodDeclaration)) // result.add(bindings[i]); // } // // all all type variables which are needed since a local variable uses it // for (int i = 0; i < fArguments.length; i++) { // IVariableBinding arg = fArguments[i]; // ITypeBinding type = arg.getType(); // if (type != null && type.isTypeVariable()) { // ASTNode decl = ((VariableBinding) bindings[i]).getVarialbe(); // if (decl == null || (!selection.covers(decl) && decl.getParent() // instanceof MethodDeclaration)) // result.add(type); // } // } // return (ITypeBinding[]) result.toArray(new ITypeBinding[result.size()]); // } public IVariableBinding getReturnValue() { return fReturnValue; } private void computeLastStatementSelected() { ASTNode[] nodes = getSelectedNodes(); if (nodes.length == 0) { fIsLastStatementSelected = false; } else { Block body = null; if (fEnclosingBodyDeclaration instanceof FunctionDeclaration) { body = ((FunctionDeclaration) fEnclosingBodyDeclaration).getBody(); } if (fEnclosingBodyDeclaration instanceof MethodDeclaration) { body = ((MethodDeclaration) fEnclosingBodyDeclaration).getFunction().getBody(); } if (body != null) { List<Statement> statements = body.statements(); fIsLastStatementSelected = nodes[nodes.length - 1] == statements.get(statements.size() - 1); } } } @Override public void endVisit(Program program) { computeLastStatementSelected(); super.endVisit(program); } public int getReturnKind() { return fReturnKind; } public IVariableBinding[] getArguments() { return fArguments; } private IVariableBinding[] removeSelectedDeclarations(IVariableBinding[] bindings) { List<IVariableBinding> result = new ArrayList<IVariableBinding>(bindings.length); Selection selection = getSelection(); for (int i = 0; i < bindings.length; i++) { if (!selection.covers(((VariableBinding) bindings[i]).getVarialbe())) ; result.add(bindings[i]); } return result.toArray(new IVariableBinding[result.size()]); } }