KickJava   Java API By Example, From Geeks To Geeks.

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


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.util.ArrayList JavaDoc;
22 import java.util.Collection JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.EnumSet JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.HashSet JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31 import org.jruby.ast.ClassNode;
32 import org.jruby.ast.ClassVarDeclNode;
33 import org.jruby.ast.Colon2Node;
34 import org.jruby.ast.CommentNode;
35 import org.jruby.ast.ConstDeclNode;
36 import org.jruby.ast.DefnNode;
37 import org.jruby.ast.DefsNode;
38 import org.jruby.ast.FCallNode;
39 import org.jruby.ast.IScopingNode;
40 import org.jruby.ast.InstAsgnNode;
41 import org.jruby.ast.ListNode;
42 import org.jruby.ast.ModuleNode;
43 import org.jruby.ast.Node;
44 import org.jruby.ast.StrNode;
45 import org.jruby.ast.SymbolNode;
46 import org.jruby.ast.VCallNode;
47 import org.jruby.ast.types.INameNode;
48 import org.jruby.lexer.yacc.ISourcePosition;
49 import org.jruby.parser.RubyParserResult;
50 import org.jruby.util.ByteList;
51 import org.netbeans.api.gsf.CompilationInfo;
52 import org.netbeans.api.gsf.Element;
53 import org.netbeans.api.gsf.ElementHandle;
54 import org.netbeans.api.gsf.ElementKind;
55 import org.netbeans.api.gsf.HtmlFormatter;
56 import org.netbeans.api.gsf.Modifier;
57 import org.netbeans.api.gsf.OffsetRange;
58 import org.netbeans.api.gsf.StructureItem;
59 import org.netbeans.api.gsf.StructureScanner;
60 import org.netbeans.modules.ruby.elements.AstAttributeElement;
61 import org.netbeans.modules.ruby.elements.AstClassElement;
62 import org.netbeans.modules.ruby.elements.AstConstantElement;
63 import org.netbeans.modules.ruby.elements.AstElement;
64 import org.netbeans.modules.ruby.elements.AstFieldElement;
65 import org.netbeans.modules.ruby.elements.AstMethodElement;
66 import org.netbeans.modules.ruby.elements.AstModuleElement;
67
68
69 /**
70  * @todo Access modifiers
71  * @todo Fields
72  * @todo Statics
73  * @todo Properties (attr_accessor etc.)
74  *
75  * @todo Rewrite various other helper classes to use the scanned structure
76  * for the file instead of searching from scratch. For example, the code
77  * completion scanner should rely on the structure view to add local
78  * classes, fields and globals - it should only scan the current method
79  * for local variables. Similarly, the declaration finder should use it
80  * to locate local classes, method definitions and such. And obviously,
81  * the semantic analyzer should use it to find private methods.
82  *
83  * @author Tor Norbye
84  */

85 public class StructureAnalyzer implements StructureScanner {
86     private Set JavaDoc<AstClassElement> haveAccessModifiers;
87     private List JavaDoc<Element> structure;
88     private Map JavaDoc<AstClassElement, Set JavaDoc<InstAsgnNode>> fields;
89     private Set JavaDoc<String JavaDoc> requires;
90     private List JavaDoc<AstMethodElement> methods;
91     private HtmlFormatter formatter;
92     private RubyParseResult result;
93
94     public StructureAnalyzer() {
95     }
96
97     public List JavaDoc<?extends StructureItem> scan(CompilationInfo info, HtmlFormatter formatter) {
98         this.result = (RubyParseResult)info.getParserResult();
99         this.formatter = formatter;
100
101         List JavaDoc<?extends Element> elements = scan(result);
102         List JavaDoc<StructureItem> structure = new ArrayList JavaDoc<StructureItem>(elements.size());
103
104         for (Element e : elements) {
105             AstElement jn = (AstElement)e;
106             structure.add(new RubyStructureItem(jn, info));
107         }
108
109         return structure;
110     }
111
112     private List JavaDoc<?extends Element> scan(RubyParseResult result) {
113         if (result.getStructure() != null) {
114             // Already done
115
return result.getStructure();
116         }
117
118         Node root = AstUtilities.getRoot(result);
119
120         if (root == null) {
121             return Collections.emptyList();
122         }
123
124         structure = new ArrayList JavaDoc<Element>();
125         fields = new HashMap JavaDoc<AstClassElement, Set JavaDoc<InstAsgnNode>>();
126         requires = new HashSet JavaDoc<String JavaDoc>();
127         methods = new ArrayList JavaDoc<AstMethodElement>();
128         haveAccessModifiers = new HashSet JavaDoc<AstClassElement>();
129
130         AstPath path = new AstPath();
131         path.descend(root);
132         // TODO: I should pass in a "default" context here to stash methods etc. outside of modules and classes
133
scan(root, path, null, null, null);
134         path.ascend();
135
136         // Process fields
137
Map JavaDoc<String JavaDoc, InstAsgnNode> names = new HashMap JavaDoc<String JavaDoc, InstAsgnNode>();
138
139         for (AstClassElement clz : fields.keySet()) {
140             Set JavaDoc<InstAsgnNode> assignments = fields.get(clz);
141
142             // Find unique variables
143
if (assignments != null) {
144                 for (InstAsgnNode assignment : assignments) {
145                     names.put(assignment.getName(), assignment);
146                 }
147
148                 // Add unique fields
149
for (InstAsgnNode field : names.values()) {
150                     AstFieldElement co = new AstFieldElement(field);
151                     //co.setIn(AstUtilities.getClassOrModuleName(clz));
152
co.setIn(clz.getFqn());
153
154                     // Make sure I don't already have an entry for this field as an
155
// attr_accessor or writer
156
String JavaDoc fieldName = field.getName();
157
158                     if (fieldName.startsWith("@@")) {
159                         fieldName = fieldName.substring(2);
160                     } else if (fieldName.startsWith("@")) {
161                         fieldName = fieldName.substring(1);
162                     }
163
164                     boolean found = false;
165
166                     for (AstElement member : clz.getChildren()) {
167                         if ((member.getKind() == ElementKind.ATTRIBUTE) &&
168                                 member.getName().equals(fieldName)) {
169                             found = true;
170
171                             break;
172                         }
173                     }
174
175                     if (!found) {
176                         clz.addChild(co);
177                     }
178                 }
179
180                 names.clear();
181             }
182         }
183
184         // Process access modifiers
185
for (AstClassElement clz : haveAccessModifiers) {
186             // There are "public", "protected" or "private" modifiers in the
187
// document; we should scan it more carefully for these and
188
// annotate them properly
189
Set JavaDoc<Node> protectedMethods = new HashSet JavaDoc<Node>();
190             Set JavaDoc<Node> privateMethods = new HashSet JavaDoc<Node>();
191             AstUtilities.findPrivateMethods(clz.getNode(), protectedMethods, privateMethods);
192
193             if (privateMethods.size() > 0) {
194                 // TODO: Annotate my structure elements appropriately
195
for (Element o : methods) {
196                     if (o instanceof AstMethodElement) {
197                         AstMethodElement jn = (AstMethodElement)o;
198
199                         if (privateMethods.contains(jn.getNode())) {
200                             jn.setAccess(Modifier.PRIVATE);
201                         }
202                     }
203                 }
204
205                 // TODO: Private fields!
206
}
207
208             if (protectedMethods.size() > 0) {
209                 // TODO: Annotate my structure elements appropriately
210
for (Element o : methods) {
211                     if (o instanceof AstMethodElement) {
212                         AstMethodElement jn = (AstMethodElement)o;
213
214                         if (protectedMethods.contains(jn.getNode())) {
215                             jn.setAccess(Modifier.PROTECTED);
216                         }
217                     }
218                 }
219
220                 // TODO: Protected fields!
221
}
222         }
223
224         // Stash the structure with the AST so other helper methods can use it
225
result.setStructure(structure);
226         result.setRequires(requires);
227
228         return structure;
229     }
230
231     public List JavaDoc<OffsetRange> folds(CompilationInfo info) {
232         Node root = AstUtilities.getRoot(info);
233
234         if (root == null) {
235             return Collections.emptyList();
236         }
237
238         List JavaDoc<OffsetRange> folds = new ArrayList JavaDoc<OffsetRange>();
239
240         addFolds(root, folds);
241
242         return folds;
243     }
244
245     private void scan(Node node, AstPath path, String JavaDoc in, Set JavaDoc<String JavaDoc> includes, AstElement parent) {
246         // Recursively search for methods or method calls that match the name and arity
247
if (node instanceof ClassNode) {
248             AstClassElement co = new AstClassElement(node);
249             co.setIn(in);
250             co.setFqn(AstUtilities.getFqnName(path));
251             // Pass on to children
252
in = AstUtilities.getClassOrModuleName((ClassNode)node);
253             includes = new HashSet JavaDoc<String JavaDoc>();
254             co.setIncludes(includes);
255
256             if (parent != null) {
257                 parent.addChild(co);
258             } else {
259                 structure.add(co);
260             }
261
262             parent = co;
263         } else if (node instanceof ModuleNode) {
264             AstModuleElement co = new AstModuleElement(node);
265             co.setIn(in);
266             co.setFqn(AstUtilities.getFqnName(path));
267             in = AstUtilities.getClassOrModuleName((ModuleNode)node);
268
269             if (parent != null) {
270                 parent.addChild(co);
271             } else {
272                 structure.add(co);
273             }
274
275             parent = co;
276
277             // XXX Can I have includes on modules?
278
} else if (node instanceof DefnNode || node instanceof DefsNode) {
279             AstMethodElement co = new AstMethodElement(node);
280             methods.add(co);
281             co.setIn(in);
282
283             // "initialize" methods are private
284
if ((node instanceof DefnNode) && "initialize".equals(((DefnNode)node).getName())) {
285                 co.setAccess(Modifier.PRIVATE);
286             }
287
288             // TODO - don't add this to the top level! Make a nested list
289
if (parent != null) {
290                 parent.addChild(co);
291             } else {
292                 structure.add(co);
293             }
294         } else if (node instanceof ConstDeclNode) {
295             AstConstantElement co = new AstConstantElement((ConstDeclNode)node);
296             co.setIn(in);
297
298             if (parent != null) {
299                 parent.addChild(co);
300             } else {
301                 structure.add(co);
302             }
303         } else if (node instanceof ClassVarDeclNode) {
304             AstFieldElement co = new AstFieldElement(node);
305             co.setIn(in);
306
307             if (parent != null) {
308                 parent.addChild(co);
309             } else {
310                 structure.add(co);
311             }
312         } else if (node instanceof InstAsgnNode && parent instanceof AstClassElement) {
313             // We don't have unique declarations, only assignments (possibly many)
314
// so stash these in a map and extract unique fields when we're done
315
Set JavaDoc<InstAsgnNode> assignments = fields.get(parent);
316
317             if (assignments == null) {
318                 assignments = new HashSet JavaDoc<InstAsgnNode>();
319                 fields.put((AstClassElement)parent, assignments);
320             }
321
322             assignments.add((InstAsgnNode)node);
323         } else if (node instanceof VCallNode) {
324             String JavaDoc name = ((INameNode)node).getName();
325
326             if (("private".equals(name) || "protected".equals(name)) &&
327                     parent instanceof AstClassElement) { // NOI18N
328
haveAccessModifiers.add((AstClassElement)parent);
329             }
330         } else if (node instanceof FCallNode) {
331             String JavaDoc name = ((INameNode)node).getName();
332
333             if (name.equals("require")) { // XXX Load too?
334

335                 Node argsNode = ((FCallNode)node).getArgsNode();
336
337                 if (argsNode instanceof ListNode) {
338                     ListNode args = (ListNode)argsNode;
339
340                     if (args.size() > 0) {
341                         Node n = args.get(0);
342
343                         if (n instanceof StrNode) {
344                             ByteList require = ((StrNode)n).getValue();
345
346                             if ((require != null) && (require.length() > 0)) {
347                                 requires.add(require.toString());
348                             }
349                         }
350                     }
351                 }
352             } else if ((includes != null) && name.equals("include")) {
353                 Node argsNode = ((FCallNode)node).getArgsNode();
354
355                 if (argsNode instanceof ListNode) {
356                     ListNode args = (ListNode)argsNode;
357
358                     if (args.size() > 0) {
359                         Node n = args.get(0);
360
361                         if (n instanceof Colon2Node) {
362                             includes.add(AstUtilities.getFqn((Colon2Node)n));
363                         } else if (n instanceof INameNode) {
364                             // Get the current module name to prefix to the include
365
// if necessary. For example,
366
// include Assertions
367
// in Test::Unit::TestCase should resolve to
368
// include Test::Unit::Assertions
369
String JavaDoc className = ((INameNode)n).getName();
370                             String JavaDoc include = getIncludeFqn(path, className);
371                             includes.add(include);
372                         }
373                     }
374                 }
375             } else if (("private".equals(name) || "protected".equals(name)) &&
376                     parent instanceof AstClassElement) { // NOI18N
377
haveAccessModifiers.add((AstClassElement)parent);
378             } else if (AstUtilities.isAttr(node)) {
379                 // TODO: Compute the symbols and check for equality
380
// attr_reader, attr_accessor, attr_writer
381
SymbolNode[] symbols = AstUtilities.getAttrSymbols(node);
382
383                 if ((symbols != null) && (symbols.length > 0)) {
384                     for (SymbolNode s : symbols) {
385                         AstAttributeElement co = new AstAttributeElement(s);
386
387                         if (parent != null) {
388                             parent.addChild(co);
389                         } else {
390                             structure.add(co);
391                         }
392                     }
393                 }
394             } else if (name.equals("module_function")) { // NOI18N
395
// TODO: module_function without arguments will make all the following methods
396
// module function - is this common?
397

398                 Node argsNode = ((FCallNode)node).getArgsNode();
399
400                 if (argsNode instanceof ListNode) {
401                     ListNode args = (ListNode)argsNode;
402
403                     for (int j = 0, m = args.size(); j < m; j++) {
404                         Node n = args.get(j);
405
406                         if (n instanceof SymbolNode) {
407                             String JavaDoc func = ((SymbolNode)n).getName();
408
409                             if ((func != null) && (func.length() > 0)) {
410                                 // Find existing method
411

412                                 AstMethodElement method = null;
413                                 for (Element o : methods) {
414                                     if (o instanceof AstMethodElement) {
415                                         AstMethodElement jn = (AstMethodElement)o;
416
417                                         if (func.equals(jn.getName())) {
418                                             // TODO - some kind of arity comparison?
419
method = jn;
420                                             break;
421                                         }
422                                     }
423                                 }
424
425                                 if (method != null) {
426                                     // Make a new static version of the named function
427
Node dupeNode = method.getNode();
428                                     AstMethodElement co = new AstMethodElement(dupeNode);
429                                     co.setIn(in);
430
431                                     // "initialize" methods are private
432
if ((dupeNode instanceof DefnNode) && "initialize".equals(((DefnNode)dupeNode).getName())) { // NOI18N
433
co.setAccess(Modifier.PRIVATE);
434                                     }
435                                     
436                                     // module_functions are static
437
// What about public/protected/private access?
438
co.setModifiers(EnumSet.of(Modifier.STATIC));
439                                     
440                                     // TODO - don't add this to the top level! Make a nested list
441
if (parent != null) {
442                                         parent.addChild(co);
443                                     } else {
444                                         structure.add(co);
445                                     }
446                                 }
447                             }
448                         }
449                     }
450                 }
451             }
452         }
453
454         @SuppressWarnings JavaDoc("unchecked")
455         List JavaDoc<Node> list = node.childNodes();
456
457         if (list == null) {
458             return;
459         }
460
461         for (Node child : list) {
462             path.descend(child);
463             scan(child, path, in, includes, parent);
464             path.ascend();
465         }
466     }
467
468     private void addFolds(Node node, List JavaDoc<OffsetRange> folds) {
469         // Recursively search for methods or method calls that match the name and arity
470
// TODO - reuse datastructure obtained from full structural scan above
471
if (node instanceof ClassNode) {
472             folds.add(AstUtilities.getRange(node));
473         } else if (node instanceof ModuleNode) {
474             // Modules are not usually interesting for folding, are they?
475
folds.add(AstUtilities.getRange(node));
476         } else if (node instanceof DefnNode || node instanceof DefsNode) {
477             folds.add(AstUtilities.getRange(node));
478         }
479
480         @SuppressWarnings JavaDoc("unchecked")
481         List JavaDoc<Node> list = node.childNodes();
482
483         for (Node child : list) {
484             addFolds(child, folds);
485         }
486     }
487
488     public static void analyze(RubyParseResult result) {
489         if (result.getStructure() == null) {
490             StructureAnalyzer analyzer = new StructureAnalyzer();
491             analyzer.scan(result);
492         }
493     }
494
495     public Set JavaDoc<String JavaDoc> getRequires() {
496         return requires;
497     }
498
499     private String JavaDoc getIncludeFqn(AstPath path, String JavaDoc name) {
500         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
501
502         Iterator JavaDoc<Node> it = path.rootToLeaf();
503
504         while (it.hasNext()) {
505             Node node = it.next();
506
507             // Include modules only, not classes
508
if (node instanceof ModuleNode) {
509                 Node cpath = ((IScopingNode)node).getCPath();
510
511                 if ((cpath != null) && cpath instanceof INameNode) {
512                     if (sb.length() > 0) {
513                         sb.append("::"); // NOI18N
514
}
515
516                     sb.append(((INameNode)cpath).getName());
517                 }
518             }
519         }
520
521         if (sb.length() > 0) {
522             sb.append("::");
523         }
524
525         sb.append(name);
526
527         return sb.toString();
528     }
529     
530     /** Look through the comment nodes and associate them with the AST nodes */
531     public void addComments(RubyParseResult result) {
532         Node root = result.getRootNode();
533         if (root == null) {
534             return;
535         }
536         RubyParserResult r = result.getJRubyResult();
537
538         // REALLY slow implementation
539

540         @SuppressWarnings JavaDoc("unchecked")
541         List JavaDoc<CommentNode> comments = r.getCommentNodes();
542         for (CommentNode comment : comments) {
543             ISourcePosition pos = comment.getPosition();
544             int start = pos.getStartOffset();
545             int end = pos.getEndOffset();
546             Node node = findClosest(root, start, end);
547             assert node != null;
548             
549             node.addComment(comment);
550         }
551     }
552     
553     private Node findClosest(Node node, int start, int end) {
554         @SuppressWarnings JavaDoc("unchecked")
555         List JavaDoc<Node> list = node.childNodes();
556
557         ISourcePosition pos = node.getPosition();
558         if (end < pos.getStartOffset()) {
559             return node;
560         }
561         
562         if (start > pos.getEndOffset()) {
563             return null;
564         }
565
566         for (Node child : list) {
567             Node result = findClosest(child, start, end);
568             if (result != null) {
569                 return result;
570             }
571         }
572         
573         return null;
574     }
575
576     private class RubyStructureItem implements StructureItem {
577         AstElement node;
578         ElementKind kind;
579         CompilationInfo info;
580
581         private RubyStructureItem(AstElement node, CompilationInfo info) {
582             this.node = node;
583             this.info = info;
584
585             kind = node.getKind();
586         }
587
588         public String JavaDoc getName() {
589             return node.getName();
590         }
591
592         public String JavaDoc getHtml() {
593             formatter.reset();
594             formatter.appendText(node.getName());
595
596             if ((kind == ElementKind.METHOD) || (kind == ElementKind.CONSTRUCTOR)) {
597                 // Append parameters
598
AstMethodElement jn = (AstMethodElement)node;
599
600                 Collection JavaDoc<String JavaDoc> parameters = jn.getParameters();
601
602                 if ((parameters != null) && (parameters.size() > 0)) {
603                     formatter.appendHtml("(");
604                     formatter.parameters(true);
605
606                     for (Iterator JavaDoc<String JavaDoc> it = parameters.iterator(); it.hasNext();) {
607                         String JavaDoc ve = it.next();
608                         // TODO - if I know types, list the type here instead. For now, just use the parameter name instead
609
formatter.appendText(ve);
610
611                         if (it.hasNext()) {
612                             formatter.appendHtml(", ");
613                         }
614                     }
615
616                     formatter.parameters(false);
617                     formatter.appendHtml(")");
618                 }
619             }
620
621             return formatter.getText();
622         }
623
624         public ElementHandle<?extends Element> getElementHandle() {
625             return info.getParser().createHandle(info, node);
626         }
627
628         public ElementKind getKind() {
629             return kind;
630         }
631
632         public Set JavaDoc<Modifier> getModifiers() {
633             return node.getModifiers();
634         }
635
636         public boolean isLeaf() {
637             switch (kind) {
638             case ATTRIBUTE:
639             case CONSTANT:
640             case CONSTRUCTOR:
641             case METHOD:
642             case FIELD:
643             case KEYWORD:
644             case VARIABLE:
645             case OTHER:
646                 return true;
647
648             case MODULE:
649             case CLASS:
650                 return false;
651
652             default:
653                 throw new RuntimeException JavaDoc("Unhandled kind: " + kind);
654             }
655         }
656
657         public List JavaDoc<?extends StructureItem> getNestedItems() {
658             List JavaDoc<AstElement> nested = node.getChildren();
659
660             if ((nested != null) && (nested.size() > 0)) {
661                 List JavaDoc<RubyStructureItem> children = new ArrayList JavaDoc<RubyStructureItem>(nested.size());
662
663                 for (Element co : nested) {
664                     children.add(new RubyStructureItem((AstElement)co, info));
665                 }
666
667                 return children;
668             } else {
669                 return Collections.emptyList();
670             }
671         }
672
673         public long getPosition() {
674             throw new UnsupportedOperationException JavaDoc("Not supported yet.");
675         }
676
677         @Override JavaDoc
678         public boolean equals(Object JavaDoc o) {
679             if (o == null) {
680                 return false;
681             }
682
683             if (!(o instanceof RubyStructureItem)) {
684                 // System.out.println("- not a desc");
685
return false;
686             }
687
688             RubyStructureItem d = (RubyStructureItem)o;
689
690             if (kind != d.kind) {
691                 // System.out.println("- kind");
692
return false;
693             }
694
695             if (!getName().equals(d.getName())) {
696                 // System.out.println("- name");
697
return false;
698             }
699
700             // if ( !this.elementHandle.signatureEquals(d.elementHandle) ) {
701
// return false;
702
// }
703

704             /*
705             if ( !modifiers.equals(d.modifiers)) {
706                 // E.println("- modifiers");
707                 return false;
708             }
709             */

710
711             // System.out.println("Equals called");
712
return true;
713         }
714
715         @Override JavaDoc
716         public int hashCode() {
717             int hash = 7;
718
719             hash = (29 * hash) + ((this.getName() != null) ? this.getName().hashCode() : 0);
720             hash = (29 * hash) + ((this.kind != null) ? this.kind.hashCode() : 0);
721
722             // hash = 29 * hash + (this.modifiers != null ? this.modifiers.hashCode() : 0);
723
return hash;
724         }
725
726         @Override JavaDoc
727         public String JavaDoc toString() {
728             return getName();
729         }
730     }
731 }
732
Popular Tags