/***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/cpl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006 Lukas Felber <lfelber@hsr.ch>
* Copyright (C) 2006 Mirko Stocker <me@misto.ch>
* Copyright (C) 2006 Thomas Corbat <tcorbat@hsr.ch>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the CPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.rubypeople.rdt.refactoring.core.extractmethod;
import java.util.Collection;
import org.jruby.ast.ArgsNode;
import org.jruby.ast.ArgumentNode;
import org.jruby.ast.ArrayNode;
import org.jruby.ast.BreakNode;
import org.jruby.ast.CaseNode;
import org.jruby.ast.ClassNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.ForNode;
import org.jruby.ast.IterNode;
import org.jruby.ast.MethodDefNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.MultipleAsgnNode;
import org.jruby.ast.NextNode;
import org.jruby.ast.Node;
import org.jruby.ast.RedoNode;
import org.jruby.ast.RetryNode;
import org.jruby.ast.RootNode;
import org.jruby.ast.SClassNode;
import org.jruby.ast.SuperNode;
import org.jruby.ast.WhenNode;
import org.jruby.ast.WhileNode;
import org.jruby.ast.YieldNode;
import org.jruby.ast.ZSuperNode;
import org.rubypeople.rdt.refactoring.core.IRefactoringConfig;
import org.rubypeople.rdt.refactoring.core.NodeProvider;
import org.rubypeople.rdt.refactoring.core.RefactoringConditionChecker;
import org.rubypeople.rdt.refactoring.core.SelectionNodeProvider;
import org.rubypeople.rdt.refactoring.exception.NoClassNodeException;
import org.rubypeople.rdt.refactoring.nodewrapper.MethodCallNodeWrapper;
import org.rubypeople.rdt.refactoring.nodewrapper.PartialClassNodeWrapper;
import org.rubypeople.rdt.refactoring.util.NodeUtil;
public class ExtractMethodConditionChecker extends RefactoringConditionChecker {
private ExtractMethodConfig config;
public ExtractMethodConditionChecker(ExtractMethodConfig config) {
super(config);
}
public void init(IRefactoringConfig configObj) {
this.config = (ExtractMethodConfig) configObj;
initEnclosingNodes();
if (!NodeProvider.isEmptyNode(config.getSelectedNodes()) && config.getExtractMethodHelper() == null ) {
config.setExtractedMethodHelper(new ExtractedMethodHelper(config));
}
}
private void initEnclosingNodes() {
RootNode rootNode = config.getDocumentProvider().getActiveFileRootNode();
config.setRootNode(rootNode);
config.setEnclosingScopeNode(SelectionNodeProvider.getEnclosingScope(rootNode, config.getSelection().getStartOffset()));
config.setEnclosingMethodNode((MethodDefNode) SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), MethodDefNode.class));
config.setSelectedNodes(getSelectedNodes(rootNode));
Node classNode = SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), ClassNode.class, SClassNode.class);
try {
config.setEnclosingClassNode(PartialClassNodeWrapper.getPartialClassNodeWrapper(classNode, rootNode));
} catch (NoClassNodeException e) {
/*don't care*/
}
}
private Node getSelectedNodes(RootNode rootNode) {
Node selectedNode = SelectionNodeProvider.getSelectedNodes(rootNode, config.getSelection());
//If selected node is an WhenNode, take the enclosing CaseNode as selectedNode.
if (NodeUtil.nodeAssignableFrom(selectedNode, WhenNode.class)) {
selectedNode = SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), CaseNode.class);
}
if(NodeUtil.nodeAssignableFrom(selectedNode, ArrayNode.class)) {
WhenNode enclosingWhen = (WhenNode) SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), WhenNode.class);
if(enclosingWhen != null && SelectionNodeProvider.nodeEnclosesNode(enclosingWhen.getExpressionNodes(), selectedNode)) {
selectedNode = SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), CaseNode.class);
}
}
//Check on loopControlNodes (Break, Redo, Next, Retry)
if(containsLoopControlNode(selectedNode)) {
selectedNode = getLoopOrItsParent(rootNode, selectedNode);
}
//Check if content of an ArgsNode is selected
if(SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), ArgsNode.class) != null) {
return SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), MethodDefNode.class);
}
//Check if the selected Node is an argumentNode
if(NodeUtil.nodeAssignableFrom(selectedNode, ArgumentNode.class)) {
selectedNode = config.getEnclosingMethodNode();
}
//check if the selected Nodes are contained in an enclosing arrayNode
//if not the fowllowing checks arent necessary anymore.
ArrayNode enclosingArrayNode = (ArrayNode) SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), ArrayNode.class);
if(!sectedNodesInArrayNode(enclosingArrayNode, selectedNode) || !NodeUtil.nodeAssignableFrom(selectedNode, ArrayNode.class)) {
return selectedNode;
}
//Check if enclosingArrayNode is the argsNode of a MethodCallNode.
Node enclosingMethodCallNode = SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), MethodCallNodeWrapper.METHOD_CALL_NODE_CLASSES());
MethodCallNodeWrapper enclosingMethodCall = new MethodCallNodeWrapper(enclosingMethodCallNode);
if(NodeUtil.nodeAssignableFrom(enclosingMethodCall.getArgsNode(), ArrayNode.class)) {
ArrayNode enclosingMethodCallArgs = (ArrayNode) enclosingMethodCall.getArgsNode();
if(enclosingArrayNode == enclosingMethodCallArgs)
return enclosingMethodCallNode;
}
//Check if enclosingArrayNode is the receiver node of a multiAsgnNode
MultipleAsgnNode asgnNode = (MultipleAsgnNode) SelectionNodeProvider.getEnclosingNode(rootNode, config.getSelection(), MultipleAsgnNode.class);
if(asgnNode != null && NodeUtil.nodeAssignableFrom(asgnNode.getHeadNode(), ArrayNode.class)) {
// ArrayNode multiAsgnHeadNode = (ArrayNode) asgnNode.getHeadNode();
// if(enclosingArrayNode == multiAsgnHeadNode) {
// return asgnNode;
// }
}
return selectedNode;
}
private Node getLoopOrItsParent(RootNode rootNode, Node selectedNode) {
Node loopNode = NodeProvider.getEnclosingNodeOfType(rootNode, selectedNode, WhileNode.class, ForNode.class, IterNode.class);
if(loopNode != null) {
selectedNode = loopNode;
}
if(NodeUtil.nodeAssignableFrom(loopNode, IterNode.class)) {
selectedNode = NodeProvider.findParentNode(rootNode, loopNode);
}
return selectedNode;
}
private boolean containsLoopControlNode(Node selectedNode) {
return !NodeProvider.getSubNodes(selectedNode, BreakNode.class, RedoNode.class, NextNode.class, RetryNode.class).isEmpty();
}
private boolean sectedNodesInArrayNode(ArrayNode arrayNode, Node selectedNode) {
if(arrayNode == null) {
return false;
}
Collection<Node> arrayChilds = NodeProvider.getAllNodes(arrayNode);
for(Object actSelectedNode : selectedNode.childNodes()) {
if(!arrayChilds.contains(actSelectedNode)) {
return false;
}
}
return true;
}
@Override
protected void checkFinalConditions() {
checkNewMethodName();
}
private void checkNewMethodName() {
String newMethodName = config.getHelper().getMethodName();
PartialClassNodeWrapper enclosingClassNode = config.getEnclosingClassNode();
if (enclosingClassNode != null) {
Collection<Node> methodNodes = NodeProvider.getSubNodes(enclosingClassNode.getWrappedNode(), DefnNode.class);
for (Node aktNode : methodNodes) {
if (((DefnNode)aktNode).getName().equals(newMethodName)) {
addError(Messages.ExtractMethodConditionChecker_MethodAlreadyExists);
}
}
}
}
@Override
protected void checkInitialConditions() {
if(!existSelectedNodes()) {
addError(Messages.ExtractMethodConditionChecker_NothingToDo);
} else if (containsYieldStatements()) {
addError(Messages.ExtractMethodConditionChecker_NotPossibleContainsYield);
} else if (containsSuperStatement()) {
addError(Messages.ExtractMethodConditionChecker_NotPossibleContainsSuper);
} else if (isModuleInSelection()) {
addError(Messages.ExtractMethodConditionChecker_NotPossibleModule);
} else if (isClassInSelection()) {
addError(Messages.ExtractMethodConditionChecker_MustNotContainAClass);
} else if (isMethodInSelction()) {
addError(Messages.ExtractMethodConditionChecker_MustNotContainAMethod);
} else {
checkInternalMethods();
}
}
private boolean existSelectedNodes() {
return !NodeProvider.isEmptyNode(config.getSelectedNodes());
}
private boolean containsYieldStatements() {
return NodeProvider.hasSubNodes(config.getSelectedNodes(), YieldNode.class);
}
private boolean containsSuperStatement() {
return NodeProvider.hasSubNodes(config.getSelectedNodes(), SuperNode.class, ZSuperNode.class);
}
private boolean isModuleInSelection() {
Node selctedNodes = config.getSelectedNodes();
Collection<Node> moduleNodes = NodeProvider.getSubNodes(selctedNodes, ModuleNode.class);
return !moduleNodes.isEmpty();
}
private boolean isClassInSelection() {
Node selctedNodes = config.getSelectedNodes();
Collection<Node> classNodes = NodeProvider.getSubNodes(selctedNodes, ClassNode.class);
return !classNodes.isEmpty();
}
private boolean isMethodInSelction() {
Node selctedNodes = config.getSelectedNodes();
Collection<Node> methodNodes = NodeProvider.getSubNodes(selctedNodes, MethodDefNode.class);
return !methodNodes.isEmpty();
}
private void checkInternalMethods() {
if (config.hasEnclosingClassNode() && !config.hasEnclosingMethodNode()) {
addError(Messages.ExtractMethodConditionChecker_NotInsideAMethod);
}
if (config.hasEnclosingClassNode() && NodeProvider.hasSubNodes(NodeUtil.getBody(config.getEnclosingScopeNode()), DefnNode.class)) {
addError(Messages.ExtractMethodConditionChecker_MustNotContainSubmethods);
}
}
}