KickJava   Java API By Example, From Geeks To Geeks.

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


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.util.ArrayList JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.LinkedList JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Set JavaDoc;
29 import javax.swing.text.BadLocationException JavaDoc;
30 import javax.swing.text.Document JavaDoc;
31 import org.jruby.ast.AliasNode;
32 import org.jruby.ast.ArgsNode;
33 import org.jruby.ast.ArgumentNode;
34 import org.jruby.ast.AssignableNode;
35 import org.jruby.ast.BlockNode;
36 import org.jruby.ast.CallNode;
37 import org.jruby.ast.ClassNode;
38 import org.jruby.ast.Colon2Node;
39 import org.jruby.ast.ConstNode;
40 import org.jruby.ast.ConstNode;
41 import org.jruby.ast.DefnNode;
42 import org.jruby.ast.DefsNode;
43 import org.jruby.ast.FCallNode;
44 import org.jruby.ast.IScopingNode;
45 import org.jruby.ast.IScopingNode;
46 import org.jruby.ast.IterNode;
47 import org.jruby.ast.ListNode;
48 import org.jruby.ast.LocalAsgnNode;
49 import org.jruby.ast.ModuleNode;
50 import org.jruby.ast.NewlineNode;
51 import org.jruby.ast.NewlineNode;
52 import org.jruby.ast.Node;
53 import org.jruby.ast.StrNode;
54 import org.jruby.ast.SymbolNode;
55 import org.jruby.ast.VCallNode;
56 import org.jruby.ast.types.INameNode;
57 import org.jruby.lexer.yacc.ISourcePosition;
58 import org.jruby.util.ByteList;
59 import org.netbeans.api.gsf.CompilationInfo;
60 import org.netbeans.api.gsf.Modifier;
61 import org.netbeans.api.gsf.OffsetRange;
62 import org.netbeans.api.gsf.ParserFile;
63 import org.netbeans.api.gsf.ParserResult;
64 import org.netbeans.api.gsf.SourceFileReader;
65 import org.netbeans.editor.BaseDocument;
66 import org.netbeans.editor.Utilities;
67 import org.netbeans.modules.ruby.elements.IndexedElement;
68 import org.netbeans.spi.gsf.DefaultParseListener;
69 import org.openide.cookies.EditorCookie;
70 import org.openide.filesystems.FileObject;
71 import org.openide.loaders.DataObject;
72 import org.openide.util.Exceptions;
73
74
75 /**
76  *
77  * @todo Create a NodePath abstraction, wrapping my ArrayList operation
78  * that I'm using here?
79  *
80  * @author Tor Norbye
81  */

82 public class AstUtilities {
83     /** Whether or not the prefixes for defs should be highlighted, e.g. in
84      * def HTTP.foo
85      * Should "HTTP." be highlighted, or just the foo portion?
86      */

87     private static final boolean INCLUDE_DEFS_PREFIX = false;
88
89     /** This is a utility class only, not instantiatiable */
90     private AstUtilities() {
91     }
92
93     /** Move to a generic (non-AST-oriented) utility class? */
94     public static BaseDocument getBaseDocument(FileObject fileObject, boolean forceOpen) {
95         DataObject dobj;
96
97         try {
98             dobj = DataObject.find(fileObject);
99
100             EditorCookie ec = dobj.getCookie(EditorCookie.class);
101
102             if (ec == null) {
103                 throw new IOException JavaDoc("Can't open " + fileObject.getNameExt());
104             }
105
106             Document JavaDoc document;
107
108             if (forceOpen) {
109                 document = ec.openDocument();
110             } else {
111                 document = ec.getDocument();
112             }
113
114             if (document instanceof BaseDocument) {
115                 return ((BaseDocument)document);
116             }
117         } catch (IOException JavaDoc ioe) {
118             Exceptions.printStackTrace(ioe);
119         }
120
121         return null;
122     }
123
124     /**
125      * Get the rdoc documentation associated with the given node in the given document.
126      * The node must have position information that matches the source in the document.
127      */

128     public static List JavaDoc<String JavaDoc> gatherDocumentation(BaseDocument baseDoc, Node node) {
129         LinkedList JavaDoc<String JavaDoc> comments = new LinkedList JavaDoc<String JavaDoc>();
130         int elementBegin = node.getPosition().getStartOffset();
131
132         try {
133             // Search to previous lines, locate comments. Once we have a non-whitespace line that isn't
134
// a comment, we're done
135
int offset = Utilities.getRowStart(baseDoc, elementBegin);
136             offset--;
137
138             // Skip empty and whitespace lines
139
while (offset >= 0) {
140                 // Find beginning of line
141
offset = Utilities.getRowStart(baseDoc, offset);
142
143                 if (!Utilities.isRowEmpty(baseDoc, offset) &&
144                         !Utilities.isRowWhite(baseDoc, offset)) {
145                     break;
146                 }
147
148                 offset--;
149             }
150
151             if (offset < 0) {
152                 return null;
153             }
154
155             while (offset >= 0) {
156                 // Find beginning of line
157
offset = Utilities.getRowStart(baseDoc, offset);
158
159                 if (Utilities.isRowEmpty(baseDoc, offset) || Utilities.isRowWhite(baseDoc, offset)) {
160                     // Empty lines not allowed within an rdoc
161
break;
162                 }
163
164                 // This is a comment line we should include
165
int lineBegin = Utilities.getRowFirstNonWhite(baseDoc, offset);
166                 int lineEnd = Utilities.getRowEnd(baseDoc, offset);
167                 String JavaDoc line = baseDoc.getText(lineBegin, lineEnd - lineBegin);
168
169                 if (line.startsWith("#")) {
170                     comments.addFirst(line);
171                 } else {
172                     // No longer in a comment
173
break;
174                 }
175
176                 // Previous line
177
offset--;
178             }
179         } catch (BadLocationException JavaDoc ble) {
180             Exceptions.printStackTrace(ble);
181         }
182
183         return comments;
184     }
185
186     public static Node getForeignNode(final IndexedElement o) {
187         ParserFile file = o.getFile();
188
189         if (file == null) {
190             return null;
191         }
192
193         List JavaDoc<ParserFile> files = Collections.singletonList(file);
194         SourceFileReader reader =
195             new SourceFileReader() {
196                 public CharSequence JavaDoc read(ParserFile file)
197                     throws IOException JavaDoc {
198                     Document JavaDoc doc = o.getDocument();
199
200                     try {
201                         return doc.getText(0, doc.getLength());
202                     } catch (BadLocationException JavaDoc ble) {
203                         IOException JavaDoc ioe = new IOException JavaDoc();
204                         ioe.initCause(ble);
205                         throw ioe;
206                     }
207                 }
208
209                 public int getCaretOffset(ParserFile fileObject) {
210                     return -1;
211                 }
212             };
213
214         DefaultParseListener listener = new DefaultParseListener();
215         new RubyParser().parseFiles(files, listener, reader);
216
217         ParserResult result = listener.getParserResult();
218
219         if (result == null) {
220             return null;
221         }
222
223         Node root = AstUtilities.getRoot(result);
224
225         if (root == null) {
226             return null;
227         }
228
229         String JavaDoc signature = o.getSignature();
230
231         if (signature == null) {
232             return null;
233         }
234
235         Node node = AstUtilities.findBySignature(root, signature);
236         
237         // Special handling for "new" - these are synthesized from "initialize" methods
238
if (node == null && "new".equals(o.getName())) {
239             signature = signature.replaceFirst("new", "initialize");
240             node = AstUtilities.findBySignature(root, signature);
241         }
242
243         return node;
244     }
245
246     /**
247      * Return the set of requires that are defined in this AST
248      * (no transitive closure though).
249      */

250     public static Set JavaDoc<String JavaDoc> getRequires(Node root) {
251         Set JavaDoc<String JavaDoc> requires = new HashSet JavaDoc<String JavaDoc>();
252         addRequires(root, requires);
253
254         return requires;
255     }
256
257     private static void addRequires(Node node, Set JavaDoc<String JavaDoc> requires) {
258         if (node instanceof FCallNode) {
259             // A method call
260
String JavaDoc name = ((INameNode)node).getName();
261
262             if (name.equals("require")) { // XXX Load too?
263

264                 Node argsNode = ((FCallNode)node).getArgsNode();
265
266                 if (argsNode instanceof ListNode) {
267                     ListNode args = (ListNode)argsNode;
268
269                     if (args.size() > 0) {
270                         Node n = args.get(0);
271
272                         if (n instanceof StrNode) {
273                             ByteList require = ((StrNode)n).getValue();
274
275                             if ((require != null) && (require.length() > 0)) {
276                                 requires.add(require.toString());
277                             }
278                         }
279                     }
280                 }
281             }
282         } else if (node instanceof ModuleNode || node instanceof ClassNode ||
283                 node instanceof DefnNode || node instanceof DefsNode) {
284             // Only look for require statements at the top level
285
return;
286         }
287
288         @SuppressWarnings JavaDoc("unchecked")
289         List JavaDoc<Node> list = node.childNodes();
290
291         for (Node child : list) {
292             addRequires(child, requires);
293         }
294     }
295
296     public static Node findLocalScope(Node node, AstPath path) {
297         Node method = findMethod(path);
298
299         if (method == null) {
300             if (path.root() != null) {
301                 return path.root();
302             }
303
304             method = findBlock(path);
305         }
306
307         if (method == null) {
308             method = path.leafParent();
309
310             if (method instanceof NewlineNode) {
311                 method = path.leafGrandParent();
312             }
313
314             if (method == null) {
315                 method = node;
316             }
317         }
318
319         return method;
320     }
321
322     public static Node findDynamicScope(Node node, AstPath path) {
323         Node block = findBlock(path);
324
325         if (block == null) {
326             // Use parent
327
block = path.leafParent();
328
329             if (block == null) {
330                 block = node;
331             }
332         }
333
334         return block;
335     }
336
337     public static Node findBlock(AstPath path) {
338         // Find the closest block node enclosing the given node
339
for (Node curr : path) {
340             if (curr instanceof BlockNode || curr instanceof IterNode) {
341                 return curr;
342             }
343         }
344
345         return null;
346     }
347
348     public static Node findMethod(AstPath path) {
349         // Find the closest block node enclosing the given node
350
for (Node curr : path) {
351             if (curr instanceof DefnNode || curr instanceof DefsNode) {
352                 return curr;
353             }
354         }
355
356         return null;
357     }
358
359     // XXX Shouldn't this go in the REVERSE direction? I might find
360
// a superclass here!
361
public static ClassNode findClass(Node node, AstPath path) {
362         // Find the closest block node enclosing the given node
363
for (Node curr : path) {
364             if (curr instanceof ClassNode) {
365                 return (ClassNode)curr;
366             }
367         }
368
369         return null;
370     }
371
372     public static String JavaDoc getCallName(Node node) {
373         assert node instanceof FCallNode || node instanceof VCallNode || node instanceof CallNode;
374
375         if (node instanceof INameNode) {
376             return ((INameNode)node).getName();
377         }
378         assert false : node;
379
380         return null;
381     }
382
383     public static String JavaDoc getDefName(Node node) {
384         if (node instanceof DefnNode) {
385             return ((DefnNode)node).getName();
386         } else if (node instanceof DefsNode) {
387             return ((DefsNode)node).getName();
388         }
389         assert false : node;
390
391         return null;
392     }
393
394     /** Find the direct child which is an ArgsNode, and pick out the argument names */
395     @SuppressWarnings JavaDoc("unchecked")
396     public static List JavaDoc<String JavaDoc> getDefArgs(Node node) {
397         // TODO - do anything special about (&), blocks, argument lists (*), etc?
398
assert node instanceof DefnNode || node instanceof DefsNode;
399
400         List JavaDoc<Node> nodes = (List JavaDoc<Node>)node.childNodes();
401
402         for (Node c : nodes) {
403             if (c instanceof ArgsNode) {
404                 ArgsNode an = (ArgsNode)c;
405
406                 List JavaDoc<Node> args = (List JavaDoc<Node>)an.childNodes();
407                 List JavaDoc<String JavaDoc> parameters = new ArrayList JavaDoc<String JavaDoc>();
408
409                 for (Node arg : args) {
410                     if (arg instanceof ListNode) {
411                         List JavaDoc<Node> args2 = (List JavaDoc<Node>)arg.childNodes();
412
413                         for (Node arg2 : args2) {
414                             if (arg2 instanceof ArgumentNode) {
415                                 String JavaDoc name = ((ArgumentNode)arg2).getName();
416                                 parameters.add(name);
417                             } else if (arg2 instanceof LocalAsgnNode) {
418                                 String JavaDoc name = ((LocalAsgnNode)arg2).getName();
419                                 parameters.add(name);
420                             }
421                         }
422                     }
423                 }
424
425                 return parameters;
426             }
427         }
428
429         return null;
430     }
431
432     // TODO - do anything special about (&), blocks, argument lists (*), etc?
433
public static String JavaDoc getDefSignature(Node node) {
434         assert node instanceof DefnNode || node instanceof DefsNode : node;
435
436         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
437         sb.append(getDefName(node));
438
439         List JavaDoc<String JavaDoc> args = getDefArgs(node);
440
441         if ((args != null) && (args.size() > 0)) {
442             sb.append('(');
443
444             Iterator JavaDoc<String JavaDoc> it = args.iterator();
445             sb.append(it.next());
446
447             while (it.hasNext()) {
448                 sb.append(',');
449                 sb.append(it.next());
450             }
451
452             sb.append(')');
453         }
454
455         return sb.toString();
456     }
457
458     /**
459      * Return true iff the given call note can be considered a valid call of the given method.
460      */

461     public static boolean isCallFor(Node call, Arity callArity, Node method) {
462         assert call instanceof CallNode || call instanceof VCallNode || call instanceof FCallNode;
463         assert method instanceof DefsNode || method instanceof DefnNode;
464
465         // Simple call today...
466
return getDefName(method).equals(getCallName(call)) &&
467         Arity.matches(callArity, Arity.getDefArity(method));
468     }
469
470     /** For the given signature, locating the corresponding Node within the tree that
471      * it corresponds to */

472     public static Node findBySignature(Node root, String JavaDoc signature) {
473         //String name = signature.split("(::)")
474
// Find next name we're looking for
475
String JavaDoc name = getNextSigComponent(signature);
476         signature = signature.substring(name.length());
477
478         return findBySignature(root, signature, name);
479     }
480
481     // For a signature of the form Foo::Bar#baz(arg1,arg2,...)
482
// pull out the next component; in the above, successively return
483
// "Foo", "Bar", "baz", etc.
484
private static String JavaDoc getNextSigComponent(String JavaDoc signature) {
485         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
486         int i = 0;
487         int n = signature.length();
488
489         // Skip leading separators
490
for (; i < n; i++) {
491             char c = signature.charAt(i);
492
493             if ((c == '#') || (c == ':') || (c == '(')) {
494                 continue;
495             }
496
497             break;
498         }
499
500         // Add the name
501
for (; i < n; i++) {
502             char c = signature.charAt(i);
503
504             if ((c == '#') || (c == ':') || (c == '(')) {
505                 break;
506             }
507
508             sb.append(c);
509         }
510
511         return sb.toString();
512     }
513
514     private static Node findBySignature(Node node, String JavaDoc signature, String JavaDoc name) {
515         boolean lookingForMethod = (Character.isLowerCase(name.charAt(0)));
516
517         if (lookingForMethod) {
518             if ((node instanceof DefnNode || node instanceof DefsNode) &&
519                     name.equals(AstUtilities.getDefName(node))) {
520                 // See if the parameter list matches
521
// XXX TODO
522
List JavaDoc<String JavaDoc> parameters = getDefArgs(node);
523
524                 if ((signature.length() == 0) &&
525                         ((parameters == null) || (parameters.size() == 0))) {
526                     // No args
527
return node;
528                 } else if (signature.length() != 0) {
529                     assert signature.charAt(0) == '(';
530
531                     String JavaDoc argList = signature.substring(1, signature.length() - 1);
532                     String JavaDoc[] args = argList.split(",");
533
534                     if (args.length == parameters.size()) {
535                         // Should I enforce equality here?
536
boolean equal = true;
537
538                         for (int i = 0; i < args.length; i++) {
539                             if (!args[i].equals(parameters.get(i))) {
540                                 equal = false;
541
542                                 break;
543                             }
544                         }
545
546                         if (equal) {
547                             return node;
548                         }
549                     }
550                 }
551             }
552         } else if ((node instanceof ModuleNode || node instanceof ClassNode) &&
553                 name.equals(AstUtilities.getClassOrModuleName(((IScopingNode)node)))) {
554             name = getNextSigComponent(signature);
555
556             if (name.length() == 0) {
557                 // The signature points to a class (or module) - just return it
558
return node;
559             }
560
561             int index = signature.indexOf(name);
562             assert index != -1;
563             signature = signature.substring(index + name.length());
564         }
565
566         @SuppressWarnings JavaDoc("unchecked")
567         List JavaDoc<Node> list = node.childNodes();
568
569         for (Node child : list) {
570             Node match = findBySignature(child, signature, name);
571
572             if (match != null) {
573                 return match;
574             }
575         }
576
577         return null;
578     }
579
580     /**
581      * Return a range that matches the given node's source buffer range
582      */

583     public static OffsetRange getRange(Node node) {
584         ISourcePosition pos = node.getPosition();
585         OffsetRange range = new OffsetRange(pos.getStartOffset(), pos.getEndOffset());
586
587         return range;
588     }
589
590     /**
591      * Return a range that matches the lvalue for an assignment. The node must be namable.
592      */

593     public static OffsetRange getLValueRange(AssignableNode node) {
594         assert node instanceof INameNode;
595         assert node instanceof AssignableNode;
596
597         ISourcePosition pos = node.getPosition();
598         OffsetRange range =
599             new OffsetRange(pos.getStartOffset(),
600                 pos.getStartOffset() + ((INameNode)node).getName().length());
601
602         return range;
603     }
604
605     /** For CallNodes, the offset range for the AST node includes the entire parameter list.
606      * We want ONLY the actual call/operator name. So compute that on our own.
607      */

608     public static OffsetRange getCallRange(Node node) {
609         ISourcePosition pos = node.getPosition();
610         int start = pos.getStartOffset();
611         int end = pos.getEndOffset();
612         assert node instanceof VCallNode || node instanceof CallNode || node instanceof FCallNode;
613         assert node instanceof INameNode;
614
615         if (node instanceof CallNode) {
616             // A call of the form Foo.bar. "bar" is the CallNode, "Foo" is the ReceiverNode.
617
// Here I'm only handling named nodes; there may be others
618
Node receiver = ((CallNode)node).getReceiverNode();
619
620             if (receiver != null) {
621                 start = receiver.getPosition().getEndOffset() + 1; // end of "Foo::bar" + "."
622
}
623         }
624
625         if (node instanceof INameNode) {
626             end = start + ((INameNode)node).getName().length();
627         }
628
629         return new OffsetRange(start, end);
630     }
631
632     @SuppressWarnings JavaDoc("unchecked")
633     public static OffsetRange getFunctionNameRange(Node node) {
634         for (Node child : (List JavaDoc<Node>)node.childNodes()) {
635             if (child instanceof ArgumentNode) {
636                 OffsetRange range = AstUtilities.getRange(child);
637
638                 return range;
639             }
640         }
641
642         if (node instanceof DefsNode || node instanceof DefnNode) {
643             for (Node child : (List JavaDoc<Node>)node.childNodes()) {
644                 if (child instanceof ConstNode) {
645                     ISourcePosition pos = child.getPosition();
646                     int end = pos.getEndOffset();
647                     int start;
648
649                     if (INCLUDE_DEFS_PREFIX) {
650                         start = pos.getStartOffset();
651                     } else {
652                         start = end + 1;
653                     }
654
655                     // TODO - look at the source buffer and tweak offset if it's wrong
656
// This assumes we have a single constant node, followed by a dot, followed by the name
657
end = end + 1 + AstUtilities.getDefName(node).length(); // +1: "."
658

659                     OffsetRange range = new OffsetRange(start, end);
660
661                     return range;
662                 }
663             }
664         }
665
666         return OffsetRange.NONE;
667     }
668
669     /**
670      * Return the OffsetRange for an AliasNode that represents the new name portion.
671      */

672     public static OffsetRange getAliasNewRange(AliasNode node) {
673         // XXX I don't know where the old and new names are since the user COULD
674
// have used more than one whitespace character for separation. For now I'll
675
// just have to assume it's the normal case with one space: alias new old.
676
// I -could- use the getPosition.getEndOffset() to see if this looks like it's
677
// the case (e.g. node length != "alias ".length + old.length+new.length+1).
678
// In this case I could go peeking in the source buffer to see where the
679
// spaces are - between alias and the first word or between old and new. XXX.
680
ISourcePosition pos = node.getPosition();
681
682         //int newStart = pos.getStartOffset() + 6; // 6: "alias ".length()
683
// BUG: AliasNode currently seems to have wrong offsets; in particular, it starts after the keyword
684
int newStart = pos.getStartOffset() + 1;
685
686         return new OffsetRange(newStart, newStart + node.getNewName().length());
687     }
688
689     /**
690      * Return the OffsetRange for an AliasNode that represents the old name portion.
691      */

692     public static OffsetRange getAliasOldRange(AliasNode node) {
693         // XXX I don't know where the old and new names are since the user COULD
694
// have used more than one whitespace character for separation. For now I'll
695
// just have to assume it's the normal case with one space: alias new old.
696
// I -could- use the getPosition.getEndOffset() to see if this looks like it's
697
// the case (e.g. node length != "alias ".length + old.length+new.length+1).
698
// In this case I could go peeking in the source buffer to see where the
699
// spaces are - between alias and the first word or between old and new. XXX.
700
ISourcePosition pos = node.getPosition();
701
702         //int oldStart = pos.getStartOffset() + 6 + node.getNewName().length() + 1; // 6: "alias ".length; 1: " ".length
703
// BUG: AliasNode currently seems to have wrong offsets; in particular, it starts after the keyword
704
int oldStart = pos.getStartOffset() + 1 + node.getNewName().length() + 1; // 6: "alias ".length; 1: " ".length
705

706         return new OffsetRange(oldStart, oldStart + node.getOldName().length());
707     }
708
709     public static String JavaDoc getClassOrModuleName(IScopingNode node) {
710         return ((INameNode)node.getCPath()).getName();
711     }
712
713     public static List JavaDoc<ClassNode> getClasses(Node root) {
714         // I would like to use a visitor for this, but it's not
715
// working - I get NPE's within DefaultIteratorVisitor
716
// on valid ASTs, and I see it's not used heavily in JRuby,
717
// so I'm not doing it this way for now.
718
//final List<ClassNode> classes = new ArrayList<ClassNode>();
719
//// There could be multiple Class definitions for this
720
//// same class, and (empirically) rdoc shows the documentation
721
//// for the last declaration.
722
//NodeVisitor findClasses = new AbstractVisitor() {
723
// public Instruction visitClassNode(ClassNode node) {
724
// classes.add(node);
725
// return visitNode(node);
726
// }
727
//
728
// protected Instruction visitNode(Node iVisited) {
729
// return null;
730
// }
731
//};
732
//new DefaultIteratorVisitor(findClasses).visitRootNode((RootNode)parseResult.getRootNode());
733
List JavaDoc<ClassNode> classes = new ArrayList JavaDoc<ClassNode>();
734         addClasses(root, classes);
735
736         return classes;
737     }
738
739     private static void addClasses(Node node, List JavaDoc<ClassNode> classes) {
740         if (node instanceof ClassNode) {
741             classes.add((ClassNode)node);
742         }
743
744         @SuppressWarnings JavaDoc("unchecked")
745         List JavaDoc<Node> list = node.childNodes();
746
747         for (Node child : list) {
748             addClasses(child, classes);
749         }
750     }
751
752     private static void addAncestorParents(Node node, StringBuilder JavaDoc sb) {
753         if (node instanceof Colon2Node) {
754             Colon2Node c2n = (Colon2Node)node;
755             addAncestorParents(c2n.getLeftNode(), sb);
756
757             if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
758                 sb.append("::");
759             }
760
761             sb.append(c2n.getName());
762         } else if (node instanceof INameNode) {
763             if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != ':')) {
764                 sb.append("::");
765             }
766
767             sb.append(((INameNode)node).getName());
768         }
769     }
770
771     public static String JavaDoc getFqn(Colon2Node c2n) {
772         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
773
774         addAncestorParents(c2n, sb);
775
776         return sb.toString();
777     }
778
779     public static String JavaDoc getSuperclass(ClassNode clz) {
780         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
781
782         if (clz.getSuperNode() != null) {
783             addAncestorParents(clz.getSuperNode(), sb);
784
785             return sb.toString();
786         }
787
788         return null;
789     }
790
791     /** Compute the module/class name for the given node path */
792     public static String JavaDoc getFqnName(AstPath path) {
793         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
794
795         Iterator JavaDoc<Node> it = path.rootToLeaf();
796
797         while (it.hasNext()) {
798             Node node = it.next();
799
800             if (node instanceof ModuleNode || node instanceof ClassNode) {
801                 Node cpath = ((IScopingNode)node).getCPath();
802
803                 if ((cpath != null) && cpath instanceof INameNode) {
804                     if (sb.length() > 0) {
805                         sb.append("::"); // NOI18N
806
}
807
808                     sb.append(((INameNode)cpath).getName());
809                 }
810             }
811         }
812
813         return sb.toString();
814     }
815
816     public static boolean isAttr(Node node) {
817         if (!(node instanceof FCallNode)) {
818             return false;
819         }
820
821         String JavaDoc name = ((INameNode)node).getName();
822
823         if (name.startsWith("attr")) { // NOI18N
824

825             if ("attr".equals(name) || "attr_reader".equals(name) || // NOI18N
826
"attr_accessor".equals(name) || "attr_writer".equals(name)) { // NOI18N
827

828                 return true;
829             }
830         }
831
832         return false;
833     }
834
835     @SuppressWarnings JavaDoc("unchecked")
836     public static SymbolNode[] getAttrSymbols(Node node) {
837         assert isAttr(node);
838
839         List JavaDoc<Node> list = node.childNodes();
840
841         for (Node child : list) {
842             if (child instanceof ListNode) {
843                 List JavaDoc<Node> symbols = (List JavaDoc<Node>)child.childNodes();
844                 List JavaDoc<SymbolNode> symbolList = new ArrayList JavaDoc<SymbolNode>(symbols.size());
845
846                 for (Node symbol : symbols) {
847                     if (symbol instanceof SymbolNode) {
848                         symbolList.add((SymbolNode)symbol);
849                     }
850                 }
851
852                 return symbolList.toArray(new SymbolNode[symbolList.size()]);
853             }
854         }
855
856         return new SymbolNode[0];
857     }
858
859     // TODO use this from all the various places that have this inlined...
860
public static Node getRoot(CompilationInfo info) {
861         ParserResult result = info.getParserResult();
862
863         if (result == null) {
864             return null;
865         }
866
867         return getRoot(result);
868     }
869
870     public static Node getRoot(ParserResult r) {
871         assert r instanceof RubyParseResult;
872
873         RubyParseResult result = (RubyParseResult)r;
874
875         // TODO - just call result.getRoot()
876
// but I might have to compensate for the new RootNode behavior in JRuby
877
ParserResult.AstTreeNode ast = result.getAst();
878
879         if (ast == null) {
880             return null;
881         }
882
883         Node root = (Node)ast.getAstNode();
884
885         return root;
886     }
887
888     /**
889      * Get the private and protected methods in the given class
890      */

891     public static void findPrivateMethods(Node clz, Set JavaDoc<Node> protectedMethods,
892         Set JavaDoc<Node> privateMethods) {
893         Set JavaDoc<String JavaDoc> publicMethodSymbols = new HashSet JavaDoc<String JavaDoc>();
894         Set JavaDoc<String JavaDoc> protectedMethodSymbols = new HashSet JavaDoc<String JavaDoc>();
895         Set JavaDoc<String JavaDoc> privateMethodSymbols = new HashSet JavaDoc<String JavaDoc>();
896         Set JavaDoc<Node> publicMethods = new HashSet JavaDoc<Node>();
897
898         @SuppressWarnings JavaDoc("unchecked")
899         List JavaDoc<Node> list = clz.childNodes();
900
901         Modifier access = Modifier.PUBLIC;
902
903         for (Node child : list) {
904             access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
905                     privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
906         }
907
908         // Can't just return private methods directly, since sometimes you can
909
// specify that a particular method is public before we know about it,
910
// so I can't just remove it from the known private list when I see the
911
// access modifier
912
privateMethodSymbols.removeAll(publicMethodSymbols);
913         protectedMethodSymbols.removeAll(publicMethodSymbols);
914
915         // Should I worry about private foo; protected :foo ?
916
// Seems unlikely somebody would do that... I guess
917
// I could do privateMethodSymbols.removeAll(protectedMethodSymbols) etc.
918
//privateMethodSymbols.removeAll(protectedMethodSymbols);
919
//protectedMethodSymbols.removeAll(privateMethodSymbols);
920

921         // Add all methods known to be private into the private node set
922
for (String JavaDoc name : privateMethodSymbols) {
923             for (Node n : publicMethods) {
924                 if (name.equals(AstUtilities.getDefName(n))) {
925                     privateMethods.add(n);
926                 }
927             }
928         }
929
930         for (String JavaDoc name : protectedMethodSymbols) {
931             for (Node n : publicMethods) {
932                 if (name.equals(AstUtilities.getDefName(n))) {
933                     protectedMethods.add(n);
934                 }
935             }
936         }
937     }
938
939     /**
940      * @todo Should I really recurse into classes? If I have nested classes private
941      * methods ther shouldn't be included for the parent!
942      *
943      * @param access The "current" known access level (PUBLIC, PROTECTED or PRIVATE)
944      * @return the access level to continue with at this syntactic level
945      */

946     @SuppressWarnings JavaDoc("unchecked")
947     private static Modifier getMethodAccess(Node node, Modifier access,
948         Set JavaDoc<String JavaDoc> publicMethodSymbols, Set JavaDoc<String JavaDoc> protectedMethodSymbols,
949         Set JavaDoc<String JavaDoc> privateMethodSymbols, Set JavaDoc<Node> publicMethods, Set JavaDoc<Node> protectedMethods,
950         Set JavaDoc<Node> privateMethods) {
951         if (node instanceof DefsNode || node instanceof DefnNode) {
952             if (access == Modifier.PRIVATE) {
953                 privateMethods.add(node);
954             } else if (access == Modifier.PUBLIC) {
955                 publicMethods.add(node);
956             } else if (access == Modifier.PROTECTED) {
957                 protectedMethods.add(node);
958             }
959
960             // XXX Can I have nested method definitions? If so I may have to continue here
961
return access;
962         } else if (node instanceof VCallNode || node instanceof FCallNode) {
963             String JavaDoc name = ((INameNode)node).getName();
964
965             if ("private".equals(name)) {
966                 // TODO - see if it has arguments, if it does - it's just a single
967
// method defined to be private
968
// Iterate over arguments and add symbols...
969
if (Arity.callHasArguments(node)) {
970                     List JavaDoc<Node> params = (List JavaDoc<Node>)node.childNodes();
971
972                     for (Node param : params) {
973                         if (param instanceof ListNode) {
974                             List JavaDoc<Node> params2 = (List JavaDoc<Node>)param.childNodes();
975
976                             for (Node param2 : params2) {
977                                 if (param2 instanceof SymbolNode) {
978                                     String JavaDoc symbol = ((SymbolNode)param2).getName();
979                                     privateMethodSymbols.add(symbol);
980                                 }
981                             }
982                         }
983                     }
984                 } else {
985                     access = Modifier.PRIVATE;
986                 }
987
988                 return access;
989             } else if ("protected".equals(name)) {
990                 // TODO - see if it has arguments, if it does - it's just a single
991
// method defined to be private
992
// Iterate over arguments and add symbols...
993
if (Arity.callHasArguments(node)) {
994                     List JavaDoc<Node> params = (List JavaDoc<Node>)node.childNodes();
995
996                     for (Node param : params) {
997                         if (param instanceof ListNode) {
998                             List JavaDoc<Node> params2 = (List JavaDoc<Node>)param.childNodes();
999
1000                            for (Node param2 : params2) {
1001                                if (param2 instanceof SymbolNode) {
1002                                    String JavaDoc symbol = ((SymbolNode)param2).getName();
1003                                    protectedMethodSymbols.add(symbol);
1004                                }
1005                            }
1006                        }
1007                    }
1008                } else {
1009                    access = Modifier.PROTECTED;
1010                }
1011
1012                return access;
1013            } else if ("public".equals(name)) {
1014                if (!Arity.callHasArguments(node)) {
1015                    access = Modifier.PUBLIC;
1016
1017                    return access;
1018                } else {
1019                    List JavaDoc<Node> params = (List JavaDoc<Node>)node.childNodes();
1020
1021                    for (Node param : params) {
1022                        if (param instanceof ListNode) {
1023                            List JavaDoc<Node> params2 = (List JavaDoc<Node>)param.childNodes();
1024
1025                            for (Node param2 : params2) {
1026                                if (param2 instanceof SymbolNode) {
1027                                    String JavaDoc symbol = ((SymbolNode)param2).getName();
1028                                    publicMethodSymbols.add(symbol);
1029                                }
1030                            }
1031                        }
1032                    }
1033                }
1034            }
1035
1036            return access;
1037        } else if (node instanceof ClassNode || node instanceof ModuleNode) {
1038            return access;
1039        }
1040
1041        @SuppressWarnings JavaDoc("unchecked")
1042        List JavaDoc<Node> list = node.childNodes();
1043
1044        for (Node child : list) {
1045            access = getMethodAccess(child, access, publicMethodSymbols, protectedMethodSymbols,
1046                    privateMethodSymbols, publicMethods, protectedMethods, privateMethods);
1047        }
1048
1049        return access;
1050    }
1051}
1052
Popular Tags