package org.rubypeople.rdt.internal.corext.callhierarchy;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.jruby.ast.CallNode;
import org.jruby.ast.ClassNode;
import org.jruby.ast.DefnNode;
import org.jruby.ast.DefsNode;
import org.jruby.ast.FCallNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.Node;
import org.jruby.ast.VCallNode;
import org.jruby.lexer.yacc.IDESourcePosition;
import org.jruby.lexer.yacc.ISourcePosition;
import org.rubypeople.rdt.core.IMember;
import org.rubypeople.rdt.core.IMethod;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.ISourceRange;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.internal.core.parser.InOrderVisitor;
import org.rubypeople.rdt.internal.core.util.ASTUtil;
import org.rubypeople.rdt.internal.ui.RubyPlugin;
class CalleeAnalyzerVisitor extends InOrderVisitor {
private IMethod fMethod;
private CallSearchResultCollector fSearchResults;
private IProgressMonitor fProgressMonitor;
private int fMethodStartPosition;
private int fMethodEndPosition;
public CalleeAnalyzerVisitor(IMethod method, IProgressMonitor progressMonitor) {
fSearchResults = new CallSearchResultCollector();
this.fMethod = method;
this.fProgressMonitor = progressMonitor;
try {
ISourceRange sourceRange = method.getSourceRange();
this.fMethodStartPosition = sourceRange.getOffset();
this.fMethodEndPosition = fMethodStartPosition + sourceRange.getLength();
} catch (RubyModelException jme) {
RubyPlugin.log(jme);
}
}
private void addMethodCall(ISourcePosition pos) {
int offset = pos.getStartOffset();
int endOffset = pos.getEndOffset();
int length = endOffset - offset;
try {
IRubyElement[] elements = fMethod.getRubyScript().codeSelect(offset, length);
if (elements == null) return; // FIXME Only take first, what do we do?
for (int i = 0; i < elements.length; i++) {
if (elements[i] instanceof IMember) {
IMember member = (IMember) elements[i];
fSearchResults.addMember(fMethod, member, offset, endOffset, pos.getStartLine());
}
}
} catch (RubyModelException e) {
RubyPlugin.log(e);
}
}
/**
* Method getCallees.
*
* @return CallerElement
*/
public Map<String, MethodCall> getCallees() {
return fSearchResults.getCallers();
}
// FIXME When visiting types, check to see if we even need to traverse into the type...
@Override
public Object visitVCallNode(VCallNode iVisited) {
if (isNodeWithinMethod(iVisited)) {
addMethodCall(iVisited.getPosition());
}
return super.visitVCallNode(iVisited);
}
@Override
public Object visitFCallNode(FCallNode iVisited) {
if (isNodeWithinMethod(iVisited)) {
addMethodCall(iVisited.getPosition());// FIXME Only look up the hierarchy for the resolution
}
return super.visitFCallNode(iVisited);
}
@Override
public Object visitCallNode(CallNode iVisited) {
if (isNodeWithinMethod(iVisited)) {
if (iVisited.getName().equals("[]"))return super.visitCallNode(iVisited);
String receiver = ASTUtil.stringRepresentation(iVisited.getReceiverNode());
ISourcePosition original = iVisited.getPosition();
int start = original.getStartOffset() + receiver.length() + 1;
ISourcePosition pos = new IDESourcePosition(original.getFile(), original.getStartLine(), original.getEndLine(), start, original.getEndOffset());
addMethodCall(pos);
}
return super.visitCallNode(iVisited);
}
private boolean isNodeWithinMethod(Node node) {
int nodeStartPosition = node.getPosition().getStartOffset();
int nodeEndPosition = node.getPosition().getEndOffset();
if (nodeStartPosition < fMethodStartPosition) {
return false;
}
if (nodeEndPosition > fMethodEndPosition) {
return false;
}
return true;
}
@Override
public Object visitDefnNode(DefnNode iVisited) {
progressMonitorWorked(1);
return super.visitDefnNode(iVisited);
}
@Override
public Object visitDefsNode(DefsNode iVisited) {
progressMonitorWorked(1);
return super.visitDefsNode(iVisited);
}
@Override
public Object visitClassNode(ClassNode iVisited) {
progressMonitorWorked(1);
return super.visitClassNode(iVisited);
}
@Override
public Object visitModuleNode(ModuleNode iVisited) {
progressMonitorWorked(1);
return super.visitModuleNode(iVisited);
}
private void progressMonitorWorked(int work) {
if (fProgressMonitor != null) {
fProgressMonitor.worked(work);
if (fProgressMonitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}