/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-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]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2009 Sun
* Microsystems, Inc. All Rights Reserved.
*
* 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.
*/
package org.netbeans.modules.ruby;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.ruby.elements.IndexedField;
import org.netbeans.api.ruby.platform.RubyPlatform;
import org.netbeans.api.ruby.platform.RubyPlatformManager;
import org.netbeans.modules.csl.api.ElementKind;
import org.netbeans.modules.parsing.spi.Parser;
import org.netbeans.modules.parsing.spi.indexing.support.IndexResult;
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport;
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport.Kind;
import org.netbeans.modules.ruby.elements.IndexedClass;
import org.netbeans.modules.ruby.elements.IndexedConstant;
import org.netbeans.modules.ruby.elements.IndexedElement;
import org.netbeans.modules.ruby.elements.IndexedMethod;
import org.netbeans.modules.ruby.elements.IndexedVariable;
import org.netbeans.modules.ruby.platform.gems.GemManager;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.Exceptions;
import static org.netbeans.modules.ruby.RubyIndexer.*;
/**
* Access to the index of known Ruby classes - core, libraries, gems, user projects, etc.
*
* @todo Pull out attributes, fields and constants from the index as well
* @todo Store signature attributes for methods: private/protected?, documented?, returntype?
* @todo When there are multiple method/field definitions, pick access level from one which sets it
* @todo I do case-sensitive startsWith filtering here which is probably not good
* @todo Abort when search list .size() > N
*
* @author Tor Norbye
*/
public final class RubyIndex {
private static final Logger LOGGGER = Logger.getLogger(RubyIndex.class.getName());
public static final String UNKNOWN_CLASS = "<Unknown>"; // NOI18N
public static final String OBJECT = "Object"; // NOI18N
private static final String CLASS = "Class"; // NOI18N
private static final String MODULE = "Module"; // NOI18N
private static final String CLUSTER_URL = "cluster:"; // NOI18N
private static final String RUBYHOME_URL = "ruby:"; // NOI18N
private static final String GEM_URL = "gem:"; // NOI18N
private static String clusterUrl = null;
private static final RubyIndex EMPTY = new RubyIndex(null);
private FileObject context;
private final QuerySupport querySupport;
/**
* Caches the index to avoid querying roots (can be time consuming). Holds the index just for
* one FileObject at time.
*/
private static final Map<FileObject, RubyIndex> CACHE = new WeakHashMap<FileObject, RubyIndex>(1);
/**
* The base class for AR model classes, needs special handling in various
* places.
*/
static final String ACTIVE_RECORD_BASE = "ActiveRecord::Base"; //NOI18N
/** AR Relation. Provides dynamic query methods. */
static final String ACTIVE_RECORD_RELATION = "ActiveRecord::Relation"; //NOI18N
private RubyIndex(QuerySupport querySupport) {
this.querySupport = querySupport;
}
public static RubyIndex get(Collection<FileObject> roots) {
try {
return new RubyIndex(QuerySupport.forRoots(
RubyIndexer.Factory.NAME,
RubyIndexer.Factory.VERSION,
roots.toArray(new FileObject[roots.size()])));
} catch (IOException ioe) {
LOGGGER.log(Level.WARNING, null, ioe);
return EMPTY;
}
}
public static RubyIndex get(final Parser.Result result) {
return get(RubyUtils.getFileObject(result));
}
public static RubyIndex get(final FileObject fo) {
RubyIndex result = CACHE.get(fo);
if (result != null) {
return result;
}
result = fo == null ? null : get(QuerySupport.findRoots(fo,
Collections.singleton(RubyLanguage.SOURCE),
Collections.singleton(RubyLanguage.BOOT),
Collections.<String>emptySet()));
// cache the index just for one fo
CACHE.clear();
CACHE.put(fo, result);
return result;
}
public static void resetCache() {
CACHE.clear();
}
public Collection<? extends IndexResult> query(
final String fieldName, final String fieldValue,
final QuerySupport.Kind kind, final String... fieldsToLoad) {
if (querySupport != null) {
try {
return querySupport.query(fieldName, fieldValue, kind, fieldsToLoad);
} catch (IOException ioe) {
LOGGGER.log(Level.WARNING, null, ioe);
}
}
return Collections.<IndexResult>emptySet();
}
private boolean search(String key, String name, QuerySupport.Kind kind, Collection<IndexResult> result, String... fieldsToLoad) {
try {
result.addAll(querySupport.query(key, name, kind, fieldsToLoad));
return true;
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
return false;
}
}
FileObject getContext() {
return context;
}
Set<IndexedClass> getClasses(String name, final QuerySupport.Kind kind, boolean includeAll,
boolean skipClasses, boolean skipModules) {
return getClasses(name, kind, includeAll, skipClasses, skipModules, null);
}
/**
* Return the full set of classes that match the given name.
*
* @param name The name of the class - possibly a fqn like File::Stat, or just a class
* name like Stat, or just a prefix like St.
* @param kind Whether we want the exact name, or whether we're searching by a prefix.
* @param includeAll If true, return multiple IndexedClasses for the same logical
* class, one for each declaration point. For example, File is defined both in the
* builtin stubs as well as in ftools.
*/
public Set<IndexedClass> getClasses(String name, final QuerySupport.Kind kind, boolean includeAll,
boolean skipClasses, boolean skipModules, Set<String> uniqueClasses) {
String classFqn = null;
if (name != null) {
if (name.indexOf("::") != -1) { // NOI18N
int p = name.lastIndexOf("::"); // NOI18N
classFqn = name.substring(0, p);
name = name.substring(p + 2);
} else if (name.endsWith(":")) {
// User has typed something like "Test:" and wants completion on
// for something like Test::Unit
classFqn = name.substring(0, name.length() - 1);
name = "";
}
}
final Set<IndexResult> result = new HashSet<IndexResult>();
String field;
switch (kind) {
case EXACT:
case PREFIX:
case CAMEL_CASE:
case REGEXP:
field = FIELD_CLASS_NAME;
break;
case CASE_INSENSITIVE_PREFIX:
case CASE_INSENSITIVE_REGEXP:
field = FIELD_CASE_INSENSITIVE_CLASS_NAME;
break;
default:
throw new UnsupportedOperationException(kind.toString());
}
search(field, name, kind, result, CLASS_FIELDS);
// TODO Prune methods to fit my scheme - later make lucene index smarter about how to prune its index search
if (includeAll) {
uniqueClasses = null;
} else if (uniqueClasses == null) {
uniqueClasses = new HashSet<String>();
}
final Set<IndexedClass> classes = new HashSet<IndexedClass>();
for (IndexResult map : result) {
String clz = map.getValue(FIELD_CLASS_NAME);
if (clz == null) {
// It's probably a module
// XXX I need to handle this... for now punt
continue;
}
// Lucene returns some inexact matches, TODO investigate why this is necessary
if ((kind == QuerySupport.Kind.PREFIX) && !clz.startsWith(name)) {
continue;
} else if (kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX && !clz.regionMatches(true, 0, name, 0, name.length())) {
continue;
}
if (classFqn != null) {
if (kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX ||
kind == QuerySupport.Kind.CASE_INSENSITIVE_REGEXP) {
if (!classFqn.equalsIgnoreCase(map.getValue(FIELD_IN))) {
continue;
}
} else if (kind == QuerySupport.Kind.CAMEL_CASE) {
String in = map.getValue(FIELD_IN);
if (in != null) {
// Superslow, make faster
StringBuilder sb = new StringBuilder();
// String prefix = null;
int lastIndex = 0;
int idx;
do {
int nextUpper = -1;
for( int i = lastIndex+1; i < classFqn.length(); i++ ) {
if ( Character.isUpperCase(classFqn.charAt(i)) ) {
nextUpper = i;
break;
}
}
idx = nextUpper;
String token = classFqn.substring(lastIndex, idx == -1 ? classFqn.length(): idx);
// if ( lastIndex == 0 ) {
// prefix = token;
// }
sb.append(token);
// TODO - add in Ruby chars here?
sb.append( idx != -1 ? "[\\p{javaLowerCase}\\p{Digit}_\\$]*" : ".*"); // NOI18N
lastIndex = idx;
}
while(idx != -1);
final Pattern pattern = Pattern.compile(sb.toString());
if (!pattern.matcher(in).matches()) {
continue;
}
} else {
continue;
}
} else {
if (!classFqn.equals(map.getValue(FIELD_IN))) {
continue;
}
}
}
String attrs = map.getValue(FIELD_CLASS_ATTRS);
boolean isClass = true;
if (attrs != null) {
int flags = IndexedElement.stringToFlag(attrs, 0);
isClass = (flags & IndexedClass.MODULE) == 0;
}
if (skipClasses && isClass) {
continue;
}
if (skipModules && !isClass) {
continue;
}
String fqn = map.getValue(FIELD_FQN_NAME);
// Only return a single instance for this signature
if (!includeAll) {
if (uniqueClasses.contains(fqn)) { // use a map to point right to the class
// Prefer the instance that provides documentation
boolean replaced = false;
int flags = 0;
if (attrs != null) {
flags = IndexedElement.stringToFlag(attrs, 0);
}
boolean isDocumented = (flags & IndexedElement.DOCUMENTED) != 0;
if (isDocumented) {
// Check the actual size of the documentation, and prefer the largest
// method
int length = 0;
int documentedAt = attrs.indexOf(';');
if (documentedAt != -1) {
int end = attrs.indexOf(';', documentedAt+1);
if (end == -1) {
end = attrs.length();
}
length = Integer.parseInt(attrs.substring(documentedAt + 1, end));
}
// This instance is documented. Replace the other instance...
for (IndexedClass c : classes) {
if (c.getSignature().equals(fqn) &&
(length > c.getDocumentationLength())) {
classes.remove(c);
replaced = true;
break;
}
}
}
if (!replaced) {
continue;
}
} else {
uniqueClasses.add(fqn);
}
}
classes.add(createClass(fqn, clz, map));
}
return classes;
}
/**
* Return the set of classes that directly subclass the given class
*
* @param name The name of the class - possibly a fqn like File::Stat, or just a class
* name like Stat, or just a prefix like St.
* @param kind Whether we want the exact name, or whether we're searching by a prefix.
* @param includeAll If true, return multiple IndexedClasses for the same logical
* class, one for each declaration point. For example, File is defined both in the
* builtin stubs as well as in ftools.
*/
public Set<IndexedClass> getSubClasses(String name, String fqn, final QuerySupport.Kind kind) {
Collection<? extends IndexResult> result =
query(FIELD_EXTENDS_NAME, fqn, QuerySupport.Kind.EXACT, FIELD_EXTENDS_NAME);
final Set<IndexedClass> classes = new HashSet<IndexedClass>();
for (IndexResult ir : result) {
String clz = ir.getValue(FIELD_CLASS_NAME);
if (clz == null) {
// It's probably a module
// XXX I need to handle this... for now punt
continue;
}
// Lucene returns some inexact matches, TODO investigate why this is necessary
if ((kind == QuerySupport.Kind.PREFIX) && !clz.startsWith(name)) {
continue;
} else if (kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX && !clz.regionMatches(true, 0, name, 0, name.length())) {
continue;
}
String cfqn = ir.getValue(FIELD_FQN_NAME);
// Only return a single instance for this signature
classes.add(createClass(cfqn, clz, ir));
}
return classes;
}
public Set<IndexedMethod> getMethods(final String name, final String clz, QuerySupport.Kind kind) {
if (clz == null) {
return getMethods(name, (Collection<String>) null, kind);
}
return getMethods(name, Collections.singleton(clz), kind);
}
Set<IndexedMethod> getMethods(String name, QuerySupport.Kind kind) {
return getMethods(name, (String) null, kind);
}
/**
* Return a set of methods that match the given name prefix, and are in the given
* class and module. If no class is specified, match methods across all classes.
* Note that inherited methods are not checked. If you want to match inherited methods
* you must call this method on each superclass as well as the mixin modules.
*/
@SuppressWarnings("fallthrough")
public Set<IndexedMethod> getMethods(final String name, final Collection<String> classes, QuerySupport.Kind kind) {
boolean inherited = classes == null;
// public void searchByCriteria(final String name, final ClassIndex.QuerySupport.Kind kind, /*final ResultConvertor<T> convertor,*/ final Set<String> file) throws IOException {
final Set<IndexResult> result = new HashSet<IndexResult>();
QuerySupport.Kind originalKind = kind;
if (kind == QuerySupport.Kind.EXACT) {
// I can't do exact searches on methods because the method
// entries include signatures etc. So turn this into a prefix
// search and then compare chopped off signatures with the name
kind = QuerySupport.Kind.PREFIX;
}
// No point in doing case insensitive searches on method names because
// method names in Ruby are always case insensitive anyway
// case CASE_INSENSITIVE_PREFIX:
// case CASE_INSENSITIVE_REGEXP:
// field = FIELD_CASE_INSENSITIVE_METHOD_NAME;
// break;
search(FIELD_METHOD_NAME, name, kind, result);
// include also for attr methods that create accessors (e.g attr, attr_accessor)
search(FIELD_ATTRIBUTE_NAME, name, kind, result);
//return Collections.unmodifiableSet(file);
// TODO Prune methods to fit my scheme - later make lucene index smarter about how to prune its index search
final Set<IndexedMethod> methods = new HashSet<IndexedMethod>();
for (IndexResult map : result) {
if (classes != null) {
String fqn = map.getValue(FIELD_FQN_NAME);
if (!classes.contains(fqn)) {
continue;
}
}
String[] signatures = map.getValues(FIELD_METHOD_NAME);
if (signatures != null) {
for (String signature : signatures) {
// Skip weird methods... Think harder about this
if (((name == null) || (name.length() == 0)) &&
!Character.isLowerCase(signature.charAt(0))) {
continue;
}
// Lucene returns some inexact matches, TODO investigate why this is necessary
if ((kind == QuerySupport.Kind.PREFIX) && !signature.startsWith(name)) {
continue;
} else if (kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX && !signature.regionMatches(true, 0, name, 0, name.length())) {
continue;
} else if (kind == QuerySupport.Kind.CASE_INSENSITIVE_REGEXP) {
int len = signature.length();
int end = signature.indexOf('(');
if (end == -1) {
end = signature.indexOf(';');
if (end == -1) {
end = len;
}
}
String n = end != len ? signature.substring(0, end) : signature;
try {
if (!n.matches(name)) {
continue;
}
} catch (Exception e) {
// Silently ignore regexp failures in the search expression
}
} else if (originalKind == QuerySupport.Kind.EXACT) {
// Make sure the name matches exactly
// We know that the prefix is correct from the first part of
// this if clause, by the signature may have more
if (((signature.length() > name.length()) &&
(signature.charAt(name.length()) != '(')) &&
(signature.charAt(name.length()) != ';')) {
continue;
}
}
// XXX THIS DOES NOT WORK WHEN THERE ARE IDENTICAL SIGNATURES!!!
assert map != null;
methods.add(createMethod(signature, map, inherited));
}
}
String[] attributes = map.getValues(FIELD_ATTRIBUTE_NAME);
if (attributes != null) {
for (String signature : attributes) {
// Skip weird methods... Think harder about this
if (((name == null) || (name.length() == 0)) &&
!Character.isLowerCase(signature.charAt(0))) {
continue;
}
// Lucene returns some inexact matches, TODO investigate why this is necessary
if (kind == QuerySupport.Kind.PREFIX && !signature.startsWith(name)) {
continue;
} else if (kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX && !signature.regionMatches(true, 0, name, 0, name.length())) {
continue;
} else if (kind == QuerySupport.Kind.CASE_INSENSITIVE_REGEXP && !signature.matches(name)) {
continue;
} else if (originalKind == QuerySupport.Kind.EXACT) {
// Make sure the name matches exactly
// We know that the prefix is correct from the first part of
// this if clause, by the signature may have more
if (((signature.length() > name.length()) &&
//(signature.charAt(name.length()) != '(')) &&
(signature.charAt(name.length()) != ';'))) {
continue;
}
}
// XXX THIS DOES NOT WORK WHEN THERE ARE IDENTICAL SIGNATURES!!!
assert map != null;
// Create method for the attribute
methods.add(createMethod(signature, map, inherited));
}
}
// TODO - fields
}
return methods;
}
public IndexedMethod createMethod(String signature, IndexResult ir, boolean inherited) {
String clz = ir.getValue(FIELD_CLASS_NAME);
String module = ir.getValue(FIELD_IN);
if (clz == null) {
// Module method?
clz = module;
} else if ((module != null) && (module.length() > 0)) {
clz = module + "::" + clz;
}
String fqn = ir.getValue(FIELD_FQN_NAME);
String require = ir.getValue(FIELD_REQUIRE);
// Extract attributes
int attributeIndex = signature.indexOf(';');
String attributes = null;
int flags = 0;
if (attributeIndex != -1) {
flags = IndexedElement.stringToFlag(signature, attributeIndex+1);
if (signature.length() > attributeIndex+1) {
attributes = signature.substring(attributeIndex+1, signature.length());
}
signature = signature.substring(0, attributeIndex);
}
IndexedMethod m = IndexedMethod.create(this, signature, fqn, clz,
ir.getFile(), require, attributes, flags, context);
m.setInherited(inherited);
return m;
}
public IndexedField createField(String signature, IndexResult ir, boolean isInstance, boolean inherited) {
String clz = ir.getValue(FIELD_CLASS_NAME);
String module = ir.getValue(FIELD_IN);
if (clz == null) {
// Module method?
clz = module;
} else if ((module != null) && (module.length() > 0)) {
clz = module + "::" + clz;
}
String fqn = ir.getValue(FIELD_FQN_NAME);
String require = ir.getValue(FIELD_REQUIRE);
int attributeIndex = signature.indexOf(';');
String attributes = null;
int flags = 0;
if (attributeIndex != -1) {
flags = IndexedElement.stringToFlag(signature, attributeIndex+1);
if (signature.length() > attributeIndex+1) {
attributes = signature.substring(attributeIndex+1, signature.length());
}
signature = signature.substring(0, attributeIndex);
}
IndexedField m = IndexedField.create(
this, signature, fqn, clz, ir, require, attributes, flags, context);
m.setInherited(inherited);
return m;
}
private static boolean isEmptyOrNull(String str) {
return str == null || "".equals(str.trim());
}
public IndexedConstant createConstant(String signature, IndexResult ir) {
String classFQN = ir.getValue(FIELD_FQN_NAME);
String require = ir.getValue(FIELD_REQUIRE);
int typeIndex = signature.indexOf(';');
String name = typeIndex == -1 ? signature : signature.substring(0, typeIndex);
int flags = 0;
// TODO parse possibly multiple types
String type = typeIndex == -1 ? null : signature.substring(typeIndex + 1);
RubyType rubyType = isEmptyOrNull(type) ? RubyType.unknown() : RubyType.create(type);
IndexedConstant m = IndexedConstant.create(
this, name, classFQN, ir, require, flags, context, rubyType);
return m;
}
public IndexedClass createClass(String fqn, String clz, IndexResult ir) {
String require = ir.getValue(FIELD_REQUIRE);
if (clz == null) {
clz = ir.getValue(FIELD_CLASS_NAME);
}
String attrs = ir.getValue(FIELD_CLASS_ATTRS);
int flags = 0;
if (attrs != null) {
flags = IndexedElement.stringToFlag(attrs, 0);
}
IndexedClass c =
IndexedClass.create(this, clz, fqn, ir, require, attrs, flags, context);
return c;
}
// List of String[2]: 0: requirename, 1: fqn
public Set<String[]> getRequires(final String name, final QuerySupport.Kind kind) {
final Set<IndexResult> result = new HashSet<IndexResult>();
String field = FIELD_REQUIRE;
search(field, name, kind, result, FIELD_REQUIRE, FIELD_FQN_NAME);
// TODO Prune methods to fit my scheme - later make lucene index smarter about how to prune its index search
final Map<String, String> fqns = new HashMap<String, String>();
for (IndexResult map : result) {
String[] r = map.getValues(field);
if (r != null) {
for (String require : r) {
// Lucene returns some inexact matches, TODO investigate why this is necessary
if (kind == QuerySupport.Kind.PREFIX && !require.startsWith(name)) {
continue;
} else if (kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX && !require.regionMatches(true, 0, name, 0, name.length())) {
continue;
}
assert map != null;
// TODO - check if there's a rubygem which captures this
// require and if so, remove it
String fqn = map.getValue(FIELD_FQN_NAME);
String there = fqns.get(require);
if ((fqn != null) &&
((there == null) ||
((there != null) && (there.length() < fqn.length())))) {
fqns.put(require, fqn);
}
}
}
}
final Set<String[]> requires = new HashSet<String[]>();
for (String require : fqns.keySet()) {
String fqn = fqns.get(require);
String[] item = new String[2];
item[0] = require;
item[1] = fqn;
requires.add(item);
}
return requires;
}
public Set<String> getRequiresTransitively(Set<String> requires) {
// Not yet implemented - this requires me to index the require-statements in the files
return requires;
}
// List of String[2]: 0: requirename, 1: fqn
public Set<String> getClassesIn(final String require) {
final Set<IndexResult> result = new HashSet<IndexResult>();
String field = FIELD_REQUIRE;
search(field, require, QuerySupport.Kind.EXACT, result, FIELD_REQUIRE, FIELD_FQN_NAME);
final Set<String> fqns = new HashSet<String>();
for (IndexResult map : result) {
String fqn = map.getValue(FIELD_FQN_NAME);
if (fqn != null) {
fqns.add(fqn);
}
}
return fqns;
}
/**
* Gets the super clases of the given class; the class itself
* is not included.
* @param fqn
* @return an ordered list of the super classes; closest first.
*/
public List<IndexedClass> getSuperClasses(String fqn) {
// todo: performance?
List<IndexedClass> superClasses = new ArrayList<IndexedClass>();
IndexedClass superClass = getSuperclass(fqn);
while (superClass != null) {
superClasses.add(superClass);
superClass = getSuperclass(superClass.getName());
}
return superClasses;
}
public IndexedClass getSuperclass(String fqn) {
final Set<IndexResult> result = new HashSet<IndexResult>();
QuerySupport.Kind kind = QuerySupport.Kind.EXACT;
String field = FIELD_FQN_NAME;
search(field, fqn, kind, result, CLASS_FIELDS);
// XXX Uhm... there could be multiple... Shouldn't I return a set here?
// (e.g. you can have your own class named File which has nothing to
// do with the builtin, and has a separate super class...
for (IndexResult map : result) {
assert fqn.equals(map.getValue(FIELD_FQN_NAME));
String extendsClass = map.getValue(FIELD_EXTENDS_NAME);
if (extendsClass != null) {
// Found the class name, now look it up in the index
result.clear();
if (!search(field, extendsClass, kind, result, CLASS_FIELDS)) {
return null;
}
// There should be exactly one match
if (result.size() > 0) {
IndexResult superMap = result.iterator().next();
String superFqn = superMap.getValue(FIELD_FQN_NAME);
return createClass(superFqn, extendsClass, superMap);
} else {
return null;
}
}
}
return null;
}
private boolean addSubclasses(String classFqn,
Set<IndexedClass> classes, Set<String> seenClasses, Set<String> scannedClasses, boolean directOnly) {
// Prevent problems with circular includes or redundant includes
if (scannedClasses.contains(classFqn)) {
return false;
}
scannedClasses.add(classFqn);
String searchField = FIELD_EXTENDS_NAME;
Set<IndexResult> result = new HashSet<IndexResult>();
search(searchField, classFqn, QuerySupport.Kind.EXACT, result, CLASS_FIELDS);
boolean foundIt = result.size() > 0;
// If this is a bogus class entry (no search rsults) don't continue
if (!foundIt) {
return foundIt;
}
for (IndexResult map : result) {
String fqn = map.getValue(FIELD_FQN_NAME);
if (!seenClasses.contains(fqn)) {
IndexedClass clz = createClass(fqn, null, map);
classes.add(clz);
seenClasses.add(fqn);
if (!directOnly) {
addSubclasses(fqn, classes, seenClasses, scannedClasses, directOnly);
}
}
}
return foundIt;
}
/** Find the subclasses of the given class name, with the POSSIBLE fqn from the
* context of the usage. */
public Set<IndexedClass> getSubClasses(String fqn, String possibleFqn, String name, boolean directOnly) {
//String field = FIELD_FQN_NAME;
Set<IndexedClass> classes = new LinkedHashSet<IndexedClass>();
Set<String> scannedClasses = new HashSet<String>();
Set<String> seenClasses = new HashSet<String>();
if (fqn != null) {
addSubclasses(fqn, classes, seenClasses, scannedClasses, directOnly);
} else {
fqn = possibleFqn;
// Try looking at the libraries too
while ((classes.size() == 0) && (fqn.length() > 0)) {
// TODO - use the boolvalue from addclasses instead!
boolean found = addSubclasses(fqn + "::" + name, classes, seenClasses, scannedClasses, directOnly);
if (found) {
return classes;
}
int f = fqn.lastIndexOf("::");
if (f == -1) {
break;
} else {
fqn = fqn.substring(0, f);
}
}
if (classes.size() == 0) {
addSubclasses(name, classes, seenClasses, scannedClasses, directOnly);
}
}
return classes;
}
/**
* Gets either the most distant or the closest method in the hierarchy that the given method overrides or
* the method itself if it doesn't override any super methods.
*
* @param className the name of class where the given <code>methodName</code> is.
* @param methodName the name of the method.
* @param closest if true, gets the closest super method, otherwise the most distant.
*
* @return method or <code>null</code> if the was no such method.
*/
public IndexedMethod getSuperMethod(String className, String methodName, boolean closest) {
return getSuperMethod(className, methodName, closest, true);
}
/**
* Gets either the most distant or the closest method in the hierarchy that the given method overrides or
* the method itself if it doesn't override any super methods.
*
* @param className the name of class where the given <code>methodName</code> is.
* @param methodName the name of the method.
* @param closest if true, gets the closest super method, otherwise the most distant.
* @param includeSelf if true, returns the method itself if it had no super methods.
*
* @return method or <code>null</code> if the was no such method.
*/
IndexedMethod getSuperMethod(String className, String methodName, boolean closest, boolean includeSelf) {
Set<IndexedMethod> methods = getInheritedMethods(className, methodName, QuerySupport.Kind.EXACT);
// todo: performance?
List<IndexedClass> superClasses = getSuperClasses(className);
if (!closest) {
Collections.reverse(superClasses);
}
for (IndexedClass superClass : superClasses) {
for (IndexedMethod method : methods) {
// getInheritedMethods may return methods ON fqn itself
String clz = method.getIn();
if (superClass.getName().equals(clz) && !clz.equals(className)) {
return method;
}
}
}
if (!includeSelf) {
return null;
}
return !methods.isEmpty() ? methods.iterator().next() : null;
}
/**
* Gets all the methods that override the given method.
*
* @param methodName the name of the method.
* @param className the (fqn) class name where the method is defined.
* @return a set containing the overriding methods.
*/
public Set<IndexedMethod> getOverridingMethods(String methodName, String className) {
return getOverridingMethods(methodName, className, false);
}
/**
* Gets all the methods that override the given method.
*
* @param methodName the name of the method.
* @param className the (fqn) class name where the method is defined.
* @param excludeSelf if true, excludes the overridden method itself from the results.
*
* @return a set containing the overriding methods.
*/
Set<IndexedMethod> getOverridingMethods(String methodName, String className, boolean excludeSelf) {
Set<IndexedMethod> result = new HashSet<IndexedMethod>();
Set<IndexedMethod> methods = getMethods(methodName, className, QuerySupport.Kind.EXACT);
for (IndexedMethod method : methods) {
Set<IndexedClass> subClasses = getSubClasses(method.getIn(), null, null, false);
Set<String> subClassNames = new HashSet<String>(subClasses.size());
for (IndexedClass subClass : subClasses) {
if (excludeSelf && className.equals(subClass.getIn())) {
continue;
}
subClassNames.add(subClass.getName());
}
if (!subClassNames.isEmpty()) {
result.addAll(getMethods(method.getName(), subClassNames, QuerySupport.Kind.EXACT));
}
}
return result;
}
/**
* Gets all the methods from the given class' hiearchy that override the
* given method.
*
* @param methodName
* @param className
* @return
*/
public Set<IndexedMethod> getAllOverridingMethodsInHierachy(String methodName, String className) {
IndexedMethod superMethod = getSuperMethod(className, methodName, false);
if (superMethod == null) {
return Collections.emptySet();
}
Set<IndexedMethod> result = new HashSet<IndexedMethod>();
result.add(superMethod);
result.addAll(getOverridingMethods(superMethod.getName(), superMethod.getIn()));
return result;
}
Set<IndexedMethod> getInheritedMethods(RubyType receiverType, String prefix, QuerySupport.Kind kind) {
Set<IndexedMethod> methods = new HashSet<IndexedMethod>();
for (String realType : receiverType.getRealTypes()) {
methods.addAll(getInheritedMethods(realType, prefix, kind));
}
return methods;
}
/**
* Get the set of inherited (through super classes and mixins) for the given fully qualified class name.
* @param classFqn FQN: module1::module2::moduleN::class
* @param prefix If kind is QuerySupport.Kind.PREFIX/CASE_INSENSITIVE_PREFIX, a prefix to filter methods by. Else,
* if kind is QuerySupport.Kind.EXACT filter methods by the exact name.
* @param kind Whether the prefix field should be taken as a prefix or a whole name
*/
public Set<IndexedMethod> getInheritedMethods(String classFqn, String prefix, QuerySupport.Kind kind) {
return getInheritedMethods(classFqn, prefix, kind, false);
}
private Set<IndexedMethod> getInheritedMethods(String classFqn, String prefix, QuerySupport.Kind kind, boolean includeDynamicMethods) {
boolean haveRedirected = false;
if ((classFqn == null) || classFqn.equals(OBJECT)) {
// Redirect inheritance tree to Class to pick up methods in Class and Module
classFqn = CLASS;
haveRedirected = true;
} else if (MODULE.equals(classFqn) || CLASS.equals(classFqn)) {
haveRedirected = true;
}
//String field = FIELD_FQN_NAME;
Set<IndexedMethod> methods = new HashSet<IndexedMethod>();
Set<String> scannedClasses = new HashSet<String>();
Set<String> seenSignatures = new HashSet<String>();
if (prefix == null) {
prefix = "";
}
addMethodsFromClass(prefix, kind, classFqn, methods, seenSignatures, scannedClasses,
haveRedirected, false, includeDynamicMethods);
return methods;
}
/**
* Like {@link #getInheritedMethods(java.lang.String, java.lang.String, org.netbeans.modules.parsing.spi.indexing.support.QuerySupport.Kind) },
* ut if {@code includeSelf} is false, excludes results from the class itself.
* @param classFqn
* @param prefix
* @param kind
* @param includeSelf specifies whether methods from {@code classFqn} should be included.
* @param includeDynamic specifies whether dynamic methods (e.g. rails finder and query methods) should
* be included.
* @return
*/
Set<IndexedMethod> getInheritedMethods(String classFqn, String prefix, QuerySupport.Kind kind, boolean includeSelf, boolean includeDynamic) {
Set<IndexedMethod> inherited = getInheritedMethods(classFqn, prefix, kind, includeDynamic);
if (includeSelf) {
return inherited;
}
Set<IndexedMethod> result = new HashSet<IndexedMethod>(inherited.size());
for (IndexedMethod each : inherited) {
if (!classFqn.equals(each.getClz())) {
result.add(each);
}
}
return result;
}
/** Return whether the specific class referenced (classFqn) was found or not. This is
* not the same as returning whether any classes were added since it may add
* additional methods from parents (Object/Class).
*/
private boolean addMethodsFromClass(String prefix, QuerySupport.Kind kind, String classFqn,
Set<IndexedMethod> methods, Set<String> seenSignatures, Set<String> scannedClasses,
boolean haveRedirected, boolean inheriting, boolean includeDynamic) {
// Prevent problems with circular includes or redundant includes
if (scannedClasses.contains(classFqn)) {
return false;
}
scannedClasses.add(classFqn);
Set<IndexResult> result = new LinkedHashSet<IndexResult>();
search(FIELD_FQN_NAME, classFqn, QuerySupport.Kind.EXACT, result);
boolean foundIt = result.size() > 0;
// If this is a bogus class entry (no search rsults) don't continue
if (!foundIt) {
return foundIt;
}
String extendsClass = null;
String classIn = null;
int fqnIndex = classFqn.lastIndexOf("::"); // NOI18N
if (fqnIndex != -1) {
classIn = classFqn.substring(0, fqnIndex);
}
for (IndexResult map : result) {
assert map != null;
if (extendsClass == null) {
extendsClass = map.getValue(FIELD_EXTENDS_NAME);
}
String includes = map.getValue(FIELD_INCLUDES);
if (includes != null) {
String[] in = includes.split(",");
// I have Util::BacktraceFilter and Assertions, which are both
// relative to ::,Test,Test::Unit
for (String include : in) {
// Try both with and without a package qualifier
boolean isQualified = false;
if (classIn != null) {
isQualified = addMethodsFromClass(prefix, kind, classIn + "::" + include,
methods, seenSignatures, scannedClasses, haveRedirected, true, includeDynamic);
}
if (!isQualified) {
addMethodsFromClass(prefix, kind, include, methods, seenSignatures,
scannedClasses, haveRedirected, true, includeDynamic);
}
}
}
String extendWith = map.getValue(FIELD_EXTEND_WITH);
if (extendWith != null) {
// Try both with and without a package qualifier
boolean isQualified = false;
Set<IndexedMethod> extendWithMethods = new HashSet<IndexedMethod>();
if (classIn != null) {
isQualified = addMethodsFromClass(prefix, kind, classIn + "::" + extendWith,
extendWithMethods, seenSignatures, scannedClasses, haveRedirected, true, includeDynamic);
}
if (!isQualified) {
addMethodsFromClass(prefix, kind, extendWith, extendWithMethods, seenSignatures,
scannedClasses, haveRedirected, true, includeDynamic);
}
// we need to explicitly set methods added via "extends with" as static
// (we don't track methods added via extend to instances)
for (IndexedMethod each : extendWithMethods) {
each.setStatic(true);
}
methods.addAll(extendWithMethods);
}
String[] signatures = map.getValues(FIELD_METHOD_NAME);
String className = map.getValue(FIELD_CLASS_NAME);
if (className == null) {
className = "";
}
if (signatures != null) {
for (String signature : signatures) {
// Skip weird methods like "[]" etc. in completion lists... TODO Think harder about this
if ((prefix.length() == 0) && !Character.isLowerCase(signature.charAt(0))) {
continue;
}
String seenSignature = signature + ";" + className;
// Prevent duplicates when method is redefined
if (!seenSignatures.contains(seenSignature)) {
if (signature.startsWith(prefix)) {
if (kind == QuerySupport.Kind.EXACT) {
// Ensure that the method is not longer than the prefix
if ((signature.length() > prefix.length()) &&
(signature.charAt(prefix.length()) != '(') &&
(signature.charAt(prefix.length()) != ';')) {
continue;
}
} else {
// REGEXP, CAMELCASE filtering etc. not supported here
assert (kind == QuerySupport.Kind.PREFIX) ||
(kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX);
}
seenSignatures.add(seenSignature);
IndexedMethod method = createMethod(signature, map, inheriting);
method.setSmart(!haveRedirected);
methods.add(method);
}
}
}
}
String[] attributes = map.getValues(FIELD_ATTRIBUTE_NAME);
if (attributes != null) {
for (String attribute : attributes) {
// Skip weird methods like "[]" etc. in completion lists... TODO Think harder about this
if ((prefix.length() == 0) && !Character.isLowerCase(attribute.charAt(0))) {
continue;
}
// Prevent duplicates when method is redefined
if (!seenSignatures.contains(attribute)) {
if (attribute.startsWith(prefix)) {
if (kind == QuerySupport.Kind.EXACT) {
// Ensure that the method is not longer than the prefix
if ((attribute.length() > prefix.length()) &&
(attribute.charAt(prefix.length()) != '(') &&
(attribute.charAt(prefix.length()) != ';')) {
continue;
}
} else {
// REGEXP, CAMELCASE filtering etc. not supported here
assert (kind == QuerySupport.Kind.PREFIX) ||
(kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX);
}
seenSignatures.add(attribute);
// TODO - create both getter and setter methods
IndexedMethod method = createMethod(attribute, map, inheriting);
method.setSmart(!haveRedirected);
method.setMethodType(IndexedMethod.MethodType.ATTRIBUTE);
methods.add(method);
}
}
}
}
}
if (classFqn.equals(OBJECT)) {
return foundIt;
}
// need to add query methods so that they can be chained
if (ACTIVE_RECORD_RELATION.equals(classFqn)) {
addQueryMethods(prefix, kind, classFqn, methods);
}
if (extendsClass == null) {
if (haveRedirected) {
addMethodsFromClass(prefix, kind, OBJECT, methods, seenSignatures, scannedClasses,
true, true, includeDynamic);
} else {
// Rather than inheriting directly from object,
// let's go via Class (and Module) up to Object
addMethodsFromClass(prefix, kind, CLASS, methods, seenSignatures, scannedClasses,
true, true, includeDynamic);
}
} else {
if (ACTIVE_RECORD_BASE.equals(extendsClass)) { // NOI18N
// Add in database fields as well
addDatabaseProperties(prefix, kind, classFqn, methods, includeDynamic);
// add in query methods if this appears to be in a rails 3 project (if AR::Relation is
// indexed we make the assumption that this is a rails 3 project)
if (includeDynamic && scannedClasses.contains(ACTIVE_RECORD_RELATION)
|| !getClasses(ACTIVE_RECORD_RELATION, Kind.EXACT, false, false, true).isEmpty()) {
addQueryMethods(prefix, kind, classFqn, methods);
}
}
// We're not sure we have a fully qualified path, so try some different candidates
if (!addMethodsFromClass(prefix, kind, extendsClass, methods, seenSignatures,
scannedClasses, haveRedirected, true, includeDynamic)) {
// Search by classIn
String fqn = classIn;
while (fqn != null) {
if (addMethodsFromClass(prefix, kind, fqn + "::" + extendsClass, methods,
seenSignatures, scannedClasses, haveRedirected, true, includeDynamic)) {
break;
}
int f = fqn.lastIndexOf("::"); // NOI18N
if (f == -1) {
break;
} else {
fqn = fqn.substring(0, f);
}
}
}
}
return foundIt;
}
private void addDatabaseProperties(String prefix, QuerySupport.Kind kind, String classFqn,
Set<IndexedMethod> methods, boolean includeDynamic) {
DatabasePropertiesIndexer.indexDatabaseProperties(this, prefix, kind, classFqn, methods, includeDynamic);
}
private void addQueryMethods(String prefix, QuerySupport.Kind kind, String classFqn, Set<IndexedMethod> methods) {
ActiveRecordQueryIndexer.indexQueryMehods(this, prefix, kind, classFqn, methods);
}
public Set<String> getDatabaseTables(String prefix, QuerySupport.Kind kind) {
// Query index for database related properties
String searchField = FIELD_DB_TABLE;
Set<IndexResult> result = new HashSet<IndexResult>();
search(searchField, prefix, kind, result, FIELD_DB_TABLE);
Set<String> tables = new HashSet<String>();
for (IndexResult map : result) {
assert map != null;
String tableName = map.getValue(FIELD_DB_TABLE);
if (tableName != null) {
tables.add(tableName);
}
}
return tables;
}
public Set<IndexedVariable> getGlobals(String prefix, QuerySupport.Kind kind) {
// Query index for database related properties
String searchField = FIELD_GLOBAL_NAME;
Set<IndexResult> result = new HashSet<IndexResult>();
// Only include globals from the user's sources, not in the libraries!
search(searchField, prefix, kind, result, FIELD_GLOBAL_NAME);
Set<IndexedVariable> globals = new HashSet<IndexedVariable>();
for (IndexResult ir : result) {
assert ir != null;
String[] names = ir.getValues(FIELD_GLOBAL_NAME);
if (names != null) {
for (String name : names) {
int flags = 0;
IndexedVariable var = IndexedVariable.create(this, name, name, null, ir, null, name, flags, ElementKind.GLOBAL, context);
globals.add(var);
}
}
}
return globals;
}
public Set<? extends IndexedConstant> getConstants(final String constantFqn) {
String[] parts = RubyUtils.parseConstantName(constantFqn);
return getConstants(parts[0], parts[1]);
}
public Set<? extends IndexedConstant> getConstants(RubyType classFqn, String prefix) {
Set<IndexedConstant> constants = new HashSet<IndexedConstant>();
for (String realType : classFqn.getRealTypes()) {
constants.addAll(getConstants(realType, prefix));
for (String parentModule : RubyUtils.getParentModules(realType)) {
constants.addAll(getConstants(parentModule, prefix));
}
}
return constants;
}
public Set<? extends IndexedConstant> getConstants(String classFqn, String prefix) {
boolean haveRedirected = false;
if ((classFqn == null) || classFqn.equals(OBJECT)) {
// Redirect inheritance tree to Class to pick up methods in Class and Module
classFqn = CLASS;
haveRedirected = true;
} else if (MODULE.equals(classFqn) || CLASS.equals(classFqn)) {
haveRedirected = true;
}
//String field = FIELD_FQN_NAME;
Set<IndexedConstant> constants = new HashSet<IndexedConstant>();
if (prefix == null) {
prefix = "";
}
addConstantsFromClass(prefix, classFqn, constants, haveRedirected);
return constants;
}
private boolean addConstantsFromClass(
final String prefix,
final String classFqn,
final Set<? super IndexedConstant> constants,
final boolean haveRedirected) {
String searchField = FIELD_FQN_NAME;
Set<IndexResult> result = new HashSet<IndexResult>();
search(searchField, classFqn, QuerySupport.Kind.EXACT, result, RubyUtils.addToArray(CLASS_FIELDS, FIELD_CONSTANT_NAME));
// If this is a bogus class entry (no search rsults) don't continue
if (result.size() <= 0) {
return false;
}
for (IndexResult map : result) {
assert map != null;
String[] indexedConstants = map.getValues(FIELD_CONSTANT_NAME);
if (indexedConstants != null) {
for (String constant : indexedConstants) {
if (prefix.length() == 0 || constant.startsWith(prefix)) {
IndexedConstant c = createConstant(constant, map);
c.setSmart(!haveRedirected);
constants.add(c);
}
}
}
}
return true;
}
public Set<IndexedField> getInheritedFields(String classFqn, String prefix, QuerySupport.Kind kind, boolean inherited) {
boolean haveRedirected = false;
if ((classFqn == null) || classFqn.equals(OBJECT)) {
// Redirect inheritance tree to Class to pick up methods in Class and Module
classFqn = CLASS;
haveRedirected = true;
} else if (MODULE.equals(classFqn) || CLASS.equals(classFqn)) {
haveRedirected = true;
}
//String field = FIELD_FQN_NAME;
Set<IndexedField> members = new HashSet<IndexedField>();
Set<String> scannedClasses = new HashSet<String>();
Set<String> seenSignatures = new HashSet<String>();
boolean instanceVars = true;
if (prefix == null) {
prefix = "";
} else if (prefix.startsWith("@@")) {
instanceVars = false;
prefix = prefix.substring(2);
} else if (prefix.startsWith("@")) {
prefix = prefix.substring(1);
}
addFieldsFromClass(prefix, kind, classFqn, members, seenSignatures, scannedClasses,
haveRedirected, instanceVars, inherited);
return members;
}
/** Return whether the specific class referenced (classFqn) was found or not. This is
* not the same as returning whether any classes were added since it may add
* additional methods from parents (Object/Class).
*/
private boolean addFieldsFromClass(String prefix, QuerySupport.Kind kind, String classFqn,
Set<IndexedField> methods, Set<String> seenSignatures, Set<String> scannedClasses,
boolean haveRedirected, boolean instanceVars, boolean inheriting) {
// Prevent problems with circular includes or redundant includes
if (scannedClasses.contains(classFqn)) {
return false;
}
scannedClasses.add(classFqn);
String searchField = FIELD_FQN_NAME;
Set<IndexResult> result = new HashSet<IndexResult>();
search(searchField, classFqn, QuerySupport.Kind.EXACT, result, RubyUtils.addToArray(CLASS_FIELDS, FIELD_FIELD_NAME));
boolean foundIt = result.size() > 0;
// If this is a bogus class entry (no search rsults) don't continue
if (!foundIt) {
return foundIt;
}
String extendsClass = null;
String classIn = null;
int fqnIndex = classFqn.lastIndexOf("::"); // NOI18N
if (fqnIndex != -1) {
classIn = classFqn.substring(0, fqnIndex);
}
for (IndexResult map : result) {
assert map != null;
if (extendsClass == null) {
extendsClass = map.getValue(FIELD_EXTENDS_NAME);
}
String includes = map.getValue(FIELD_INCLUDES);
if (includes != null) {
String[] in = includes.split(",");
// I have Util::BacktraceFilter and Assertions, which are both
// relative to ::,Test,Test::Unit
for (String include : in) {
// Try both with and without a package qualifier
boolean isQualified = false;
if (classIn != null) {
isQualified = addFieldsFromClass(prefix, kind, classIn + "::" + include,
methods, seenSignatures, scannedClasses, haveRedirected, instanceVars, true);
}
if (!isQualified) {
addFieldsFromClass(prefix, kind, include, methods, seenSignatures,
scannedClasses, haveRedirected, instanceVars, true);
}
}
}
String extendWith = map.getValue(FIELD_EXTEND_WITH);
if (extendWith != null) {
// Try both with and without a package qualifier
boolean isQualified = false;
if (classIn != null) {
isQualified = addFieldsFromClass(prefix, kind, classIn + "::" + extendWith,
methods, seenSignatures, scannedClasses, haveRedirected, instanceVars, true);
}
if (!isQualified) {
addFieldsFromClass(prefix, kind, extendWith, methods, seenSignatures,
scannedClasses, haveRedirected, instanceVars, true);
}
}
String[] fields = map.getValues(FIELD_FIELD_NAME);
if (fields != null) {
for (String field : fields) {
// Skip weird methods like "[]" etc. in completion lists... TODO Think harder about this
if ((prefix.length() == 0) && !Character.isLowerCase(field.charAt(0))) {
continue;
}
// Prevent duplicates when method is redefined
if (!seenSignatures.contains(field)) {
// See if we need instancevars or classvars
boolean isInstance = true;
int signatureIndex = field.indexOf(';');
if (signatureIndex != -1) {
int flags = IndexedElement.stringToFlag(field, signatureIndex + 1);
isInstance = (flags & IndexedElement.STATIC) == 0;
}
if (isInstance != instanceVars) {
continue;
}
if (field.startsWith(prefix)) {
if (kind == QuerySupport.Kind.EXACT) {
// Ensure that the method is not longer than the prefix
if ((field.length() > prefix.length()) &&
(field.charAt(prefix.length()) != '(') &&
(field.charAt(prefix.length()) != ';')) {
continue;
}
} else {
// REGEXP, CAMELCASE filtering etc. not supported here
assert (kind == QuerySupport.Kind.PREFIX) ||
(kind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX);
}
seenSignatures.add(field);
IndexedField f = createField(field, map, isInstance, inheriting);
f.setSmart(!haveRedirected);
methods.add(f);
}
}
}
}
}
if (classFqn.equals(OBJECT)) {
return foundIt;
}
if (extendsClass == null) {
if (haveRedirected) {
addFieldsFromClass(prefix, kind, OBJECT, methods, seenSignatures, scannedClasses,
true, instanceVars, true);
} else {
// Rather than inheriting directly from object,
// let's go via Class (and Module) up to Object
addFieldsFromClass(prefix, kind, CLASS, methods, seenSignatures, scannedClasses,
true, instanceVars, true);
}
} else {
// We're not sure we have a fully qualified path, so try some different candidates
if (!addFieldsFromClass(prefix, kind, extendsClass, methods, seenSignatures,
scannedClasses, haveRedirected, instanceVars, true)) {
// Search by classIn
String fqn = classIn;
while (fqn != null) {
if (addFieldsFromClass(prefix, kind, fqn + "::" + extendsClass, methods,
seenSignatures, scannedClasses, haveRedirected, instanceVars, true)) {
break;
}
int f = fqn.lastIndexOf("::"); // NOI18N
if (f == -1) {
break;
} else {
fqn = fqn.substring(0, f);
}
}
}
}
return foundIt;
}
/** Return all the method or class definitions for the given FQN that are documented. */
public Set<?extends IndexedElement> getDocumented(final String fqn) {
assert (fqn != null) && (fqn.length() > 0);
int hashIndex = fqn.indexOf('#');
if (hashIndex == -1) {
// Looking for a class or a module
return getDocumentedClasses(fqn);
} else {
// Looking for a method
String clz = fqn.substring(0, hashIndex);
String method = fqn.substring(hashIndex + 1);
return getDocumentedMethods(clz, method);
}
}
private Set<IndexedClass> getDocumentedClasses(final String fqn) {
final Set<IndexResult> result = new HashSet<IndexResult>();
String field = FIELD_FQN_NAME;
search(field, fqn, QuerySupport.Kind.EXACT, result, CLASS_FIELDS);
Set<IndexedClass> matches = new HashSet<IndexedClass>();
for (IndexResult map : result) {
assert map != null;
String attributes = map.getValue(FIELD_CLASS_ATTRS);
if (attributes != null) {
int flags = IndexedElement.stringToFlag(attributes, 0);
if ((flags & IndexedElement.DOCUMENTED) != 0) {
matches.add(createClass(fqn, null, map));
}
}
}
return matches;
}
private Set<IndexedMethod> getDocumentedMethods(final String fqn, String method) {
final Set<IndexResult> result = new HashSet<IndexResult>();
String field = FIELD_FQN_NAME;
search(field, fqn, QuerySupport.Kind.EXACT, result);
Set<IndexedMethod> matches = new HashSet<IndexedMethod>();
for (IndexResult map : result) {
String[] signatures = map.getValues(FIELD_METHOD_NAME);
if (signatures != null) {
for (String signature : signatures) {
// Skip weird methods... Think harder about this
if (((method == null) || (method.length() == 0)) &&
!Character.isLowerCase(signature.charAt(0))) {
continue;
}
if (!signature.startsWith(method)) {
continue;
}
// Make sure the name matches exactly
// We know that the prefix is correct from the first part of
// this if clause, by the signature may have more
if (((signature.length() > method.length()) &&
(signature.charAt(method.length()) != '(')) &&
(signature.charAt(method.length()) != ';')) {
continue;
}
int attributes = signature.indexOf(';', method.length());
if (attributes == -1) {
continue;
}
int flags = IndexedElement.stringToFlag(signature, attributes+1);
if ((flags & IndexedElement.DOCUMENTED) != 0) {
// Method is documented
assert map != null;
matches.add(createMethod(signature, map, false));
}
}
}
String[] attribs = map.getValues(FIELD_ATTRIBUTE_NAME);
if (attribs != null) {
for (String signature : attribs) {
// Skip weird methods... Think harder about this
if (((method == null) || (method.length() == 0)) &&
!Character.isLowerCase(signature.charAt(0))) {
continue;
}
if (!signature.startsWith(method)) {
continue;
}
// Make sure the name matches exactly
// We know that the prefix is correct from the first part of
// this if clause, by the signature may have more
if (((signature.length() > method.length()) &&
//(signature.charAt(method.length()) != '(')) &&
(signature.charAt(method.length()) != ';'))) {
continue;
}
// TODO - index whether attributes are documented!
//int attributes = signature.indexOf(';', method.length());
//
//if (attributes == -1) {
// continue;
//}
//
//if (signature.indexOf('d', attributes + 1) != -1) {
// // Method is documented
assert map != null;
matches.add(createMethod(signature, map, false));
//}
}
}
}
return matches;
}
/** Return the {@link FileObject} corresponding to the given require statement */
public FileObject getRequiredFile(final String require) {
final Set<IndexResult> result = new HashSet<IndexResult>();
String field = FIELD_REQUIRE;
search(field, require, QuerySupport.Kind.EXACT, result, FIELD_REQUIRE);
// TODO Prune methods to fit my scheme - later make lucene index smarter about how to prune its index search
for (IndexResult ir : result) {
FileObject file = ir.getFile();
if (file != null) {
return file;
}
}
return null;
}
static String getClusterUrl() {
if (clusterUrl == null) {
File f =
InstalledFileLocator.getDefault()
.locate("modules/org-netbeans-modules-ruby.jar", null, false); // NOI18N
if (f == null) {
throw new RuntimeException("Can't find cluster");
}
f = new File(f.getParentFile().getParentFile().getAbsolutePath());
try {
f = f.getCanonicalFile();
clusterUrl = f.toURI().toURL().toExternalForm();
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
}
}
return clusterUrl;
}
// For testing only
public static void setClusterUrl(String url) {
clusterUrl = url;
}
static String getPreindexUrl(String url) {
return getPreindexUrl(url, null);
}
static String getPreindexUrl(String url, FileObject context) {
// no preindexing in parsing api
// if (RubyIndexer.isPreindexing()) {
// Iterator<RubyPlatform> it = null;
// if (context != null && context.isValid()) {
// Project project = FileOwnerQuery.getOwner(context);
// if (project != null) {
// RubyPlatform platform = RubyPlatform.platformFor(project);
// if (platform != null) {
// it = Collections.singleton(platform).iterator();
// }
// }
// }
// if (it == null) {
// it = RubyPlatformManager.platformIterator();
// }
// while (it.hasNext()) {
// RubyPlatform platform = it.next();
// String s = getGemHomeURL(platform);
//
// if (s != null && url.startsWith(s)) {
// return GEM_URL + url.substring(s.length());
// }
//
// s = platform.getHomeUrl();
//
// if (url.startsWith(s)) {
// url = RUBYHOME_URL + url.substring(s.length());
//
// return url;
// }
// }
// } else {
// FIXME: use right platform
RubyPlatform platform = RubyPlatformManager.getDefaultPlatform();
if (platform != null) {
String s = getGemHomeURL(platform);
if (s != null && url.startsWith(s)) {
return GEM_URL + url.substring(s.length());
}
s = platform.getHomeUrl();
if (url.startsWith(s)) {
url = RUBYHOME_URL + url.substring(s.length());
return url;
}
}
// }
String s = getClusterUrl();
if (url.startsWith(s)) {
return CLUSTER_URL + url.substring(s.length());
}
return url;
}
/** Get the FileObject corresponding to a URL returned from the index */
public static FileObject getFileObject(String url) {
return getFileObject(url, null);
}
public static FileObject getFileObject(String url, FileObject context) {
try {
if (url.startsWith(RUBYHOME_URL)) {
Iterator<RubyPlatform> it = null;
if (context != null) {
Project project = FileOwnerQuery.getOwner(context);
if (project != null) {
RubyPlatform platform = RubyPlatform.platformFor(project);
if (platform != null) {
it = Collections.singleton(platform).iterator();
}
}
}
if (it == null) {
it = RubyPlatformManager.platformIterator();
}
while (it.hasNext()) {
RubyPlatform platform = it.next();
String u = platform.getHomeUrl() + url.substring(RUBYHOME_URL.length());
FileObject fo = URLMapper.findFileObject(new URL(u));
if (fo != null) {
return fo;
}
}
return null;
} else if (url.startsWith(GEM_URL)) {
Iterator<RubyPlatform> it = null;
if (context != null) {
Project project = FileOwnerQuery.getOwner(context);
if (project != null) {
RubyPlatform platform = RubyPlatform.platformFor(project);
if (platform != null) {
it = Collections.singleton(platform).iterator();
}
}
}
if (it == null) {
it = RubyPlatformManager.platformIterator();
}
while (it.hasNext()) {
RubyPlatform platform = it.next();
if (!platform.hasRubyGemsInstalled()) {
continue;
}
GemManager gemManager = platform.getGemManager();
if (gemManager != null) {
String u = gemManager.getGemHomeUrl() + url.substring(GEM_URL.length());
FileObject fo = URLMapper.findFileObject(new URL(u));
if (fo != null) {
return fo;
}
}
}
return null;
} else if (url.startsWith(CLUSTER_URL)) {
url = getClusterUrl() + url.substring(CLUSTER_URL.length()); // NOI18N
}
return URLMapper.findFileObject(new URL(url));
} catch (MalformedURLException mue) {
Exceptions.printStackTrace(mue);
}
return null;
}
private static String getGemHomeURL(RubyPlatform platform) {
return platform.hasRubyGemsInstalled() ? platform.getGemManager().getGemHomeUrl() : null;
}
}