1 19 package org.netbeans.modules.ruby; 20 21 import java.util.ArrayList ; 22 import java.util.Collection ; 23 import java.util.Collections ; 24 import java.util.EnumSet ; 25 import java.util.HashMap ; 26 import java.util.HashSet ; 27 import java.util.Iterator ; 28 import java.util.List ; 29 import java.util.Map ; 30 import java.util.Set ; 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 85 public class StructureAnalyzer implements StructureScanner { 86 private Set <AstClassElement> haveAccessModifiers; 87 private List <Element> structure; 88 private Map <AstClassElement, Set <InstAsgnNode>> fields; 89 private Set <String > requires; 90 private List <AstMethodElement> methods; 91 private HtmlFormatter formatter; 92 private RubyParseResult result; 93 94 public StructureAnalyzer() { 95 } 96 97 public List <?extends StructureItem> scan(CompilationInfo info, HtmlFormatter formatter) { 98 this.result = (RubyParseResult)info.getParserResult(); 99 this.formatter = formatter; 100 101 List <?extends Element> elements = scan(result); 102 List <StructureItem> structure = new ArrayList <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 <?extends Element> scan(RubyParseResult result) { 113 if (result.getStructure() != null) { 114 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 <Element>(); 125 fields = new HashMap <AstClassElement, Set <InstAsgnNode>>(); 126 requires = new HashSet <String >(); 127 methods = new ArrayList <AstMethodElement>(); 128 haveAccessModifiers = new HashSet <AstClassElement>(); 129 130 AstPath path = new AstPath(); 131 path.descend(root); 132 scan(root, path, null, null, null); 134 path.ascend(); 135 136 Map <String , InstAsgnNode> names = new HashMap <String , InstAsgnNode>(); 138 139 for (AstClassElement clz : fields.keySet()) { 140 Set <InstAsgnNode> assignments = fields.get(clz); 141 142 if (assignments != null) { 144 for (InstAsgnNode assignment : assignments) { 145 names.put(assignment.getName(), assignment); 146 } 147 148 for (InstAsgnNode field : names.values()) { 150 AstFieldElement co = new AstFieldElement(field); 151 co.setIn(clz.getFqn()); 153 154 String 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 for (AstClassElement clz : haveAccessModifiers) { 186 Set <Node> protectedMethods = new HashSet <Node>(); 190 Set <Node> privateMethods = new HashSet <Node>(); 191 AstUtilities.findPrivateMethods(clz.getNode(), protectedMethods, privateMethods); 192 193 if (privateMethods.size() > 0) { 194 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 } 207 208 if (protectedMethods.size() > 0) { 209 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 } 222 } 223 224 result.setStructure(structure); 226 result.setRequires(requires); 227 228 return structure; 229 } 230 231 public List <OffsetRange> folds(CompilationInfo info) { 232 Node root = AstUtilities.getRoot(info); 233 234 if (root == null) { 235 return Collections.emptyList(); 236 } 237 238 List <OffsetRange> folds = new ArrayList <OffsetRange>(); 239 240 addFolds(root, folds); 241 242 return folds; 243 } 244 245 private void scan(Node node, AstPath path, String in, Set <String > includes, AstElement parent) { 246 if (node instanceof ClassNode) { 248 AstClassElement co = new AstClassElement(node); 249 co.setIn(in); 250 co.setFqn(AstUtilities.getFqnName(path)); 251 in = AstUtilities.getClassOrModuleName((ClassNode)node); 253 includes = new HashSet <String >(); 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 } else if (node instanceof DefnNode || node instanceof DefsNode) { 279 AstMethodElement co = new AstMethodElement(node); 280 methods.add(co); 281 co.setIn(in); 282 283 if ((node instanceof DefnNode) && "initialize".equals(((DefnNode)node).getName())) { 285 co.setAccess(Modifier.PRIVATE); 286 } 287 288 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 Set <InstAsgnNode> assignments = fields.get(parent); 316 317 if (assignments == null) { 318 assignments = new HashSet <InstAsgnNode>(); 319 fields.put((AstClassElement)parent, assignments); 320 } 321 322 assignments.add((InstAsgnNode)node); 323 } else if (node instanceof VCallNode) { 324 String name = ((INameNode)node).getName(); 325 326 if (("private".equals(name) || "protected".equals(name)) && 327 parent instanceof AstClassElement) { haveAccessModifiers.add((AstClassElement)parent); 329 } 330 } else if (node instanceof FCallNode) { 331 String name = ((INameNode)node).getName(); 332 333 if (name.equals("require")) { 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 String className = ((INameNode)n).getName(); 370 String include = getIncludeFqn(path, className); 371 includes.add(include); 372 } 373 } 374 } 375 } else if (("private".equals(name) || "protected".equals(name)) && 376 parent instanceof AstClassElement) { haveAccessModifiers.add((AstClassElement)parent); 378 } else if (AstUtilities.isAttr(node)) { 379 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")) { 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 func = ((SymbolNode)n).getName(); 408 409 if ((func != null) && (func.length() > 0)) { 410 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 method = jn; 420 break; 421 } 422 } 423 } 424 425 if (method != null) { 426 Node dupeNode = method.getNode(); 428 AstMethodElement co = new AstMethodElement(dupeNode); 429 co.setIn(in); 430 431 if ((dupeNode instanceof DefnNode) && "initialize".equals(((DefnNode)dupeNode).getName())) { co.setAccess(Modifier.PRIVATE); 434 } 435 436 co.setModifiers(EnumSet.of(Modifier.STATIC)); 439 440 if (parent != null) { 442 parent.addChild(co); 443 } else { 444 structure.add(co); 445 } 446 } 447 } 448 } 449 } 450 } 451 } 452 } 453 454 @SuppressWarnings ("unchecked") 455 List <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 <OffsetRange> folds) { 469 if (node instanceof ClassNode) { 472 folds.add(AstUtilities.getRange(node)); 473 } else if (node instanceof ModuleNode) { 474 folds.add(AstUtilities.getRange(node)); 476 } else if (node instanceof DefnNode || node instanceof DefsNode) { 477 folds.add(AstUtilities.getRange(node)); 478 } 479 480 @SuppressWarnings ("unchecked") 481 List <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 <String > getRequires() { 496 return requires; 497 } 498 499 private String getIncludeFqn(AstPath path, String name) { 500 StringBuilder sb = new StringBuilder (); 501 502 Iterator <Node> it = path.rootToLeaf(); 503 504 while (it.hasNext()) { 505 Node node = it.next(); 506 507 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("::"); } 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 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 540 @SuppressWarnings ("unchecked") 541 List <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 ("unchecked") 555 List <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 getName() { 589 return node.getName(); 590 } 591 592 public String getHtml() { 593 formatter.reset(); 594 formatter.appendText(node.getName()); 595 596 if ((kind == ElementKind.METHOD) || (kind == ElementKind.CONSTRUCTOR)) { 597 AstMethodElement jn = (AstMethodElement)node; 599 600 Collection <String > parameters = jn.getParameters(); 601 602 if ((parameters != null) && (parameters.size() > 0)) { 603 formatter.appendHtml("("); 604 formatter.parameters(true); 605 606 for (Iterator <String > it = parameters.iterator(); it.hasNext();) { 607 String ve = it.next(); 608 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 <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 ("Unhandled kind: " + kind); 654 } 655 } 656 657 public List <?extends StructureItem> getNestedItems() { 658 List <AstElement> nested = node.getChildren(); 659 660 if ((nested != null) && (nested.size() > 0)) { 661 List <RubyStructureItem> children = new ArrayList <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 ("Not supported yet."); 675 } 676 677 @Override 678 public boolean equals(Object o) { 679 if (o == null) { 680 return false; 681 } 682 683 if (!(o instanceof RubyStructureItem)) { 684 return false; 686 } 687 688 RubyStructureItem d = (RubyStructureItem)o; 689 690 if (kind != d.kind) { 691 return false; 693 } 694 695 if (!getName().equals(d.getName())) { 696 return false; 698 } 699 700 704 710 711 return true; 713 } 714 715 @Override 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 return hash; 724 } 725 726 @Override 727 public String toString() { 728 return getName(); 729 } 730 } 731 } 732 | Popular Tags |