package org.rubypeople.rdt.internal.ui.search;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.search.ui.text.Match;
import org.jruby.ast.ArgumentNode;
import org.jruby.ast.BlockArgNode;
import org.jruby.ast.BlockNode;
import org.jruby.ast.CallNode;
import org.jruby.ast.ClassNode;
import org.jruby.ast.ClassVarAsgnNode;
import org.jruby.ast.ClassVarDeclNode;
import org.jruby.ast.ClassVarNode;
import org.jruby.ast.Colon2Node;
import org.jruby.ast.Colon3Node;
import org.jruby.ast.ConstDeclNode;
import org.jruby.ast.ConstNode;
import org.jruby.ast.DAsgnNode;
import org.jruby.ast.DVarNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.FCallNode;
import org.jruby.ast.GlobalAsgnNode;
import org.jruby.ast.GlobalVarNode;
import org.jruby.ast.InstAsgnNode;
import org.jruby.ast.InstVarNode;
import org.jruby.ast.LocalAsgnNode;
import org.jruby.ast.LocalVarNode;
import org.jruby.ast.MethodDefNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.Node;
import org.jruby.ast.ReturnNode;
import org.jruby.ast.SymbolNode;
import org.jruby.ast.VCallNode;
import org.jruby.ast.types.INameNode;
import org.jruby.lexer.yacc.IDESourcePosition;
import org.jruby.lexer.yacc.ISourcePosition;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.internal.core.util.ASTUtil;
import org.rubypeople.rdt.internal.ti.util.FirstPrecursorNodeLocator;
import org.rubypeople.rdt.internal.ti.util.INodeAcceptor;
import org.rubypeople.rdt.internal.ti.util.OffsetNodeLocator;
import org.rubypeople.rdt.internal.ti.util.ScopedNodeLocator;
import org.rubypeople.rdt.internal.ui.RubyPlugin;
/**
* Implements "Mark Occurences" feature
*
* @author Jason Morrison
*
*/
public class OccurrencesFinder extends AbstractOccurencesFinder {
// Root of the document to search
private Node root;
// Originating node; corresponds to cursor selection
private Node fSelectedNode;
private List<Node> fUsages = new ArrayList<Node>();
private List<Node> fWriteUsages = new ArrayList<Node>();
public String getJobLabel() {
return SearchMessages.OccurrencesFinder_searchfor;
}
public String getUnformattedPluralLabel() {
return SearchMessages.OccurrencesFinder_label_plural;
}
public String getUnformattedSingularLabel() {
return SearchMessages.OccurrencesFinder_label_singular;
}
public void collectOccurrenceMatches(IRubyElement element,
IDocument document, Collection resultingMatches) {
HashMap lineToGroup = new HashMap();
for (Iterator iter = fUsages.iterator(); iter.hasNext();) {
Node node = (Node) iter.next();
ISourcePosition position;
try {
position = getPositionOfName(node);
} catch (RuntimeException e) {
RubyPlugin.log(e);
continue;
}
if (position == null)
continue;
int startPosition = position.getStartOffset();
if (startPosition < 0)
continue;
int length = position.getEndOffset() - position.getStartOffset();
try {
boolean isWriteAccess = fWriteUsages.contains(node);
int line = document.getLineOfOffset(startPosition);
Integer lineInteger = new Integer(line);
OccurrencesGroupKey groupKey = (OccurrencesGroupKey) lineToGroup
.get(lineInteger);
if (groupKey == null) {
IRegion region = document.getLineInformation(line);
String lineContents = document.get(region.getOffset(),
region.getLength()).trim();
groupKey = new OccurrencesGroupKey(element, line,
lineContents, isWriteAccess,
isVariable(fSelectedNode));
lineToGroup.put(lineInteger, groupKey);
} else if (isWriteAccess) {
// a line with read an write access is considered as write
// access:
groupKey.setWriteAccess(true);
}
Match match = new Match(groupKey, startPosition, length);
resultingMatches.add(match);
} catch (BadLocationException e) {
// nothing
}
}
}
private boolean isVariable(Node node) {
return ASTUtil.isVariable(node);
}
public String initialize(Node root, int offset, int length) {
if (root == null) {
return null;
}
this.root = root;
this.fSelectedNode = OffsetNodeLocator.Instance().getNodeAtOffset(root,
offset);
if (fSelectedNode == null) {
return SearchMessages.OccurrencesFinder_no_element;
}
// if (fSelectedNode.getPosition().getEndOffset() > offset + length) {
// // Selection spans nodes; not handling that for now.
// return "Selection spans nodes; can only search for a single node.";
// }
fUsages.clear();
fWriteUsages.clear();
return null;
}
/**
* Determines the kind of originating node, and collects occurrences
* accordingly
*/
public List<Position> perform() {
// Mark no occurrences if root is null (AST couldn't be parsed
// correctly.)
if (root == null)
return new LinkedList<Position>();
if (fSelectedNode == null)
return new LinkedList<Position>();
if (fMarkLocalVariableOccurrences && isLocalVarRef(fSelectedNode)) {
pushLocalVarRefs(root, fSelectedNode, fUsages);
}
if (fMarkLocalVariableOccurrences && isDVarRef(fSelectedNode)) {
pushDVarRefs(root, fSelectedNode, fUsages);
}
if (fMarkLocalVariableOccurrences && isInstanceVarRef(fSelectedNode)) {
pushInstVarRefs(root, fSelectedNode, fUsages);
}
if (fMarkLocalVariableOccurrences && isClassVarRef(fSelectedNode)) {
pushClassVarRefs(root, fSelectedNode, fUsages);
}
if (fMarkLocalVariableOccurrences && isGlobalVarRef(fSelectedNode)) {
pushGlobalVarRefs(root, fSelectedNode, fUsages);
}
if (fMarkConstantOccurrences && fSelectedNode instanceof SymbolNode) {
pushSymbolRefs(root, fSelectedNode, fUsages);
}
if (fMarkMethodOccurrences
&& (isMethodRefNode(fSelectedNode) || isMethodDefNode(fSelectedNode))) {
pushMethodRefs(root, fSelectedNode, fUsages);
}
if (fMarkConstantOccurrences && isConstRef(fSelectedNode)) {
pushConstRefs(root, fSelectedNode, fUsages);
}
if (fMarkTypeOccurrences && isTypeRef(fSelectedNode)) {
pushTypeRefs(root, fSelectedNode, fUsages);
}
if (fMarkMethodExitPoints) {
pushReturns(root, fSelectedNode, fUsages);
}
// Convert ISourcePosition to IPosition
List<Position> positions = new LinkedList<Position>();
for (Node node : fUsages) {
try {
ISourcePosition occurrence = getPositionOfName(node);
if (occurrence == null)
continue;
Position position = new Position(occurrence.getStartOffset(),
occurrence.getEndOffset() - occurrence.getStartOffset());
positions.add(position);
} catch (RuntimeException re) {
RubyPlugin.log(re);
}
}
// Uniqueify positions
positions = new LinkedList<Position>(new HashSet<Position>(positions));
return positions;
}
private boolean isMethodRefNode(Node selectedNode) {
return selectedNode instanceof VCallNode
|| selectedNode instanceof FCallNode
|| selectedNode instanceof CallNode;
}
// ****************************************************************************
// *
// * Reference kind definitions
// *
// ****************************************************************************
/**
* Determines whether a given node is a local variable reference
*
* @param node
* @return
*/
private boolean isLocalVarRef(Node node) {
return ((node instanceof LocalAsgnNode)
|| (node instanceof ArgumentNode) || (node instanceof LocalVarNode));
}
/**
* Determines whether a given node is a dynamic variable reference
*
* @param node
* @return
*/
private boolean isDVarRef(Node node) {
return ((node instanceof DVarNode) || (node instanceof DAsgnNode));
}
/**
* Determines whether a given node is an instance variable reference
*
* @param node
* @return
*/
private boolean isInstanceVarRef(Node node) {
return ((node instanceof InstAsgnNode) || (node instanceof InstVarNode));
}
/**
* Determines whether a given node is a class variable reference
*
* @param node
* @return
*/
private boolean isClassVarRef(Node node) {
return ((node instanceof ClassVarNode)
|| (node instanceof ClassVarAsgnNode) || (node instanceof ClassVarDeclNode));
}
/**
* Determines whether a given node is a global variable reference
*
* @param node
* @return
*/
private boolean isGlobalVarRef(Node node) {
return ((node instanceof GlobalAsgnNode) || (node instanceof GlobalVarNode));
}
/**
* Determines whether a given node is a constant reference (constant)
*
* @param node
* @return
*/
private boolean isConstRef(Node node) {
return (node instanceof ConstNode) || (node instanceof ConstDeclNode);
}
/**
* Determines whether a given node is a type reference (class, module)
*
* @param node
* @return
*/
private boolean isTypeRef(Node node) {
// TODO: Classes can be referred to as a ConstNode; i.e. "class
// Klass;end; k = Klass.new" the last reference is a ConstNode, not a
// ClassNode. Special way to handle this?
return ((node instanceof ClassNode) || (node instanceof ModuleNode) || (node instanceof ConstNode));
}
// ****************************************************************************
// *
// * Worker methods - handles delegation of occurrence searches
// *
// ****************************************************************************
/**
* Collects all corresponding local variable occurrences
*
* @param root
* Root node to search
* @param fSelectedNode
* Originating node
* @param occurrences
*/
private void pushLocalVarRefs(Node root, Node orig, List<Node> occurrences) {
// Find the search space
Node searchSpace = FirstPrecursorNodeLocator.Instance()
.findFirstPrecursor(root, orig.getPosition().getStartOffset(),
new INodeAcceptor() {
public boolean doesAccept(Node node) {
return ((node instanceof DefnNode) || (node instanceof DefsNode)); // TODO:
// Block
// Body?
}
});
// If no enclosing node found, search the entire space
if (searchSpace == null) {
searchSpace = root;
}
// Finalize searchSpace because Java's scoping rules are the awesome
final Node finalSearchSpace = searchSpace;
// Get name of local variable reference
final String origName = ASTUtil.getNameReflectively(orig);
// Find all pertinent nodes
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(searchSpace, new INodeAcceptor() {
public boolean doesAccept(Node node) {
String name = ASTUtil.getNameReflectively(node);
return (name != null && name.equals(origName));
}
});
// Scrape position from pertinent nodes
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
if (searchResult instanceof LocalAsgnNode)
fWriteUsages.add(searchResult);
}
}
/**
* Collects all corresponding dynamic variable occurrences
*
* @param root
* Root node to search
* @param fSelectedNode
* Originating node
* @param occurrences
*/
private void pushDVarRefs(Node root, Node orig, List<Node> occurrences) {
// Find the search space
Node searchSpace = FirstPrecursorNodeLocator.Instance()
.findFirstPrecursor(root, orig.getPosition().getStartOffset(),
new INodeAcceptor() {
public boolean doesAccept(Node node) {
return ((node instanceof DefnNode) || (node instanceof DefsNode)); // TODO:
// Block
// Body?
}
});
// If no enclosing node found, search the entire space
if (searchSpace == null) {
searchSpace = root;
}
// Get name of local variable reference
final String origName = ASTUtil.getNameReflectively(orig);
// Find all pertinent nodes
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(searchSpace, new INodeAcceptor() {
public boolean doesAccept(Node node) {
if (isDVarRef(node)) {
String name = ASTUtil.getNameReflectively(node);
return (name != null && name.equals(origName));
}
return false;
}
});
// Scrape position from pertinent nodes
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
if (searchResult instanceof DAsgnNode)
fWriteUsages.add(searchResult);
}
}
/**
* Collects all instance variable occurrences
*
* @param root
* @param fSelectedNode
* @param occurrences
*/
private void pushInstVarRefs(Node root, Node orig, List<Node> occurrences) {
Node searchSpace = determineSearchSpace(root, orig);
// Finalize searchSpace because Java's scoping rules are the awesome
// TODO: not needed?
// final Node finalSearchSpace = searchSpace;
// Get name of local variable reference
final String origName = ASTUtil.getNameReflectively(orig);
// Find all pertinent nodes
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(searchSpace, new INodeAcceptor() {
public boolean doesAccept(Node node) {
if (isInstanceVarRef(node)) {
String name = ASTUtil.getNameReflectively(node);
return (name != null && name.equals(origName));
}
return false;
}
});
// Scrape position from pertinent nodes
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
if (searchResult instanceof InstAsgnNode)
fWriteUsages.add(searchResult);
}
}
private Node determineSearchSpace(Node root, Node orig) {
// Find the name of the enclosing class
ClassNode enclosingClass = (ClassNode) FirstPrecursorNodeLocator
.Instance().findFirstPrecursor(root,
orig.getPosition().getStartOffset(),
new INodeAcceptor() {
public boolean doesAccept(Node node) {
return (node instanceof ClassNode);
}
});
// If no enclosing class is identified, search root.
if (enclosingClass == null) {
return root;
}
// Find the search space - all ClassNodes for that name within root
// scope
else {
final String className = getClassNodeName(enclosingClass);
List<Node> classNodes = ScopedNodeLocator.Instance()
.findNodesInScope(root, new INodeAcceptor() {
public boolean doesAccept(Node node) {
if (node instanceof ClassNode) {
return getClassNodeName((ClassNode) node)
.equals(className);
}
return false;
}
});
BlockNode blockNode = new BlockNode(new IDESourcePosition());
for (Node classNode : classNodes) {
blockNode.add(classNode);
}
return blockNode;
}
}
/**
* Collects all class variable occurrences
*
* @param root
* @param fSelectedNode
* @param occurrences
*/
private void pushClassVarRefs(Node root, Node orig, List<Node> occurrences) {
Node searchSpace = determineSearchSpace(root, orig);
// Finalize searchSpace because Java's scoping rules are the awesome
// todo: not needed?
// final Node finalSearchSpace = searchSpace;
// Get name of local variable reference
final String origName = ASTUtil.getNameReflectively(orig);
// Find all pertinent nodes
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(searchSpace, new INodeAcceptor() {
public boolean doesAccept(Node node) {
if (isClassVarRef(node)) {
String name = ASTUtil.getNameReflectively(node);
return (name != null && name.equals(origName));
}
return false;
}
});
// Scrape position from pertinent nodes
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
if ((searchResult instanceof ClassVarAsgnNode)
|| (searchResult instanceof ClassVarDeclNode))
fWriteUsages.add(searchResult);
}
}
/**
* Collects all global variable occurrences
*
* @param root
* @param fSelectedNode
* @param occurrences
*/
private void pushGlobalVarRefs(Node root, Node orig, List<Node> occurrences) {
final Node searchSpace = root;
final String origName = ASTUtil.getNameReflectively(orig);
// Find all pertinent nodes
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(searchSpace, new INodeAcceptor() {
public boolean doesAccept(Node node) {
return isGlobalVarRef(node)
&& ASTUtil.getNameReflectively(node).equals(
origName);
}
});
// Scrape position from pertinent nodes
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
if (searchResult instanceof GlobalAsgnNode)
fWriteUsages.add(searchResult);
}
}
/**
* Collects all symbol occurrences
*
* @param root
* @param fSelectedNode
* @param occurrences
*/
private void pushSymbolRefs(Node root, Node orig, List<Node> occurrences) {
final Node searchSpace = root;
final String origName = ((SymbolNode) orig).getName();
// Find all pertinent nodes
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(searchSpace, new INodeAcceptor() {
public boolean doesAccept(Node node) {
return (node instanceof SymbolNode)
&& ((SymbolNode) node).getName().equals(
origName);
}
});
// Scrape position from pertinent nodes
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
}
}
// todo: complete
// private void pushMethodRefs( Node root, Node fSelectedNode,
// List<ISourcePosition>
// occurrences) {
//
// // DefnNode DefsNode CallNode VCallNode
//
// System.out.println("Finding occurrences for method reference node " +
// fSelectedNode.toString() );
//
// final Node searchSpace = root;
// String origName = getMethodRefName(fSelectedNode);
//
// // If fSelectedNode is a method definition, find all occurrences to that
// selector
// for the fSelectedNode's enclosing type
// if ( fSelectedNode instanceof DefnNode || fSelectedNode instanceof
// DefsNode )
// {
// ((DefnNode)fSelectedNode).g
// }
//
// Node receiver = getMethodReceiver(fSelectedNode);
// }
/**
* Collects all pertinent const occurrences
*/
private void pushConstRefs(Node root, Node orig, List<Node> occurrences) {
if (!isConstRef(orig)) {
return;
}
final String matchName = ASTUtil.getNameReflectively(orig);
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(root, new INodeAcceptor() {
public boolean doesAccept(Node node) {
if (isConstRef(node)) {
return ASTUtil.getNameReflectively(node).equals(
matchName);
}
return false;
}
});
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
if (searchResult instanceof ConstDeclNode)
fWriteUsages.add(searchResult);
}
}
/**
* Collects all pertinent method calls
*/
private void pushMethodRefs(Node root, Node orig, List<Node> occurrences) {
if (!isMethodRefNode(orig) && !isMethodDefNode(orig)) {
return;
}
final String matchName = ASTUtil.getNameReflectively(orig);
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(root, new INodeAcceptor() {
public boolean doesAccept(Node node) {
if (isMethodRefNode(node) || isMethodDefNode(node)) {
return ASTUtil.getNameReflectively(node).equals(
matchName);
}
return false;
}
});
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
}
}
protected boolean isMethodDefNode(Node node) {
return node instanceof MethodDefNode;
}
/**
* Collects all pertinent type ref occurrences
*/
private void pushTypeRefs(Node root, Node orig, List<Node> occurrences) {
if (!isTypeRef(orig)) {
return;
}
final String matchName = ASTUtil.getNameReflectively(orig);
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(root, new INodeAcceptor() {
public boolean doesAccept(Node node) {
if (isTypeRef(node)) {
return getTypeRefName(node).equals(matchName);
}
return false;
}
});
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
}
}
private void pushReturns(Node root, Node orig, List<Node> occurrences) {
// TODO Combine most of this stuff with the stuff in pushLocalVarRefs
// Find the search space
Node searchSpace = FirstPrecursorNodeLocator.Instance()
.findFirstPrecursor(root, orig.getPosition().getStartOffset(),
new INodeAcceptor() {
public boolean doesAccept(Node node) {
return ((node instanceof DefnNode) || (node instanceof DefsNode)); // TODO:
// Block
// Body?
}
});
// If no enclosing node found, search the entire space
if (searchSpace == null) {
searchSpace = root;
}
// Find all return nodes
List<Node> searchResults = ScopedNodeLocator.Instance()
.findNodesInScope(searchSpace, new INodeAcceptor() {
public boolean doesAccept(Node node) {
return (node instanceof ReturnNode);
}
});
// Scrape position from pertinent nodes
for (Node searchResult : searchResults) {
occurrences.add(searchResult);
}
}
// ****************************************************************************
// *
// * Utility methods
// *
// ****************************************************************************
/**
* Gets the position of the name for the specified node.
*
* @param node
* Node that responds to getName() or some variant
* @return ISourcePosition that holds the name of the node
*/
private ISourcePosition getPositionOfName(Node node) {
ISourcePosition pos = node.getPosition();
// TODO refactor the getting-of-name
String name = null;
if (node instanceof ReturnNode) {
return node.getPosition();
} else if (isLocalVarRef(node) || isDVarRef(node)
|| isInstanceVarRef(node) || isGlobalVarRef(node)
|| isClassVarRef(node) || isConstRef(node)
|| node instanceof BlockArgNode) {
name = ASTUtil.getNameReflectively(node);
} else if (node instanceof ClassNode) {
return ((ClassNode) node).getCPath().getPosition();
} else if (node instanceof ModuleNode) {
return ((ModuleNode) node).getCPath().getPosition();
} else if (node instanceof SymbolNode) {
// XXX: This is a hack to get around improper offsets in my JRuby
// copy; ":foo" returns offset for ":fo", so compensate by adding
// one
name = ((SymbolNode) node).getName();
return new IDESourcePosition(pos.getFile(), pos.getStartLine(), pos
.getEndLine(), pos.getStartOffset(), pos.getStartOffset()
+ name.length() + 1);
} else if (node instanceof CallNode) {
CallNode vcall = (CallNode) node;
name = vcall.getName();
Node receiver = vcall.getReceiverNode();
int start = receiver.getPosition().getEndOffset() + 1;
return new IDESourcePosition(pos.getFile(), pos.getStartLine(), pos
.getEndLine(), start, start + name.length());
} else if (node instanceof MethodDefNode) {
MethodDefNode def = (MethodDefNode) node;
return def.getNameNode().getPosition();
} else if (node instanceof INameNode) {
INameNode vcall = (INameNode) node;
name = vcall.getName();
return new IDESourcePosition(pos.getFile(), pos.getStartLine(), pos
.getEndLine(), pos.getStartOffset(), pos.getStartOffset()
+ name.length());
}
if (name == null) {
throw new RuntimeException("Couldn't get the name for: "
+ node.toString());
}
return new IDESourcePosition(pos.getFile(), pos.getStartLine(), pos
.getEndLine(), pos.getStartOffset(), pos.getStartOffset()
+ name.length());
}
/**
* Helper method to get the class name froma ClassNode
*
* @param classNode
* @return
*/
private String getClassNodeName(ClassNode classNode) {
if (classNode.getCPath() instanceof Colon2Node) {
Colon2Node c2node = (Colon2Node) classNode.getCPath();
return c2node.getName();
} else if (classNode.getCPath() instanceof Colon3Node) {
Colon3Node c2node = (Colon3Node) classNode.getCPath();
return c2node.getName();
}
throw new RuntimeException(
"ClassNode.getCPath() returned other than Colon2Node: "
+ classNode.getCPath().toString());
}
/**
* Helper method to get the class name from a ModuleNode
*
* @param classNode
* @return
*/
private String getModuleNodeName(ModuleNode moduleNode) {
if (moduleNode.getCPath() instanceof Colon2Node) {
Colon2Node c2node = (Colon2Node) moduleNode.getCPath();
return c2node.getName();
} else if (moduleNode.getCPath() instanceof Colon3Node) {
Colon3Node c2node = (Colon3Node) moduleNode.getCPath();
return c2node.getName();
}
throw new RuntimeException(
"ModuleNode.getCPath() returned other than Colon2Node: "
+ moduleNode.getCPath().toString());
}
/**
* Helper method to get the class name from a const ref node (Class/Module)
*
* @param node
* @return
*/
private String getTypeRefName(Node node) {
if (node instanceof ClassNode) {
return getClassNodeName((ClassNode) node);
}
if (node instanceof ModuleNode) {
return getModuleNodeName((ModuleNode) node);
}
return ASTUtil.getNameReflectively(node);
}
public String getElementName() {
if (fSelectedNode != null) {
return ASTUtil.stringRepresentation(fSelectedNode);
}
return null;
}
}