package org.rubypeople.rdt.internal.core.search.matching; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.jruby.ast.CallNode; import org.jruby.ast.DefnNode; import org.jruby.ast.DefsNode; import org.jruby.ast.FCallNode; import org.jruby.ast.IArgumentNode; import org.jruby.ast.Node; import org.jruby.ast.VCallNode; import org.jruby.ast.types.INameNode; import org.rubypeople.rdt.core.IMember; import org.rubypeople.rdt.core.IMethod; import org.rubypeople.rdt.core.IParent; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.ISourceRange; import org.rubypeople.rdt.core.IType; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.internal.core.RubyScript; import org.rubypeople.rdt.internal.core.parser.InOrderVisitor; import org.rubypeople.rdt.internal.core.parser.RubyParser; public class MethodLocator extends PatternLocator { private MethodPattern pattern; public MethodLocator(MethodPattern pattern) { super(pattern); this.pattern = pattern; } @Override public void reportMatches(RubyScript script, MatchLocator locator) { if (!this.pattern.findReferences) { reportMatches((IParent) script, locator); } else { reportASTMatches(script, locator); } } private void reportASTMatches(final RubyScript script, final MatchLocator locator) { try { Node ast = script.lastGoodAST; if (ast == null) { ast = new RubyParser().parse(script.getElementName(), script.getSource()).getAST(); } final boolean findDeclarations = this.pattern.findDeclarations; new InOrderVisitor() { @Override public Object visitVCallNode(VCallNode iVisited) { match(iVisited, 0); return super.visitVCallNode(iVisited); } @Override public Object visitFCallNode(FCallNode iVisited) { match(iVisited, getArgumentsFromFunctionCall(iVisited).size()); return super.visitFCallNode(iVisited); } @Override public Object visitCallNode(CallNode iVisited) { match(iVisited, getArgumentsFromFunctionCall(iVisited).size()); return super.visitCallNode(iVisited); } @Override public Object visitDefnNode(DefnNode iVisited) { if (findDeclarations) matchDeclaration(iVisited, iVisited.getArgsNode().getRequiredArgsCount()); return super.visitDefnNode(iVisited); } @Override public Object visitDefsNode(DefsNode iVisited) { if (findDeclarations) matchDeclaration(iVisited, iVisited.getArgsNode().getRequiredArgsCount()); return super.visitDefsNode(iVisited); } private void match(Node iVisited, int arity) { String name = ((INameNode)iVisited).getName(); int accuracy = getAccuracy(name, arity); if (accuracy != IMPOSSIBLE_MATCH) { try { IRubyElement element = script.getElementAt(iVisited.getPosition().getStartOffset()); if (element == null) element = script; if (locator.encloses(element)) { IRubyElement binding = resolve(element, iVisited); int start = iVisited.getPosition().getStartOffset(); int length = iVisited.getPosition().getEndOffset() - start; boolean isConstructor = false; if (name.equals("new")) { isConstructor = true; } List<String> args = new ArrayList<String>(); if (iVisited instanceof IArgumentNode) { args = getArgumentsFromFunctionCall((IArgumentNode) iVisited); } locator.report(locator.newMethodReferenceMatch(element, binding, args, accuracy, start, length, isConstructor, iVisited)); } } catch (CoreException e) { RubyCore.log(e); } } } private IRubyElement resolve(IRubyElement element, Node visited) { // TODO resolve a method call to it's declaration! return element; } private void matchDeclaration(Node iVisited, int arity) { String name = ((INameNode)iVisited).getName(); int accuracy = getAccuracy(name, arity); if (accuracy != IMPOSSIBLE_MATCH) { try { IRubyElement element = script.getElementAt(iVisited.getPosition().getStartOffset()); if (element == null) element = script; if (locator.encloses(element)) { int start = iVisited.getPosition().getStartOffset(); int length = iVisited.getPosition().getEndOffset() - start; locator.report(locator.newDeclarationMatch(element, accuracy, start, length)); } } catch (CoreException e) { RubyCore.log(e); } } } }.acceptNode(ast); } catch(RubyModelException e) { RubyCore.log(e); } } private void reportMatches(IParent parent, MatchLocator locator) { try { IRubyElement[] children = parent.getChildren(); for (int i = 0; i < children.length; i++) { IRubyElement child = children[i]; if (child.isType(IRubyElement.METHOD) && locator.encloses(child)) { IMethod method = (IMethod) child; int accuracy = getAccuracy(method); if (accuracy != IMPOSSIBLE_MATCH) { IMember member = (IMember) child; ISourceRange range = member.getSourceRange(); try { locator.report(locator.newDeclarationMatch(child, accuracy, range.getOffset(), range.getLength())); } catch (CoreException e) { RubyCore.log(e); } } } if (child instanceof IParent) { IParent parentTwo = (IParent) child; reportMatches(parentTwo, locator); } } } catch (RubyModelException e) { RubyCore.log(e); } } private int getAccuracy(IMethod method) throws RubyModelException { int accuracy = getAccuracy(method.getElementName(), method.getParameterNames().length); if (accuracy == IMPOSSIBLE_MATCH) return accuracy; // Compare declaring type IType type = method.getDeclaringType(); char[] declaringTypeName = new char[0]; if (type != null) { declaringTypeName = type.getElementName().toCharArray(); } if (pattern.declaringSimpleName != null && !matchesName(pattern.declaringSimpleName, declaringTypeName)) return IMPOSSIBLE_MATCH; return ACCURATE_MATCH; } private int getAccuracy(String name, int arity) { // Verify method name if (!matchesName(this.pattern.selector, name.toCharArray())) return IMPOSSIBLE_MATCH; // Verify parameter count if (this.pattern.parameterNames != null) { int length = this.pattern.parameterNames.length; if (length != arity) return IMPOSSIBLE_MATCH; } // Method may match pattern return ACCURATE_MATCH; } }