KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > ruby > DeclarationFinder


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.ruby;
20
21 import java.io.IOException JavaDoc;
22 import java.net.MalformedURLException JavaDoc;
23 import java.net.URL JavaDoc;
24 import java.util.Collections JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Set JavaDoc;
28
29 import javax.swing.text.BadLocationException JavaDoc;
30 import javax.swing.text.Document JavaDoc;
31
32 import org.jruby.ast.AliasNode;
33 import org.jruby.ast.ArgsNode;
34 import org.jruby.ast.ArgumentNode;
35 import org.jruby.ast.CallNode;
36 import org.jruby.ast.ClassNode;
37 import org.jruby.ast.ClassVarDeclNode;
38 import org.jruby.ast.ClassVarNode;
39 import org.jruby.ast.Colon2Node;
40 import org.jruby.ast.ConstDeclNode;
41 import org.jruby.ast.ConstNode;
42 import org.jruby.ast.DAsgnNode;
43 import org.jruby.ast.DVarNode;
44 import org.jruby.ast.DefnNode;
45 import org.jruby.ast.DefsNode;
46 import org.jruby.ast.FCallNode;
47 import org.jruby.ast.GlobalAsgnNode;
48 import org.jruby.ast.GlobalVarNode;
49 import org.jruby.ast.InstAsgnNode;
50 import org.jruby.ast.InstVarNode;
51 import org.jruby.ast.ListNode;
52 import org.jruby.ast.LocalAsgnNode;
53 import org.jruby.ast.LocalVarNode;
54 import org.jruby.ast.Node;
55 import org.jruby.ast.SymbolNode;
56 import org.jruby.ast.VCallNode;
57 import org.jruby.ast.types.INameNode;
58 import org.netbeans.api.gsf.CompilationInfo;
59 import org.netbeans.api.gsf.DeclarationFinder.DeclarationLocation;
60 import org.netbeans.api.gsf.GsfTokenId;
61 import org.netbeans.api.gsf.Index.NameKind;
62 import org.netbeans.api.gsf.OffsetRange;
63 import org.netbeans.api.lexer.Token;
64 import org.netbeans.api.lexer.TokenHierarchy;
65 import org.netbeans.api.lexer.TokenId;
66 import org.netbeans.api.lexer.TokenSequence;
67 import org.netbeans.editor.BaseDocument;
68 import org.netbeans.modules.ruby.elements.IndexedClass;
69 import org.netbeans.modules.ruby.elements.IndexedElement;
70 import org.netbeans.modules.ruby.elements.IndexedMethod;
71 import org.netbeans.modules.ruby.lexer.LexUtilities;
72 import org.netbeans.modules.ruby.lexer.LexUtilities.Call;
73 import org.netbeans.modules.ruby.lexer.RubyCommentTokenId;
74 import org.netbeans.modules.ruby.lexer.RubyTokenId;
75 import org.openide.filesystems.FileObject;
76 import org.openide.util.Exceptions;
77
78
79 /**
80  * Find a declaration from an element in the JRuby AST.
81  *
82  * @todo Look at the target to see which method to choose. For example, if
83  * you do Foo.new, I should locate "initialize" in Foo, not somewhere else.
84  * @todo Don't include inexact matches like alias nodes when searching first;
85  * only if a search for actual declaration nodes fail should I revert to looking
86  * for aliases!
87  * @author Tor Norbye
88  */

89 public class DeclarationFinder implements org.netbeans.api.gsf.DeclarationFinder {
90     /** When true, don't match alias nodes as reads. Used during traversal of the AST. */
91     private boolean ignoreAlias;
92
93     /** Creates a new instance of DeclarationFinder */
94     public DeclarationFinder() {
95     }
96
97     public OffsetRange getReferenceSpan(Document JavaDoc doc, int offset) {
98         TokenHierarchy<Document JavaDoc> th = TokenHierarchy.get(doc);
99         TokenSequence<?extends TokenId> ts = th.tokenSequence(RubyTokenId.language());
100
101         if (ts == null) {
102             return OffsetRange.NONE;
103         }
104
105         ts.move(offset);
106
107         if (!ts.moveNext() && !ts.movePrevious()) {
108             return OffsetRange.NONE;
109         }
110
111         // Determine whether the caret position is right between two tokens
112
boolean isBetween = (offset == ts.offset());
113
114         OffsetRange range = getReferenceSpan(ts, th, offset);
115
116         if ((range == OffsetRange.NONE) && isBetween) {
117             // The caret is between two tokens, and the token on the right
118
// wasn't linkable. Try on the left instead.
119
if (ts.movePrevious()) {
120                 range = getReferenceSpan(ts, th, offset);
121             }
122         }
123
124         return range;
125     }
126
127     private OffsetRange getReferenceSpan(TokenSequence<?extends TokenId> ts,
128         TokenHierarchy<Document JavaDoc> th, int offset) {
129         Token<?extends TokenId> token = ts.token();
130         TokenId id = token.id();
131
132         // TODO: Tokens.SUPER, Tokens.THIS, Tokens.SELF ...
133
if ((id == GsfTokenId.IDENTIFIER) || (id == GsfTokenId.CLASS_VAR) ||
134                 (id == GsfTokenId.GLOBAL_VAR) || (id == GsfTokenId.CONSTANT) ||
135                 (id == GsfTokenId.TYPE_SYMBOL) || (id == GsfTokenId.INSTANCE_VAR)) {
136             return new OffsetRange(ts.offset(), ts.offset() + token.length());
137         }
138
139         // Look for embedded RDoc comments:
140
TokenSequence<?extends TokenId> embedded = ts.embedded();
141
142         if (embedded != null) {
143             ts = embedded;
144             embedded.move(offset);
145
146             if (embedded.moveNext()) {
147                 Token<?extends TokenId> embeddedToken = embedded.token();
148
149                 if (embeddedToken.id() == RubyCommentTokenId.COMMENT_LINK) {
150                     return new OffsetRange(embedded.offset(),
151                         embedded.offset() + embeddedToken.length());
152                 }
153             }
154         }
155
156         // Allow hyperlinking of some literal strings too, such as require strings
157
if ((id == RubyTokenId.QUOTED_STRING_LITERAL) || (id == RubyTokenId.STRING_LITERAL)) {
158             int requireStart = LexUtilities.getRequireStringOffset(offset, th);
159
160             if (requireStart != -1) {
161                 String JavaDoc require = LexUtilities.getStringAt(offset, th);
162
163                 if (require != null) {
164                     return new OffsetRange(requireStart, requireStart + require.length());
165                 }
166             }
167         }
168
169         return OffsetRange.NONE;
170     }
171
172     public DeclarationLocation findDeclaration(CompilationInfo info, int caretOffset) {
173         // Is this a require-statement? If so, jump to the required file
174
try {
175             Document JavaDoc doc = info.getDocument();
176
177             // Determine the bias (if the caret is between two tokens, did we
178
// click on a link for the left or the right?
179
OffsetRange range = getReferenceSpan(doc, caretOffset);
180
181             if (range == OffsetRange.NONE) {
182                 return DeclarationLocation.NONE;
183             }
184
185             boolean leftSide = range.getEnd() <= caretOffset;
186
187             Node root = AstUtilities.getRoot(info);
188
189             if (root == null) {
190                 // No parse tree - try to just use the syntax info to do a simple index lookup
191
// for methods and classes
192
String JavaDoc text = doc.getText(range.getStart(), range.getLength());
193                 RubyIndex index = RubyIndex.get(info.getIndex());
194
195                 if ((index == null) || (text.length() == 0)) {
196                     return DeclarationLocation.NONE;
197                 }
198
199                 if (Character.isUpperCase(text.charAt(0))) {
200                     // A Class or Constant?
201
Set JavaDoc<IndexedClass> classes =
202                         index.getClasses(text, NameKind.EXACT_NAME, true, false, false);
203
204                     if (classes.size() == 0) {
205                         return DeclarationLocation.NONE;
206                     }
207
208                     try {
209                         IndexedClass candidate =
210                             findBestClassMatch(classes, (BaseDocument)info.getDocument(), null,
211                                 null, index);
212
213                         if (candidate != null) {
214                             IndexedElement com = candidate;
215                             Node node = AstUtilities.getForeignNode(com);
216
217                             return new DeclarationLocation(com.getFile().getFileObject(),
218                                 node.getPosition().getStartOffset());
219                         }
220                     } catch (IOException JavaDoc ioe) {
221                         Exceptions.printStackTrace(ioe);
222                     }
223                 } else {
224                     // A method?
225
Set JavaDoc<IndexedMethod> methods = index.getMethods(text, null, NameKind.EXACT_NAME);
226
227                     try {
228                         IndexedMethod candidate =
229                             findBestMethodMatch(text, methods, (BaseDocument)info.getDocument(),
230                                 caretOffset, null, null, index);
231
232                         if (candidate != null) {
233                             IndexedElement com = candidate;
234                             Node node = AstUtilities.getForeignNode(com);
235
236                             return new DeclarationLocation(com.getFile().getFileObject(),
237                                 node.getPosition().getStartOffset());
238                         }
239                     } catch (IOException JavaDoc ioe) {
240                         Exceptions.printStackTrace(ioe);
241                     }
242                 } // TODO: @ - field?
243

244                 return DeclarationLocation.NONE;
245             }
246
247             RubyIndex index = RubyIndex.get(info.getIndex());
248
249             TokenHierarchy<Document JavaDoc> th = TokenHierarchy.get(doc);
250
251             int tokenOffset = caretOffset;
252
253             if (leftSide && (tokenOffset > 0)) {
254                 tokenOffset--;
255             }
256
257             // See if the hyperlink is for the string in a require statement
258
int requireStart = LexUtilities.getRequireStringOffset(tokenOffset, th);
259
260             if (requireStart != -1) {
261                 String JavaDoc require = LexUtilities.getStringAt(tokenOffset, th);
262
263                 if (require != null) {
264                     String JavaDoc file = index.getRequiredFileUrl(require);
265
266                     if (file != null) {
267                         FileObject fo = RubyIndex.getFileObject(file);
268
269                         return new DeclarationLocation(fo, 0);
270                     }
271                 }
272
273                 // It's in a require string so no possible other matches
274
return DeclarationLocation.NONE;
275             }
276
277             AstPath path = new AstPath(root, caretOffset);
278             Node closest = path.leaf();
279
280             // See if the hyperlink is over a method reference in an rdoc comment
281
DeclarationLocation rdoc = findRDocMethod(doc, tokenOffset, root, path);
282
283             if (rdoc != DeclarationLocation.NONE) {
284                 return fix(rdoc, info);
285             }
286
287             // Look at the parse tree; find the closest node and jump based on the context
288
if (closest instanceof LocalVarNode || closest instanceof LocalAsgnNode) {
289                 // A local variable read or a parameter read, or an assignment to one of these
290
String JavaDoc name = ((INameNode)closest).getName();
291                 Node method = AstUtilities.findLocalScope(closest, path);
292
293                 return fix(findLocal(method, name), info);
294             } else if (closest instanceof DVarNode) {
295                 // A dynamic variable read or assignment
296
String JavaDoc name = ((DVarNode)closest).getName(); // Does not implement INameNode
297
Node block = AstUtilities.findDynamicScope(closest, path);
298
299                 return fix(findDynamic(block, name), info);
300             } else if (closest instanceof DAsgnNode) {
301                 // A dynamic variable read or assignment
302
String JavaDoc name = ((INameNode)closest).getName();
303                 Node block = AstUtilities.findDynamicScope(closest, path);
304
305                 return fix(findDynamic(block, name), info);
306             } else if (closest instanceof InstVarNode) {
307                 // A field variable read
308
return fix(findInstance(root, ((INameNode)closest).getName()), info);
309             } else if (closest instanceof ClassVarNode) {
310                 // A class variable read
311
return fix(findClassVar(root, ((ClassVarNode)closest).getName()), info);
312             } else if (closest instanceof GlobalVarNode) {
313                 // A global variable read
314
String JavaDoc name = ((GlobalVarNode)closest).getName(); // GlobalVarNode does not implement INameNode
315

316                 return fix(findGlobal(root, name), info);
317             } else if (closest instanceof FCallNode || closest instanceof VCallNode ||
318                     closest instanceof CallNode) {
319                 // A method call
320
String JavaDoc name = ((INameNode)closest).getName();
321
322                 Call call = LexUtilities.getCallType(doc, th, caretOffset);
323
324                 // Constructors: "new" ends up calling "initialize".
325
// Actually, it's more complicated than this: a method CAN override new
326
// in which case I should show it, but that is discouraged and people
327
// SHOULD override initialize, which is what the default new method will
328
// call for initialization.
329
if (call.getType() == null) { // unknown type - search locally
330

331                     if (name.equals("new")) { // NOI18N
332
name = "initialize"; // NOI18N
333
}
334
335                     Arity arity = Arity.getCallArity(closest);
336
337                     DeclarationLocation loc = fix(findMethod(root, name, arity), info);
338
339                     if (loc != DeclarationLocation.NONE) {
340                         return loc;
341                     }
342                 }
343
344                 Set JavaDoc<IndexedMethod> methods = index.getMethods(name, null, NameKind.EXACT_NAME);
345
346                 try {
347                     IndexedMethod candidate =
348                         findBestMethodMatch(name, methods, (BaseDocument)info.getDocument(),
349                             caretOffset, path, closest, index);
350
351                     if (candidate != null) {
352                         IndexedElement com = candidate;
353                         Node node = AstUtilities.getForeignNode(com);
354
355                         return new DeclarationLocation(com.getFile().getFileObject(),
356                             node.getPosition().getStartOffset());
357                     }
358                 } catch (IOException JavaDoc ioe) {
359                     Exceptions.printStackTrace(ioe);
360                 }
361             } else if (closest instanceof ConstNode || closest instanceof Colon2Node) {
362                 // POSSIBLY a class usage.
363
String JavaDoc name = ((INameNode)closest).getName();
364                 Node localClass = findClass(root, name);
365
366                 if (localClass != null) {
367                     // Ensure that we have the right FQN if specific
368
if (closest instanceof Colon2Node) {
369                         AstPath classPath = new AstPath(root, localClass);
370
371                         if (classPath.leaf() != null) {
372                             String JavaDoc fqn1 = AstUtilities.getFqn((Colon2Node)closest);
373                             String JavaDoc fqn2 = AstUtilities.getFqnName(classPath);
374
375                             if (fqn1.equals(fqn2)) {
376                                 return fix(getLocation(localClass), info);
377                             }
378                         } else {
379                             assert false : localClass.toString();
380                         }
381                     } else {
382                         return fix(getLocation(localClass), info);
383                     }
384                 }
385
386                 // Try searching by qualified name by context first, if it's not qualified
387
Set JavaDoc<IndexedClass> classes = Collections.emptySet();
388
389                 if (closest instanceof Colon2Node) {
390                     name = AstUtilities.getFqn((Colon2Node)closest);
391                 }
392
393                 // E.g. for "include Assertions" within Test::Unit::TestCase, try looking
394
// for Test::Unit::TestCase::Assertions, Test::Unit:Assertions, Test::Assertions.
395
// And for "include Util::Backtracefilter" try Test::Unit::Util::Backtracefilter etc.
396
String JavaDoc fqn = AstUtilities.getFqnName(path);
397
398                 while ((classes.size() == 0) && (fqn.length() > 0)) {
399                     classes = index.getClasses(fqn + "::" + name, NameKind.EXACT_NAME, true, false,
400                             false);
401
402                     int f = fqn.lastIndexOf("::");
403
404                     if (f == -1) {
405                         break;
406                     } else {
407                         fqn = fqn.substring(0, f);
408                     }
409                 }
410
411                 if (classes.size() == 0) {
412                     classes = index.getClasses(name, NameKind.EXACT_NAME, true, false, false);
413                 }
414
415                 try {
416                     IndexedClass candidate =
417                         findBestClassMatch(classes, (BaseDocument)info.getDocument(), path,
418                             closest, index);
419
420                     if (candidate != null) {
421                         IndexedElement com = candidate;
422                         Node node = AstUtilities.getForeignNode(com);
423
424                         return new DeclarationLocation(com.getFile().getFileObject(),
425                             node.getPosition().getStartOffset());
426                     }
427                 } catch (IOException JavaDoc ioe) {
428                     Exceptions.printStackTrace(ioe);
429                 }
430             } else if (closest instanceof SymbolNode) {
431                 String JavaDoc name = ((SymbolNode)closest).getName();
432
433                 // Search for methods, fields, etc.
434
Arity arity = Arity.UNKNOWN;
435                 DeclarationLocation location = findMethod(root, name, arity);
436
437                 if (location == DeclarationLocation.NONE) {
438                     location = findInstance(root, name);
439                 }
440
441                 if (location == DeclarationLocation.NONE) {
442                     location = findClassVar(root, name);
443                 }
444
445                 if (location == DeclarationLocation.NONE) {
446                     location = findGlobal(root, name);
447                 }
448
449                 if (location == DeclarationLocation.NONE) {
450                     Node clz = findClass(root, ((INameNode)closest).getName());
451
452                     if (clz != null) {
453                         location = getLocation(clz);
454                     }
455                 }
456
457                 return fix(location, info);
458             } else if (closest instanceof AliasNode) {
459                 AliasNode an = (AliasNode)closest;
460
461                 // TODO - determine if the click is over the new name or the old name
462
String JavaDoc newName = an.getNewName();
463
464                 // XXX I don't know where the old and new names are since the user COULD
465
// have used more than one whitespace character for separation. For now I'll
466
// just have to assume it's the normal case with one space: alias new old.
467
// I -could- use the getPosition.getEndOffset() to see if this looks like it's
468
// the case (e.g. node length != "alias ".length + old.length+new.length+1).
469
// In this case I could go peeking in the source buffer to see where the
470
// spaces are - between alias and the first word or between old and new. XXX.
471
int newLength = newName.length();
472                 int aliasPos = an.getPosition().getStartOffset();
473
474                 // BUG: AliasNode currently seems to have wrong offsets; in particular, it starts after the keyword
475
//if (caretOffset > aliasPos+6) { // 6: "alias ".length()
476
if (caretOffset > (aliasPos + 1)) { // 1: " ".length()
477

478                     if (caretOffset > (aliasPos + 6 + newLength)) {
479                         // It's over the old word: this counts as a usage.
480
// The problem is that we don't know if it's a local, a dynamic, an instance
481
// variable, etc. (The $ and @ parts are not included in the alias statement).
482
// First see if it's a local variable.
483
String JavaDoc name = an.getOldName();
484                         ignoreAlias = true;
485
486                         try {
487                             DeclarationLocation location =
488                                 findLocal(AstUtilities.findLocalScope(closest, path), name);
489
490                             if (location == DeclarationLocation.NONE) {
491                                 location = findDynamic(AstUtilities.findDynamicScope(closest, path),
492                                         name);
493                             }
494
495                             if (location == DeclarationLocation.NONE) {
496                                 location = findMethod(root, name, Arity.UNKNOWN);
497                             }
498
499                             if (location == DeclarationLocation.NONE) {
500                                 location = findInstance(root, name);
501                             }
502
503                             if (location == DeclarationLocation.NONE) {
504                                 location = findClassVar(root, name);
505                             }
506
507                             if (location == DeclarationLocation.NONE) {
508                                 location = findGlobal(root, name);
509                             }
510
511                             if (location == DeclarationLocation.NONE) {
512                                 Node clz = findClass(root, name);
513
514                                 if (clz != null) {
515                                     location = getLocation(clz);
516                                 }
517                             }
518
519                             // TODO - what if we're aliasing another alias? I think that should show up in the various
520
// other nodes
521
if (location == DeclarationLocation.NONE) {
522                                 return location;
523                             } else {
524                                 return fix(location, info);
525                             }
526                         } finally {
527                             ignoreAlias = false;
528                         }
529                     } else {
530                         // It's over the new word: this counts as a declaration. Nothing to do here except
531
// maybe jump right back to the beginning.
532
return new DeclarationLocation(info.getFileObject(), aliasPos + 4);
533                     }
534                 }
535             } else if (closest instanceof ArgumentNode) {
536                 // A method name (if under a DefnNode or DefsNode) or a parameter (if indirectly under an ArgsNode)
537
String JavaDoc name = ((ArgumentNode)closest).getName(); // ArgumentNode doesn't implement INameNode
538

539                 Node parent = path.leafParent();
540
541                 if (parent != null) {
542                     if (parent instanceof DefnNode || parent instanceof DefsNode) {
543                         // It's a method name
544
return DeclarationLocation.NONE;
545                     } else {
546                         // Parameter (check to see if its under ArgumentNode)
547
Node method = AstUtilities.findLocalScope(closest, path);
548
549                         return fix(findLocal(method, name), info);
550                     }
551                 }
552             }
553         } catch (IOException JavaDoc ioe) {
554             Exceptions.printStackTrace(ioe);
555         } catch (BadLocationException JavaDoc ble) {
556             Exceptions.printStackTrace(ble);
557         }
558
559         return DeclarationLocation.NONE;
560     }
561
562     private DeclarationLocation fix(DeclarationLocation location, CompilationInfo info) {
563         if ((location != DeclarationLocation.NONE) && (location.getFileObject() == null) &&
564                 (location.getUrl() == null)) {
565             return new DeclarationLocation(info.getFileObject(), location.getOffset());
566         }
567
568         return location;
569     }
570
571     private DeclarationLocation getLocation(Node node) {
572         return new DeclarationLocation(null, node.getPosition().getStartOffset());
573     }
574
575     private DeclarationLocation findRDocMethod(Document JavaDoc doc, int offset, Node root, AstPath path) {
576         TokenHierarchy<Document JavaDoc> th = TokenHierarchy.get(doc);
577         TokenSequence<?extends TokenId> ts = th.tokenSequence(RubyTokenId.language());
578
579         if (ts == null) {
580             return DeclarationLocation.NONE;
581         }
582
583         ts.move(offset);
584
585         if (!ts.moveNext() && !ts.movePrevious()) {
586             return DeclarationLocation.NONE;
587         }
588
589         Token<?extends TokenId> token = ts.token();
590
591         TokenSequence<?extends TokenId> embedded = ts.embedded();
592
593         if (embedded != null) {
594             ts = embedded;
595
596             embedded.move(offset);
597
598             if (!embedded.moveNext() && !embedded.movePrevious()) {
599                 return DeclarationLocation.NONE;
600             }
601
602             token = embedded.token();
603         }
604
605         // Is this a comment? If so, possibly do rdoc-method reference jump
606
if ((token != null) && (token.id() == RubyCommentTokenId.COMMENT_LINK)) {
607             String JavaDoc method = token.text().toString();
608
609             if (method.startsWith("#")) {
610                 method = method.substring(1);
611
612                 DeclarationLocation loc = findMethod(root, method, Arity.UNKNOWN);
613
614                 // It looks like "#foo" can refer not just to methods (as rdoc suggested)
615
// but to attributes as well - in Rails' initializer.rb this is used
616
// in a number of places.
617
if (loc == DeclarationLocation.NONE) {
618                     loc = findInstance(root, "@" + method);
619                 }
620
621                 return loc;
622             } else {
623                 // A URL such as http://netbeans.org - try to open it in a browser!
624
try {
625                     URL JavaDoc url = new URL JavaDoc(method);
626
627                     return new DeclarationLocation(url);
628                 } catch (MalformedURLException JavaDoc mue) {
629                     // URL is from user source... don't complain with exception dialogs etc.
630
;
631                 }
632             }
633         }
634
635         return DeclarationLocation.NONE;
636     }
637
638     private IndexedClass findBestClassMatch(Set JavaDoc<IndexedClass> classes, BaseDocument doc,
639         AstPath path, Node reference, RubyIndex index) {
640         // Make sure that the best fit method actually has a corresponding valid source location
641
// and parse tree
642
while (!classes.isEmpty()) {
643             IndexedClass clz = findBestClassMatchHelper(classes, doc, path, reference, index);
644             Node node = AstUtilities.getForeignNode(clz);
645
646             if (node != null) {
647                 return clz;
648             }
649
650             classes.remove(clz);
651         }
652
653         return null;
654     }
655
656     // Now that I have a common RubyObject superclass, can I combine this and findBestMethodMatchHelper
657
// since there's a lot of code duplication that could be shared by just operating on RubyObjects ?
658
private IndexedClass findBestClassMatchHelper(Set JavaDoc<IndexedClass> classes, BaseDocument doc,
659         AstPath path, Node reference, RubyIndex index) {
660         // 1. First see if the reference is fully qualified. If so the job should
661
// be easier: prune the result set down
662
// If I have the fqn, I can also call RubyIndex.getRDocLocation to pick the
663
// best candidate
664
Set JavaDoc<IndexedClass> candidates = new HashSet JavaDoc<IndexedClass>();
665
666         if (reference instanceof Colon2Node) {
667             String JavaDoc fqn = AstUtilities.getFqn((Colon2Node)reference);
668
669             while ((fqn != null) && (fqn.length() > 0)) {
670                 for (IndexedClass clz : classes) {
671                     if (fqn.equals(clz.getSignature())) {
672                         candidates.add(clz);
673                     }
674                 }
675
676                 // TODO: Use the fqn to check if the class is documented: if so, prefer it
677

678                 // Check inherited methods; for example, if we've determined
679
// that you're looking for Integer::foo, I should happily match
680
// Numeric::foo.
681
IndexedClass superClass = index.getSuperclass(fqn);
682
683                 if (superClass != null) {
684                     fqn = superClass.getSignature();
685                 } else {
686                     break;
687                 }
688             }
689         }
690
691         if (candidates.size() == 1) {
692             return candidates.iterator().next();
693         } else if (!candidates.isEmpty()) {
694             classes = candidates;
695         }
696
697         // 2. See if the reference is followed by a method call - if so, that may
698
// help disambiguate which reference we're after.
699
// TODO
700

701         // 3. See which of the class references are defined in files directly
702
// required by this file.
703
Set JavaDoc<String JavaDoc> requires = null;
704
705         if (path != null) {
706             candidates = new HashSet JavaDoc<IndexedClass>();
707
708             requires = AstUtilities.getRequires(path.root());
709
710             for (IndexedClass clz : classes) {
711                 String JavaDoc require = clz.getRequire();
712
713                 if (requires.contains(require)) {
714                     candidates.add(clz);
715                 }
716             }
717
718             if (candidates.size() == 1) {
719                 return candidates.iterator().next();
720             } else if (!candidates.isEmpty()) {
721                 classes = candidates;
722             }
723         }
724
725         // 4. See if any of the classes are "kernel" classes (builtins) and for these
726
// go to the known locations
727
candidates = new HashSet JavaDoc<IndexedClass>();
728
729         for (IndexedClass clz : classes) {
730             String JavaDoc url = clz.getFileUrl();
731
732             if (url.indexOf("rubystubs") != -1) {
733                 candidates.add(clz);
734             }
735         }
736
737         if (candidates.size() == 1) {
738             return candidates.iterator().next();
739         } else if (!candidates.isEmpty()) {
740             classes = candidates;
741         }
742
743         // 5. See which classes are documented, and prefer those over undocumented classes
744
candidates = new HashSet JavaDoc<IndexedClass>();
745
746         for (IndexedClass clz : classes) {
747             // TODO
748
}
749
750         // 6. Look at transitive closure of require statements and see which files
751
// are most likely candidates
752
if ((index != null) && (requires != null)) {
753             candidates = new HashSet JavaDoc<IndexedClass>();
754
755             Set JavaDoc<String JavaDoc> allRequires = index.getRequiresTransitively(requires);
756
757             for (IndexedClass clz : classes) {
758                 String JavaDoc require = clz.getRequire();
759
760                 if (allRequires.contains(require)) {
761                     candidates.add(clz);
762                 }
763             }
764
765             if (candidates.size() == 1) {
766                 return candidates.iterator().next();
767             } else if (!candidates.isEmpty()) {
768                 classes = candidates;
769             }
770         }
771
772         // 7. Other heuristics: Look at the method definition with the
773
// most methods associated with it. Look at other uses of this
774
// class in this parse tree, look at the methods and see if we
775
// can rule out candidates based on that
776

777         // 8. Look at superclasses and consider -their- requires to figure out
778
// which class we're supposed to use
779
// TODO
780
candidates = new HashSet JavaDoc<IndexedClass>();
781
782         // Pick one arbitrarily
783
if (classes.size() > 0) {
784             return classes.iterator().next();
785         } else {
786             return null;
787         }
788     }
789
790     private IndexedMethod findBestMethodMatch(String JavaDoc name, Set JavaDoc<IndexedMethod> methods,
791         BaseDocument doc, int offset, AstPath path, Node call, RubyIndex index) {
792         // Make sure that the best fit method actually has a corresponding valid source location
793
// and parse tree
794
while (!methods.isEmpty()) {
795             IndexedMethod method =
796                 findBestMethodMatchHelper(name, methods, doc, offset, path, call, index);
797             Node node = AstUtilities.getForeignNode(method);
798
799             if (node != null) {
800                 return method;
801             }
802
803             methods.remove(method);
804         }
805
806         return null;
807     }
808
809     private IndexedMethod findBestMethodMatchHelper(String JavaDoc name, Set JavaDoc<IndexedMethod> methods,
810         BaseDocument doc, int offset, AstPath path, Node callNode, RubyIndex index) {
811         Set JavaDoc<IndexedMethod> candidates = new HashSet JavaDoc<IndexedMethod>();
812
813         // 1. First see if the reference is fully qualified. If so the job should
814
// be easier: prune the result set down
815
// If I have the fqn, I can also call RubyIndex.getRDocLocation to pick the
816
// best candidate
817
if (callNode instanceof CallNode) {
818             Node node = ((CallNode)callNode).getReceiverNode();
819             String JavaDoc fqn = null;
820
821             if (node instanceof Colon2Node) {
822                 fqn = AstUtilities.getFqn((Colon2Node)node);
823             } else if (node instanceof ConstNode) {
824                 fqn = ((ConstNode)node).getName();
825             }
826
827             if (fqn != null) {
828                 while ((fqn != null) && (fqn.length() > 0)) {
829                     for (IndexedMethod method : methods) {
830                         if (fqn.equals(method.getClz())) {
831                             candidates.add(method);
832                         }
833                     }
834
835                     // Check inherited methods; for example, if we've determined
836
// that you're looking for Integer::foo, I should happily match
837
// Numeric::foo.
838
IndexedClass superClass = index.getSuperclass(fqn);
839
840                     if (superClass != null) {
841                         fqn = superClass.getSignature();
842                     } else {
843                         break;
844                     }
845                 }
846             }
847         }
848
849         if (candidates.size() == 1) {
850             return candidates.iterator().next();
851         } else if (!candidates.isEmpty()) {
852             methods = candidates;
853         }
854
855         // 2. See if the reference is not qualified (no :: or . prior to
856
// the method call; if so it must be an inherited method (or a local
857
// method, but we've already checked that possibility before getting
858
// into the index search)
859
TokenHierarchy<Document JavaDoc> th = TokenHierarchy.get((Document JavaDoc)doc);
860
861         Call call = LexUtilities.getCallType(doc, th, offset);
862         boolean skipPrivate = true;
863
864         if ((path != null) && (callNode != null) && (call != Call.LOCAL) && (call != Call.NONE)) {
865             boolean skipInstanceMethods = call.isStatic();
866
867             candidates = new HashSet JavaDoc<IndexedMethod>();
868
869             String JavaDoc type = call.getType();
870
871             // I'm not doing any data flow analysis at this point, so
872
// I can't do anything with a LHS like "foo.". Only actual types.
873
if ((type != null) && (type.length() > 0)) {
874                 String JavaDoc lhs = call.getLhs();
875
876                 String JavaDoc fqn = AstUtilities.getFqnName(path);
877
878                 // TODO for self and super, rather than computing ALL inherited methods
879
// (and picking just one of them), I should use the FIRST match as the
880
// one to show! (closest super class or include definition)
881
if ("self".equals(lhs)) {
882                     type = fqn;
883                     skipPrivate = false;
884                 } else if ("super".equals(lhs)) {
885                     skipPrivate = false;
886
887                     IndexedClass sc = index.getSuperclass(fqn);
888
889                     if (sc != null) {
890                         type = sc.getFqn();
891                     } else {
892                         ClassNode cls = AstUtilities.findClass(callNode, path);
893
894                         if (cls != null) {
895                             type = AstUtilities.getSuperclass(cls);
896                         }
897                     }
898                 }
899
900                 if ((type != null) && (type.length() > 0)) {
901                     // Possibly a class on the left hand side: try searching with the class as a qualifier.
902
// Try with the LHS + current FQN recursively. E.g. if we're in
903
// Test::Unit when there's a call to Foo.x, we'll try
904
// Test::Unit::Foo, and Test::Foo
905
while (candidates.size() == 0) {
906                         candidates = index.getInheritedMethods(fqn + "::" + type, name);
907
908                         int f = fqn.lastIndexOf("::");
909
910                         if (f == -1) {
911                             break;
912                         } else {
913                             fqn = fqn.substring(0, f);
914                         }
915                     }
916
917                     // Add methods in the class (without an FQN)
918
// TODO - this is doing a prefix match. I want an exact name match here.
919
candidates = index.getInheritedMethods(type, name);
920                 }
921             }
922
923             if (skipPrivate || skipInstanceMethods) {
924                 Set JavaDoc<IndexedMethod> m = new HashSet JavaDoc<IndexedMethod>();
925
926                 for (IndexedMethod method : candidates) {
927                     // Don't include private or protected methods on other objects
928
if (skipPrivate && (method.isPrivate() && !"new".equals(method.getName()))) {
929                         // TODO - "initialize" removal here should not be necessary since they should
930
// be marked as private, but index doesn't contain that yet
931
continue;
932                     }
933
934                     // We can only call static methods
935
if (skipInstanceMethods && !method.isStatic()) {
936                         continue;
937                     }
938
939                     m.add(method);
940                 }
941
942                 candidates = m;
943             }
944         }
945
946         if (candidates.size() == 1) {
947             return candidates.iterator().next();
948         } else if (!candidates.isEmpty()) {
949             methods = candidates;
950         }
951
952         // 3. Use method arity to rule out mismatches
953
// TODO - this is tricky since Ruby lets you specify more or fewer
954
// parameters with some reasonable behavior...
955
// Possibly I should do this check further down since the
956
// other heuristics may work better as a first-level disambiguation
957

958         // 4. Check to see which classes are required directly from this file, and
959
// prefer matches that are in this set of classes
960
Set JavaDoc<String JavaDoc> requires = null;
961
962         if (path != null) {
963             candidates = new HashSet JavaDoc<IndexedMethod>();
964
965             requires = AstUtilities.getRequires(path.root());
966
967             for (IndexedMethod method : methods) {
968                 String JavaDoc require = method.getRequire();
969
970                 if (requires.contains(require)) {
971                     candidates.add(method);
972                 }
973             }
974
975             if (candidates.size() == 1) {
976                 return candidates.iterator().next();
977             } else if (!candidates.isEmpty()) {
978                 methods = candidates;
979             }
980         }
981
982         // 3. See if any of the methods are in "kernel" classes (builtins) and for these
983
// go to the known locations
984
candidates = new HashSet JavaDoc<IndexedMethod>();
985
986         for (IndexedMethod method : methods) {
987             String JavaDoc url = method.getFileUrl();
988
989             if (url.indexOf("rubystubs") != -1) {
990                 candidates.add(method);
991             }
992         }
993
994         if (candidates.size() == 1) {
995             return candidates.iterator().next();
996         } else if (!candidates.isEmpty()) {
997             methods = candidates;
998         }
999
1000        // 4. See which methods are documented, and prefer those over undocumented methods
1001
candidates = new HashSet JavaDoc<IndexedMethod>();
1002
1003        for (IndexedMethod method : methods) {
1004            // TODO
1005
}
1006
1007        // 5. Look at transitive closure of require statements and see which files
1008
// are most likely candidates
1009
if ((index != null) && (requires != null)) {
1010            candidates = new HashSet JavaDoc<IndexedMethod>();
1011
1012            Set JavaDoc<String JavaDoc> allRequires = index.getRequiresTransitively(requires);
1013
1014            for (IndexedMethod method : methods) {
1015                String JavaDoc require = method.getRequire();
1016
1017                if (allRequires.contains(require)) {
1018                    candidates.add(method);
1019                }
1020            }
1021
1022            if (candidates.size() == 1) {
1023                return candidates.iterator().next();
1024            } else if (!candidates.isEmpty()) {
1025                methods = candidates;
1026            }
1027        }
1028
1029        // 6. Other heuristics: Look at the method definition with the
1030
// class with most methods associated with it. Look at other uses of this
1031
// method in this parse tree and see if I can figure out the containing class
1032
// or rule out other candidates based on that
1033

1034        // 7. Look at superclasses and consider -their- requires to figure out
1035
// which class we're looking for methods in
1036
// TODO
1037

1038        // Pick one arbitrarily
1039
if (methods.size() > 0) {
1040            return methods.iterator().next();
1041        } else {
1042            return null;
1043        }
1044    }
1045
1046    @SuppressWarnings JavaDoc("unchecked")
1047    private DeclarationLocation findLocal(Node node, String JavaDoc name) {
1048        if (node instanceof LocalAsgnNode) {
1049            if (((INameNode)node).getName().equals(name)) {
1050                return getLocation(node);
1051            }
1052        } else if (!ignoreAlias && node instanceof AliasNode) {
1053            if (((AliasNode)node).getNewName().equals(name)) {
1054                return getLocation(node);
1055            }
1056        } else if (node instanceof ArgsNode) {
1057            ArgsNode an = (ArgsNode)node;
1058
1059            if (an.getArgsCount() > 0) {
1060                List JavaDoc<Node> args = (List JavaDoc<Node>)an.childNodes();
1061
1062                for (Node arg : args) {
1063                    if (arg instanceof ListNode) {
1064                        List JavaDoc<Node> args2 = (List JavaDoc<Node>)arg.childNodes();
1065
1066                        for (Node arg2 : args2) {
1067                            if (arg2 instanceof ArgumentNode) {
1068                                if (((ArgumentNode)arg2).getName().equals(name)) {
1069                                    return getLocation(arg2);
1070                                }
1071                            } else if (arg2 instanceof LocalAsgnNode) {
1072                                if (((LocalAsgnNode)arg2).getName().equals(name)) {
1073                                    return getLocation(arg2);
1074                                }
1075                            }
1076                        }
1077                    }
1078                }
1079            }
1080        }
1081
1082        List JavaDoc<Node> list = node.childNodes();
1083
1084        for (Node child : list) {
1085            DeclarationLocation location = findLocal(child, name);
1086
1087            if (location != DeclarationLocation.NONE) {
1088                return location;
1089            }
1090        }
1091
1092        return DeclarationLocation.NONE;
1093    }
1094
1095    private DeclarationLocation findDynamic(Node node, String JavaDoc name) {
1096        if (node instanceof DAsgnNode) {
1097            if (((INameNode)node).getName().equals(name)) {
1098                return getLocation(node);
1099            }
1100
1101            //} else if (node instanceof ArgsNode) {
1102
// ArgsNode an = (ArgsNode)node;
1103
//
1104
// if (an.getArgsCount() > 0) {
1105
// List<Node> args = (List<Node>)an.childNodes();
1106
// List<String> parameters = null;
1107
//
1108
// for (Node arg : args) {
1109
// if (arg instanceof ListNode) {
1110
// List<Node> args2 = (List<Node>)arg.childNodes();
1111
// parameters = new ArrayList<String>(args2.size());
1112
//
1113
// for (Node arg2 : args2) {
1114
// if (arg2 instanceof ArgumentNode) {
1115
// OffsetRange range = getRange(arg2);
1116
// ranges.put(range, ColoringAttributes.MARK_OCCURRENCES);
1117
// } else if (arg2 instanceof LocalAsgnNode) {
1118
// OffsetRange range = getRange(arg2);
1119
// ranges.put(range, ColoringAttributes.MARK_OCCURRENCES);
1120
// }
1121
// }
1122
// }
1123
// }
1124
// }
1125
} else if (!ignoreAlias && node instanceof AliasNode) {
1126            if (((AliasNode)node).getNewName().equals(name)) {
1127                return getLocation(node);
1128            }
1129        }
1130
1131        @SuppressWarnings JavaDoc("unchecked")
1132        List JavaDoc<Node> list = node.childNodes();
1133
1134        for (Node child : list) {
1135            DeclarationLocation location = findDynamic(child, name);
1136
1137            if (location != DeclarationLocation.NONE) {
1138                return location;
1139            }
1140        }
1141
1142        return DeclarationLocation.NONE;
1143    }
1144
1145    private DeclarationLocation findInstance(Node node, String JavaDoc name) {
1146        if (node instanceof InstAsgnNode) {
1147            if (((INameNode)node).getName().equals(name)) {
1148                return getLocation(node);
1149            }
1150        } else if (!ignoreAlias && node instanceof AliasNode) {
1151            if (((AliasNode)node).getNewName().equals(name)) {
1152                return getLocation(node);
1153            }
1154        } else if (AstUtilities.isAttr(node)) {
1155            // TODO: Compute the symbols and check for equality
1156
// attr_reader, attr_accessor, attr_writer
1157
SymbolNode[] symbols = AstUtilities.getAttrSymbols(node);
1158
1159            for (int i = 0; i < symbols.length; i++) {
1160                if (name.equals("@" + symbols[i].getName())) {
1161                    return getLocation(symbols[i]);
1162                }
1163            }
1164        }
1165
1166        @SuppressWarnings JavaDoc("unchecked")
1167        List JavaDoc<Node> list = node.childNodes();
1168
1169        for (Node child : list) {
1170            DeclarationLocation location = findInstance(child, name);
1171
1172            if (location != DeclarationLocation.NONE) {
1173                return location;
1174            }
1175        }
1176
1177        return DeclarationLocation.NONE;
1178    }
1179
1180    private DeclarationLocation findClassVar(Node node, String JavaDoc name) {
1181        if (node instanceof ClassVarDeclNode) {
1182            if (((INameNode)node).getName().equals(name)) {
1183                return getLocation(node);
1184            }
1185        } else if (!ignoreAlias && node instanceof AliasNode) {
1186            if (((AliasNode)node).getNewName().equals(name)) {
1187                return getLocation(node);
1188            }
1189
1190            // TODO: Are there attr readers and writers for class variables?
1191
// } else if (AstUtilities.isAttrReader(node) || AstUtilities.isAttrWriter(node)) {
1192
// // TODO: Compute the symbols and check for equality
1193
// // attr_reader, attr_accessor, attr_writer
1194
// SymbolNode[] symbols = AstUtilities.getAttrSymbols(node);
1195
//
1196
// for (int i = 0; i < symbols.length; i++) {
1197
// if (name.equals("@" + symbols[i].getName())) {
1198
// return getLocation(symbols[i]);
1199
// }
1200
// }
1201
}
1202
1203        @SuppressWarnings JavaDoc("unchecked")
1204        List JavaDoc<Node> list = node.childNodes();
1205
1206        for (Node child : list) {
1207            DeclarationLocation location = findClassVar(child, name);
1208
1209            if (location != DeclarationLocation.NONE) {
1210                return location;
1211            }
1212        }
1213
1214        return DeclarationLocation.NONE;
1215    }
1216
1217    private DeclarationLocation findGlobal(Node node, String JavaDoc name) {
1218        if (node instanceof GlobalAsgnNode) {
1219            if (((INameNode)node).getName().equals(name)) {
1220                return getLocation(node);
1221            }
1222        } else if (!ignoreAlias && node instanceof AliasNode) {
1223            if (((AliasNode)node).getNewName().equals(name)) {
1224                return getLocation(node);
1225            }
1226        }
1227
1228        @SuppressWarnings JavaDoc("unchecked")
1229        List JavaDoc<Node> list = node.childNodes();
1230
1231        for (Node child : list) {
1232            DeclarationLocation location = findGlobal(child, name);
1233
1234            if (location != DeclarationLocation.NONE) {
1235                return location;
1236            }
1237        }
1238
1239        return DeclarationLocation.NONE;
1240    }
1241
1242    private DeclarationLocation findMethod(Node node, String JavaDoc name, Arity arity) {
1243        // Recursively search for methods or method calls that match the name and arity
1244
if (node instanceof DefnNode) {
1245            // DefnNode doesn't implement INameNode
1246
if (((DefnNode)node).getName().equals(name) &&
1247                    Arity.matches(arity, Arity.getDefArity(node))) {
1248                return getLocation(node);
1249            }
1250        } else if (node instanceof DefsNode) {
1251            // DefsNode doesn't implement INameNode
1252
if (((DefsNode)node).getName().equals(name) &&
1253                    Arity.matches(arity, Arity.getDefArity(node))) {
1254                return getLocation(node);
1255            }
1256        } else if (!ignoreAlias && node instanceof AliasNode) {
1257            if (((AliasNode)node).getNewName().equals(name)) {
1258                // No obvious way to check arity
1259
return getLocation(node);
1260            }
1261        }
1262
1263        @SuppressWarnings JavaDoc("unchecked")
1264        List JavaDoc<Node> list = node.childNodes();
1265
1266        for (Node child : list) {
1267            DeclarationLocation location = findMethod(child, name, arity);
1268
1269            if (location != DeclarationLocation.NONE) {
1270                return location;
1271            }
1272        }
1273
1274        return DeclarationLocation.NONE;
1275    }
1276
1277    private Node findClass(Node node, String JavaDoc name) {
1278        if (node instanceof ClassNode) {
1279            String JavaDoc n = AstUtilities.getClassOrModuleName((ClassNode)node);
1280
1281            if (n.equals(name)) {
1282                return node;
1283            }
1284        } else if (node instanceof ConstDeclNode) {
1285            if (((INameNode)node).getName().equals(name)) {
1286                return node;
1287            }
1288        } else if (!ignoreAlias && node instanceof AliasNode) {
1289            if (((AliasNode)node).getNewName().equals(name)) {
1290                return node;
1291            }
1292        }
1293
1294        @SuppressWarnings JavaDoc("unchecked")
1295        List JavaDoc<Node> list = node.childNodes();
1296
1297        for (Node child : list) {
1298            Node match = findClass(child, name);
1299
1300            if (match != null) {
1301                return match;
1302            }
1303        }
1304
1305        return null;
1306    }
1307}
1308
Popular Tags