/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * Contributor(s): * * Portions Copyrighted 2008 Sun Microsystems, Inc. */ package org.netbeans.modules.ruby; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.jrubyparser.ast.AliasNode; import org.jrubyparser.ast.ClassNode; import org.jrubyparser.ast.Colon2Node; import org.jrubyparser.ast.ConstDeclNode; import org.jrubyparser.ast.Node; import org.jrubyparser.ast.INameNode; import org.netbeans.modules.csl.api.DeclarationFinder.DeclarationLocation; import org.netbeans.modules.csl.spi.ParserResult; import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; import org.netbeans.modules.ruby.elements.IndexedClass; final class RubyClassDeclarationFinder extends RubyBaseDeclarationFinder<IndexedClass> { private final Node classNode; RubyClassDeclarationFinder() { this(null, null, null, null, null); } RubyClassDeclarationFinder( final ParserResult info, final Node root, final AstPath path, final RubyIndex index, final Node classNode) { super(info, root, path, index); this.classNode = classNode; } DeclarationLocation findClassDeclaration() { String className = ((INameNode)classNode).getName(); // Disable local class searching for now - it should instead be a // criterion for increasing match priority Node localClass = null; // findClassDeclaration(root, className); if (localClass != null) { // Ensure that we have the right FQN if specific if (classNode instanceof Colon2Node) { AstPath classPath = new AstPath(root, localClass); if (classPath.leaf() != null) { String fqn1 = AstUtilities.getFqn((Colon2Node) classNode); String fqn2 = AstUtilities.getFqnName(classPath); if (fqn1.equals(fqn2)) { return fix(getLocation(info, localClass), info); } } else { assert false : localClass.toString(); } } else { return fix(getLocation(info, localClass), info); } } if (classNode instanceof Colon2Node) { className = AstUtilities.getFqn((Colon2Node) classNode); } // E.g. for "include Assertions" within Test::Unit::TestCase, try looking // for Test::Unit::TestCase::Assertions, Test::Unit:Assertions, Test::Assertions. // And for "include Util::Backtracefilter" try Test::Unit::Util::Backtracefilter etc. String possibleFqn = AstUtilities.getFqnName(path); // Try searching by qualified name by context first, if it's not qualified Set<IndexedClass> classes = Collections.emptySet(); String fqn = possibleFqn; // First try looking only at the local scope Set<String> uniqueClasses = new HashSet<String>(); while ((classes.size() == 0) && (fqn.length() > 0)) { classes = index.getClasses(fqn + "::" + className, QuerySupport.Kind.EXACT, // NOI18N true, false, false, uniqueClasses); int f = fqn.lastIndexOf("::"); // NOI18N if (f == -1) { break; } else { fqn = fqn.substring(0, f); } } if (classes.size() == 0) { classes = index.getClasses(className, QuerySupport.Kind.EXACT, true, false, false, uniqueClasses); } // If no success with looking only at the source scope, look in libraries as well if (classes.size() == 0) { fqn = possibleFqn; // Try looking at the libraries too while ((classes.size() == 0) && (fqn.length() > 0)) { classes = index.getClasses(fqn + "::" + className, QuerySupport.Kind.EXACT, true, false, // NOI18N false); int f = fqn.lastIndexOf("::"); if (f == -1) { break; } else { fqn = fqn.substring(0, f); } } if (classes.size() == 0) { classes = index.getClasses(className, QuerySupport.Kind.EXACT, true, false, false); } } return getElementDeclaration(classes, classNode); } Node findClass( final Node node, final String name, final boolean ignoreAlias) { if (node instanceof ClassNode) { String n = AstUtilities.getClassOrModuleName((ClassNode) node); if (n.equals(name)) { return node; } } else if (node instanceof ConstDeclNode) { if (((INameNode) node).getName().equals(name)) { return node; } } else if (!ignoreAlias && node instanceof AliasNode) { String newName = AstUtilities.getNameOrValue(((AliasNode)node).getNewName()); if (name.equals(newName)) { return node; } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } Node match = findClass(child, name, ignoreAlias); if (match != null) { return match; } } return null; } @Override // Now that I have a common RubyObject superclass, can I combine this and // findBestMethodMatchHelper since there's a lot of code duplication that // could be shared by just operating on RubyObjects ? IndexedClass findBestMatchHelper(Set<? extends IndexedClass> origClasses) { Set<? extends IndexedClass> classes = new HashSet<IndexedClass>(origClasses); // 1. First see if the reference is fully qualified. If so the job should // be easier: prune the result set down // If I have the fqn, I can also call RubyIndex.getRDocLocation to pick the // best candidate Set<IndexedClass> candidates = new HashSet<IndexedClass>(); if (classNode instanceof Colon2Node) { String fqn = AstUtilities.getFqn((Colon2Node) classNode); while ((fqn != null) && (fqn.length() > 0)) { for (IndexedClass clz : classes) { if (fqn.equals(clz.getSignature())) { candidates.add(clz); } } // TODO: Use the fqn to check if the class is documented: if so, prefer it // Check inherited methods; for example, if we've determined // that you're looking for Integer::foo, I should happily match // Numeric::foo. IndexedClass superClass = index.getSuperclass(fqn); if (superClass != null) { fqn = superClass.getSignature(); } else { break; } } } if (candidates.size() == 1) { return candidates.iterator().next(); } else if (!candidates.isEmpty()) { classes = candidates; } // 2. See if the reference is followed by a method call - if so, that may // help disambiguate which reference we're after. // TODO // 3. See which of the class references are defined in files directly // required by this file. Set<String> requires = null; if (path != null) { candidates = new HashSet<IndexedClass>(); requires = AstUtilities.getRequires(path.root()); for (IndexedClass clz : classes) { String require = clz.getRequire(); if (requires.contains(require)) { candidates.add(clz); } } if (candidates.size() == 1) { return candidates.iterator().next(); } else if (!candidates.isEmpty()) { classes = candidates; } } // 4. See if any of the classes are "kernel" classes (builtins) and for these // go to the known locations candidates = new HashSet<IndexedClass>(); for (IndexedClass clz : classes) { String url = clz.getFileUrl(); if (RubyUtils.isRubyStubsURL(url)) { candidates.add(clz); } } if (candidates.size() == 1) { return candidates.iterator().next(); } else if (!candidates.isEmpty()) { classes = candidates; } // 5. See which classes are documented, and prefer those over undocumented classes candidates = new HashSet<IndexedClass>(); int longestDocLength = 0; for (IndexedClass clz : classes) { int length = clz.getDocumentationLength(); if (length > longestDocLength) { candidates.clear(); candidates.add(clz); longestDocLength = length; } else if ((length > 0) && (length == longestDocLength)) { candidates.add(clz); } } if (candidates.size() == 1) { return candidates.iterator().next(); } else if (!candidates.isEmpty()) { classes = candidates; } // 6. Look at transitive closure of require statements and see which files // are most likely candidates if ((index != null) && (requires != null)) { candidates = new HashSet<IndexedClass>(); Set<String> allRequires = index.getRequiresTransitively(requires); for (IndexedClass clz : classes) { String require = clz.getRequire(); if (allRequires.contains(require)) { candidates.add(clz); } } if (candidates.size() == 1) { return candidates.iterator().next(); } else if (!candidates.isEmpty()) { classes = candidates; } } // 7. Other heuristics: Look at the method definition with the // most methods associated with it. Look at other uses of this // class in this parse tree, look at the methods and see if we // can rule out candidates based on that // // 7b. Give priority to class definitions that are local: obviously // there are class definitions in the same file, and then in the same project // // 8. Look at superclasses and consider -their- requires to figure out // which class we're supposed to use // TODO candidates = new HashSet<IndexedClass>(); // Pick one arbitrarily if (classes.size() > 0) { return classes.iterator().next(); } else { return null; } } }