/**
* Aptana Studio
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ruby.internal.core.index;
import java.net.URI;
import java.util.Stack;
import com.aptana.core.util.ArrayUtil;
import com.aptana.core.util.StringUtil;
import com.aptana.index.core.Index;
import com.aptana.ruby.core.IRubyConstants;
import com.aptana.ruby.core.IRubyMethod.Visibility;
import com.aptana.ruby.core.ISourceElementRequestor;
import com.aptana.ruby.core.index.IRubyIndexConstants;
// TODO Also index symbols?
class RubySourceIndexer implements ISourceElementRequestor
{
private static final String NAMESPACE_DELIMETER = IRubyConstants.NAMESPACE_DELIMETER;
protected static final String VERSION_KEY = "index_version"; //$NON-NLS-1$
protected static final int CURRENT_VERSION = 5;
private Stack<TypeInfo> typeStack = new Stack<TypeInfo>();
private Index index;
private URI documentPath;
RubySourceIndexer(Index index, URI documentPath)
{
this.index = index;
this.documentPath = documentPath;
}
private void addIndex(String category, String word)
{
index.addEntry(category, word, documentPath);
}
public void exitType(int endOffset)
{
typeStack.pop();
}
public void exitScript(int endOffset)
{
typeStack.clear();
}
public void exitMethod(int endOffset)
{
}
public void exitField(int endOffset)
{
}
public void exitConstructor(int endOffset)
{
}
public void enterType(TypeInfo type)
{
String simpleName = getSimpleName(type.name);
String[] enclosingTypes = getEnclosingTypeNames(type.name);
addClassDeclaration(type.isModule, simpleName, enclosingTypes, type.superclass, type.modules, type.secondary);
typeStack.push(type);
}
private void addClassDeclaration(boolean isModule, String simpleName, String[] enclosingTypes, String superclass,
String[] modules, boolean secondary)
{
String indexKey = createTypeDeclarationKey(isModule, simpleName, enclosingTypes, secondary);
addIndex(IRubyIndexConstants.TYPE_DECL, indexKey);
if (superclass != null && !IRubyConstants.OBJECT.equals(superclass))
{
addTypeReference(superclass);
}
if (!isModule)
{
// We know that both class and superclass must be classes because Modules can't have subclasses
if (superclass != null && !IRubyConstants.OBJECT.equals(superclass))
{
addIndex(
IRubyIndexConstants.SUPER_REF,
createSuperTypeReferenceKey(simpleName, enclosingTypes, IRubyIndexConstants.CLASS_SUFFIX,
superclass, IRubyIndexConstants.CLASS_SUFFIX));
}
}
if (modules != null)
{
for (String module : modules)
{
addTypeReference(module);
addIncludedModuleReference(simpleName, enclosingTypes, module);
}
}
}
/**
* Generates a key of the form: TypeName/namespace/(M|C)/S i.e. "Base/ActiveRecord/C"
*
* @param isModule
* @param typeName
* @param packageName
* @param enclosingTypeNames
* @param secondary
* @return
*/
private String createTypeDeclarationKey(boolean isModule, String typeName, String[] enclosingTypeNames,
boolean secondary)
{
StringBuilder builder = new StringBuilder();
builder.append(typeName);
builder.append(IRubyIndexConstants.SEPARATOR);
if (enclosingTypeNames != null && enclosingTypeNames.length > 0)
{
for (String enclosingName : enclosingTypeNames)
{
builder.append(enclosingName);
builder.append(NAMESPACE_DELIMETER);
}
builder.delete(builder.length() - 2, builder.length());
}
builder.append(IRubyIndexConstants.SEPARATOR);
builder.append(isModule ? IRubyIndexConstants.MODULE_SUFFIX : IRubyIndexConstants.CLASS_SUFFIX);
if (secondary)
{
builder.append(IRubyIndexConstants.SEPARATOR);
builder.append('S');
}
return builder.toString();
}
private String[] getEnclosingTypeNames(String typeName)
{
String[] parts = typeName.split(NAMESPACE_DELIMETER);
String[] names = new String[typeStack.size() + parts.length - 1];
int i = 0;
for (TypeInfo info : typeStack)
{
names[i++] = info.name;
}
for (int j = 0; j < parts.length - 1; j++)
{
names[i++] = parts[j];
}
return names;
}
public void enterScript()
{
}
public void enterConstructor(MethodInfo constructor)
{
// FIXME Create a special key for the constructor
addIndex(
IRubyIndexConstants.CONSTRUCTOR_DECL,
createMethodDefKey("initialize", getSimpleName(constructor.name), //$NON-NLS-1$
new String[] { getNamespace(constructor.name) }, Visibility.PUBLIC, true,
constructor.parameterNames.length));
}
public void enterField(FieldInfo field)
{
if (field == null || field.name == null || field.name.length() == 0)
{
return;
}
if (field.name.startsWith("@@")) //$NON-NLS-1$
{
addIndex(IRubyIndexConstants.FIELD_DECL, createdNamespacedFieldKey(field.name));
return;
}
else if (field.name.length() > 0 && field.name.charAt(0) == '@')
{
addIndex(IRubyIndexConstants.FIELD_DECL, createdNamespacedFieldKey(field.name));
return;
}
else if (field.name.length() > 0 && field.name.charAt(0) == '$')
{
addIndex(IRubyIndexConstants.GLOBAL_DECL, field.name);
return;
}
else if (Character.isUpperCase(field.name.charAt(0)))
{
addIndex(IRubyIndexConstants.CONSTANT_DECL, createdNamespacedFieldKey(field.name));
return;
}
addIndex(IRubyIndexConstants.LOCAL_DECL, field.name);
}
private String createdNamespacedFieldKey(String name)
{
// TODO Use Toplevel, not Object?
String simpleName = IRubyConstants.OBJECT;
String[] enclosingTypes = ArrayUtil.NO_STRINGS;
if (!typeStack.isEmpty())
{
TypeInfo info = typeStack.pop();
simpleName = getSimpleName(info.name);
enclosingTypes = getEnclosingTypeNames(info.name);
typeStack.push(info);
}
StringBuilder builder = new StringBuilder();
builder.append(name);
builder.append(IRubyIndexConstants.SEPARATOR);
// Defining type simple name
builder.append(simpleName);
builder.append(IRubyIndexConstants.SEPARATOR);
// Defining type namespace
if (enclosingTypes != null && enclosingTypes.length > 0)
{
for (String enclosingName : enclosingTypes)
{
builder.append(enclosingName);
builder.append(NAMESPACE_DELIMETER);
}
builder.delete(builder.length() - 2, builder.length());
}
return builder.toString();
}
public void enterMethod(MethodInfo method)
{
// TODO Use Toplevel, not Object?
String simpleName = IRubyConstants.OBJECT;
String[] enclosingTypes = ArrayUtil.NO_STRINGS;
if (!typeStack.isEmpty())
{
TypeInfo info = typeStack.pop();
simpleName = getSimpleName(info.name);
enclosingTypes = getEnclosingTypeNames(info.name);
typeStack.push(info);
}
addIndex(
IRubyIndexConstants.METHOD_DECL,
createMethodDefKey(method.name, simpleName, enclosingTypes, method.visibility, method.isClassLevel,
method.parameterNames.length));
}
public void acceptYield(String name)
{
}
public void acceptTypeReference(String name, int startOffset, int endOffset)
{
addTypeReference(name);
}
private void addTypeReference(String name)
{
addIndex(IRubyIndexConstants.REF, getSimpleName(name));
}
private String lastSegment(String name, String delimeter)
{
if (name == null)
{
return null;
}
int index = name.lastIndexOf(delimeter);
if (index != -1)
{
return name.substring(index + delimeter.length());
}
return name;
}
public void acceptModuleFunction(String function)
{
}
public void acceptMixin(String moduleName)
{
addIndex(IRubyIndexConstants.REF, getSimpleName(moduleName));
if (typeStack != null && !typeStack.isEmpty())
{
// We need to pop and then push after grabbing namespace because the method assumes the current type info
// isn't on the stack yet!
TypeInfo info = typeStack.pop();
String[] enclosingTypes = getEnclosingTypeNames(info.name);
typeStack.push(info);
addIncludedModuleReference(getSimpleName(info.name), enclosingTypes, moduleName);
}
}
private void addIncludedModuleReference(String simpleName, String[] enclosingTypes, String moduleName)
{
addIndex(
IRubyIndexConstants.SUPER_REF,
createSuperTypeReferenceKey(simpleName, enclosingTypes, IRubyIndexConstants.CLASS_SUFFIX, moduleName,
IRubyIndexConstants.MODULE_SUFFIX));
}
/**
* SuperTypeName(Simple)/SuperTypeNamespace/SimpleName/EnclosingTypeName/SuperIsClassOrModule(M|C)
* isClassorModule(M|C)
*
* @param typeName
* @param enclosingTypeNames
* @param classOrModule
* @param superTypeName
* @param superClassOrModule
* @return
*/
private String createSuperTypeReferenceKey(String typeName, String[] enclosingTypeNames, char classOrModule,
String superTypeName, char superClassOrModule)
{
if (superTypeName == null)
{
superTypeName = IRubyConstants.OBJECT;
}
String superSimpleName = lastSegment(superTypeName, NAMESPACE_DELIMETER);
char[] superQualification = null;
if (!superTypeName.equals(superSimpleName))
{
int length = superTypeName.length() - superSimpleName.length() - 1;
superQualification = new char[length - 1];
System.arraycopy(superTypeName.toCharArray(), 0, superQualification, 0, length - 1);
}
// if the supertype name contains a $, then split it into: source name and append the $
// prefix to the qualification
// e.g. p.A$B ---> p.A$ + B
String superTypeSourceName = lastSegment(superSimpleName, NAMESPACE_DELIMETER);
if (superSimpleName != null && !superSimpleName.equals(superTypeSourceName))
{
int start = (superQualification == null) ? 0 : superQualification.length + 1;
int prefixLength = superSimpleName.length() - superTypeSourceName.length();
char[] mangledQualification = new char[start + prefixLength];
if (superQualification != null)
{
System.arraycopy(superQualification, 0, mangledQualification, 0, start - 1);
mangledQualification[start - 1] = '.';
}
System.arraycopy(superSimpleName.toCharArray(), 0, mangledQualification, start, prefixLength);
superQualification = mangledQualification;
superSimpleName = superTypeSourceName;
}
String simpleName = lastSegment(typeName, NAMESPACE_DELIMETER);
String enclosingTypeName = StringUtil.join(NAMESPACE_DELIMETER, enclosingTypeNames);
StringBuilder builder = new StringBuilder();
builder.append(superSimpleName);
builder.append(IRubyIndexConstants.SEPARATOR);
if (superQualification != null)
{
builder.append(superQualification);
}
builder.append(IRubyIndexConstants.SEPARATOR);
builder.append(simpleName);
builder.append(IRubyIndexConstants.SEPARATOR);
builder.append(enclosingTypeName);
builder.append(IRubyIndexConstants.SEPARATOR);
builder.append(superClassOrModule);
builder.append(classOrModule);
return builder.toString();
}
private String getNamespace(String name)
{
if (name == null)
{
return null;
}
int index = name.lastIndexOf(NAMESPACE_DELIMETER);
if (index != -1)
{
return name.substring(0, index);
}
return StringUtil.EMPTY;
}
private String getSimpleName(String name)
{
return lastSegment(name, NAMESPACE_DELIMETER);
}
public void acceptMethodVisibilityChange(String methodName, Visibility visibility)
{
}
public void acceptMethodReference(String name, int argCount, int offset)
{
addIndex(IRubyIndexConstants.METHOD_REF, createMethodRefKey(name, argCount));
}
private String createMethodRefKey(String name, int argCount)
{
return name + IRubyIndexConstants.SEPARATOR + String.valueOf(argCount);
}
private String createMethodDefKey(String methodName, String definingTypeSimpleName, String[] definingTypeNamespace,
Visibility visibility, boolean isSingleton, int argCount)
{
StringBuilder builder = new StringBuilder();
builder.append(methodName);
builder.append(IRubyIndexConstants.SEPARATOR);
// Defining type simple name
builder.append(definingTypeSimpleName);
builder.append(IRubyIndexConstants.SEPARATOR);
// Defining type namespace
if (definingTypeNamespace != null && definingTypeNamespace.length > 0)
{
for (String enclosingName : definingTypeNamespace)
{
builder.append(enclosingName);
builder.append(NAMESPACE_DELIMETER);
}
builder.delete(builder.length() - 2, builder.length());
}
builder.append(IRubyIndexConstants.SEPARATOR);
// Visibility
builder.append(getVisibilityChar(visibility));
builder.append(IRubyIndexConstants.SEPARATOR);
// Singleton or instance
builder.append(isSingleton ? 'S' : 'I');
builder.append(IRubyIndexConstants.SEPARATOR);
// Arg count
builder.append(String.valueOf(argCount));
return builder.toString();
}
private char getVisibilityChar(Visibility visibility)
{
switch (visibility)
{
case PRIVATE:
return 'V';
case PROTECTED:
return 'R';
case PUBLIC:
return 'P';
}
return 'X';
}
public void acceptImport(String value, int startOffset, int endOffset)
{
// FIXME This is really, really bad. requires are relative to loadpaths, which are dynamic.
// IFile requireFile = file.getParent().getFile(new Path(value));
// if (requireFile.exists())
// {
// addIndex(IRubyIndexConstants.REQUIRE, requireFile.getProjectRelativePath().toPortableString());
// }
addIndex(IRubyIndexConstants.REQUIRE, value);
}
public void acceptFieldReference(String name, int offset)
{
addIndex(IRubyIndexConstants.REF, name);
}
public void acceptConstructorReference(String name, int argCount, int offset)
{
String simpleTypeName = getSimpleName(name);
addIndex(IRubyIndexConstants.REF, simpleTypeName);
addIndex(IRubyIndexConstants.CONSTRUCTOR_REF, createMethodRefKey(simpleTypeName, argCount));
}
public void enterBlock(int startOffset, int endOffset)
{
}
public void exitBlock(int endOffset)
{
// no-op
}
}