/***** 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; import java.util.ArrayList; import java.util.Collection; import org.jruby.ast.AttrAssignNode; import org.jruby.ast.BlockNode; import org.jruby.ast.CallNode; import org.jruby.ast.ClassNode; import org.jruby.ast.ClassVarAsgnNode; import org.jruby.ast.ClassVarNode; import org.jruby.ast.DAsgnNode; import org.jruby.ast.DVarNode; import org.jruby.ast.GlobalAsgnNode; import org.jruby.ast.GlobalVarNode; import org.jruby.ast.InstAsgnNode; import org.jruby.ast.InstVarNode; import org.jruby.ast.IterNode; import org.jruby.ast.LocalAsgnNode; import org.jruby.ast.LocalVarNode; import org.jruby.ast.MethodDefNode; import org.jruby.ast.ModuleNode; import org.jruby.ast.NewlineNode; import org.jruby.ast.NilImplicitNode; import org.jruby.ast.Node; import org.jruby.ast.RootNode; import org.jruby.ast.types.INameNode; import org.jruby.lexer.yacc.ISourcePosition; import org.rubypeople.rdt.refactoring.exception.NoClassNodeException; import org.rubypeople.rdt.refactoring.nodewrapper.AttrAccessorNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.ClassNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.INodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.PartialClassNodeWrapper; import org.rubypeople.rdt.refactoring.util.NodeUtil; public class SelectionNodeProvider { public static Node getEnclosingScope(Node rootNode, Node from) { return getEnclosingScope(rootNode, from.getPosition().getStartOffset()); } public static Node getEnclosingScope(Node rootNode, int where) { return SelectionNodeProvider.getSelectedNodeOfType(rootNode, where, MethodDefNode.class, ClassNode.class, RootNode.class, IterNode.class); } public static Node getSelectedNodes(Node rootNode, IRefactoringContext selection) { Node enclosingNode = getEnclosingNode(rootNode, selection, Node.class); if (enclosingNode == null) return null; BlockNode enclosingBlockNode = (BlockNode) getEnclosingNode(rootNode, selection, BlockNode.class); if (enclosingBlockNode == null) { return enclosingNode; } Collection<Node> blockChildren = NodeProvider.getChildren(enclosingBlockNode); Node beginNode = getSelectedNodeOfType(enclosingBlockNode, selection.getStartOffset(), Node.class); Node beginBlockChildNode = getEnclosingNode(beginNode, blockChildren); Node endNode = getSelectedNodeOfType(enclosingBlockNode, selection.getEndOffset(), Node.class); Node endBlockChildNode = getEnclosingNode(endNode, blockChildren); Collection<Node> selectedNodes = getNodesFromTo(beginBlockChildNode, endBlockChildNode, blockChildren); if(isNodeContainedInNode(selectedNodes.toArray(new Node[selectedNodes.size()])[0], enclosingNode)) { BlockNode blockAroundSelected = NodeFactory.createBlockNode(selectedNodes.toArray(new Node[0])); // new BlockNode(NodeFactory.unionPositions(NodeProvider.unwrap(beginNode).getPosition(), NodeProvider.unwrap(endNode).getPosition())); blockAroundSelected.setPosition(NodeFactory.unionPositions(NodeProvider.unwrap(beginBlockChildNode).getPosition(), NodeProvider.unwrap(endBlockChildNode).getPosition())); return blockAroundSelected; } else if (beginNode.equals(endNode)) { return beginNode; } return enclosingNode; } public static boolean isNodeContainedInNode(Node containedNode, Node containingNode) { return (nodeContainsPosition(containingNode, containedNode.getPosition().getStartOffset()) && nodeContainsPosition(containingNode, containedNode.getPosition().getEndOffset())); } private static Collection<Node> getNodesFromTo(Node beginNode, Node endNode, Collection<Node> allNodes) { Collection<Node> affectedNodes = new ArrayList<Node>(); boolean between = false; for (Node aktNode : allNodes) { if (aktNode.equals(beginNode)) between = true; if (between) { affectedNodes.add(aktNode); } if (aktNode.equals(endNode)) between = false; } return affectedNodes; } private static Node getEnclosingNode(Node enclosedNode, Collection<Node> possibleEnclosingNodes) { for (Node aktNode : possibleEnclosingNodes) { if (nodeEnclosesNode(aktNode, enclosedNode)) return aktNode; } return null; } public static boolean nodeEnclosesNode(Node enclosingNode, Node enclosedNode) { ISourcePosition enclosingPos = enclosingNode.getPosition(); ISourcePosition enclosedPos = enclosedNode.getPosition(); return (enclosingPos.getStartOffset() <= enclosedPos.getStartOffset() && enclosingPos.getEndOffset() >= enclosedPos.getEndOffset()); } public static Node getEnclosingNode(Node rootNode, IRefactoringContext selection, Class... classes) { Collection<Node> enclosingNodes = getEnclosingNodes(rootNode, selection, classes); Node lastNode = null; Node secondLastNode = null; Node thirdLastNode = null; for (Node aktNode : enclosingNodes) { thirdLastNode = secondLastNode; secondLastNode = lastNode; lastNode = aktNode; } if (thirdLastNode != null && sameStartPosAndIsVariableNode(thirdLastNode, lastNode) && hasSamePosAndIsSelfAsignment(secondLastNode, thirdLastNode)) { return thirdLastNode; } if (secondLastNode != null && hasSamePosAndIsSelfAsignment(lastNode, secondLastNode)) { return secondLastNode; } return lastNode; } private static boolean sameStartPosAndIsVariableNode(Node firstNode, Node secondNode) { Class[] classes = { LocalAsgnNode.class, LocalVarNode.class, DAsgnNode.class, DVarNode.class, InstAsgnNode.class, InstVarNode.class, ClassVarAsgnNode.class, ClassVarNode.class, GlobalAsgnNode.class, GlobalVarNode.class}; boolean sameStart = firstNode.getPosition().getStartOffset() == secondNode.getPosition().getStartOffset(); boolean isFirstNodeVarNode = NodeUtil.nodeAssignableFrom(firstNode, classes); boolean isSecondNodeVarNode = NodeUtil.nodeAssignableFrom(secondNode, classes); return sameStart && isFirstNodeVarNode && isSecondNodeVarNode; } private static boolean hasSamePosAndIsSelfAsignment(Node probablyCallNode, Node probablyAsgnNode) { boolean sameStart = probablyCallNode.getPosition().getStartOffset() == probablyAsgnNode.getPosition().getStartOffset(); boolean sameEnd = probablyCallNode.getPosition().getEndOffset() == probablyAsgnNode.getPosition().getEndOffset(); boolean isAsgnNode = NodeUtil.nodeAssignableFrom(probablyAsgnNode, LocalAsgnNode.class, DAsgnNode.class, InstAsgnNode.class, ClassVarAsgnNode.class); boolean isCallNode = NodeUtil.nodeAssignableFrom(probablyCallNode, CallNode.class, AttrAssignNode.class); return sameStart && sameEnd && isCallNode && isAsgnNode; } public static Collection<Node> getEnclosingNodes(Node rootNode, IRefactoringContext selection, Class... classes) { Collection<Node> allNodes = NodeProvider.getAllNodes(rootNode); Collection<Node> enclosingStartNodes = getSelectedNodesOfType(allNodes, selection.getStartOffset(), classes); Collection<Node> enclosingNodes = new ArrayList<Node>(); for (Node aktNode : enclosingStartNodes) { if (nodeContainsPosition(aktNode, selection.getEndOffset())) { enclosingNodes.add(aktNode); } else { break; } } return enclosingNodes; } public static Node getSelectedNodeOfType(Node baseNode, int position, Class... klasses) { return getSelectedNodeOfType(NodeProvider.getAllNodes(baseNode), position, klasses); } public static Node getSelectedNodeOfType(Collection<? extends Node> nodes, int position, Class... klasses) { return returnLast(getSelectedNodesOfType(nodes, position, klasses)); } private static Node returnLast(Collection<Node> candidates) { if (candidates.size() <= 0) return null; Node candidate = candidates.toArray(new Node[candidates.size()])[0]; for (Node node : candidates) { if(node.getPosition().getEndOffset() <= candidate.getPosition().getEndOffset()) { candidate = node; } } // we assume that the last element in the list is the most accurate return candidate; } public static Collection<Node> getSelectedNodesOfType(Collection<? extends Node> nodes, int position, Class... klasses) { ArrayList<Node> candidates = new ArrayList<Node>(); for (Node n : nodes) { if (n.equals(NilImplicitNode.NIL)) continue; if (nodeContainsPosition(n, position) && !(n instanceof NewlineNode) && NodeUtil.nodeAssignableFrom(n, klasses)) { candidates.add(n); } } return candidates; } public static Collection<Node> getSelectedNodesOfType(Node baseNode, int position, Class... klasses) { return getSelectedNodesOfType(NodeProvider.getAllNodes(baseNode), position, klasses); } public static boolean nodeContainsPosition(Node n, int position) { return (position + CURSOR_TOLERANCE >= NodeUtil.subPositionUnion(n).getStartOffset() && position - CURSOR_TOLERANCE < NodeUtil.subPositionUnion(n).getEndOffset()); } public static final int CURSOR_TOLERANCE = 1; public static ClassNodeWrapper getSelectedClassNode(Node rootNode, int position) throws NoClassNodeException { Node enclosingClassNode = getSelectedNodeOfType(rootNode, position, ClassNode.class); PartialClassNodeWrapper partialClassNode = PartialClassNodeWrapper.getPartialClassNodeWrapper(enclosingClassNode, rootNode); ArrayList<ModuleNode> moduleNodes = new ArrayList<ModuleNode>(); Collection<Node> subNodes = NodeProvider.getSubNodes(rootNode, ModuleNode.class); for (Node node : subNodes) { if(nodeContainsPosition(node, position)) { moduleNodes.add((ModuleNode) node); } } partialClassNode.setEnclosingModules(moduleNodes); return new ClassNodeWrapper(partialClassNode); } public static AttrAccessorNodeWrapper getSelectedAccessorNode(Node baseNode, INameNode selectedAccessorNameNode) { Collection<AttrAccessorNodeWrapper> accessorNodes = NodeProvider.getAccessorNodes(baseNode); String selectedName = selectedAccessorNameNode.getName(); if (selectedName.charAt(0) == '@') { selectedName = selectedName.substring(1); } AttrAccessorNodeWrapper selectedAccessor = null; for (AttrAccessorNodeWrapper aktAccessorNode : accessorNodes) { if (aktAccessorNode.getAttrName().equals(selectedName)) { if (selectedAccessor == null) { selectedAccessor = aktAccessorNode; } else { selectedAccessor.addAccessorType(aktAccessorNode); } } } return selectedAccessor; } public static <T extends INodeWrapper> T getSelectedWrappedNode(Collection<T> candidates, int caretPosition) { T selected = null; for(T aktNode : candidates) { if(nodeContainsPosition(aktNode.getWrappedNode(), caretPosition)) { selected = getBestCandidate(selected, aktNode); } } return selected; } private static <T extends INodeWrapper> T getBestCandidate(T oldNode, T newNode) { if(oldNode == null) { return newNode; } if(nodeEnclosesNode(newNode.getWrappedNode(), oldNode.getWrappedNode())) { return oldNode; } return newNode; } }