/*
* Author: C.Williams
*
* Copyright (c) 2004 RubyPeople.
*
* This file is part of the Ruby Development Tools (RDT) plugin for eclipse. You
* can get copy of the GPL along with further information about RubyPeople and
* third party software bundled with RDT in the file
* org.rubypeople.rdt.core_x.x.x/RDT.license or otherwise at
* http://www.rubypeople.org/RDT.license.
*
* RDT is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* RDT is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* RDT; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston, MA 02111-1307 USA
*/
package org.rubypeople.rdt.internal.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.rubypeople.rdt.core.IMethod;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.core.compiler.CategorizedProblem;
import org.rubypeople.rdt.internal.compiler.ISourceElementRequestor;
/**
* @author Chris
*/
public class RubyScriptStructureBuilder implements ISourceElementRequestor
{
private InfoStack infoStack;
private HandleStack modelStack;
private RubyScriptElementInfo scriptInfo;
private IRubyScript script;
private Map<IRubyElement, RubyElementInfo> newElements;
private RubyElementInfo importContainerInfo;
/**
* @param script
* The RubyScript whose contents we're parsing
* @param info
* The RubyElementInfo of the RubyScript
* @param newElements
* a Map passed in. It is actually a temporarcy cache from the RubyModelManager. It holds elements below
* the level of a RubyScript in our hierarchy.
*/
public RubyScriptStructureBuilder(IRubyScript script, RubyScriptElementInfo info, Map<IRubyElement, RubyElementInfo> newElements)
{
this.script = script;
this.scriptInfo = info;
this.newElements = newElements;
infoStack = new InfoStack();
modelStack = new HandleStack();
modelStack.push(script);
infoStack.push(scriptInfo);
}
/**
* @return
*/
private RubyElementInfo getCurrentTypeInfo()
{
List<RubyElementInfo> extras = new ArrayList<RubyElementInfo>();
RubyElementInfo element = infoStack.peek();
while (!(element instanceof RubyTypeElementInfo))
{
extras.add(infoStack.pop());
element = infoStack.peek();
if (element == null)
break;
}
Collections.reverse(extras); // Need to reverse extra before pushing back on the stack!
for (Iterator<RubyElementInfo> iter = extras.iterator(); iter.hasNext();)
{
infoStack.push(iter.next());
}
if (element == null)
return scriptInfo;
return element;
}
private RubyType findChild(RubyElement parent, int type, String name)
{
try
{
// FIXME What should we do when resource doesn't "exist" (is
// external?)
if (!parent.exists())
return null;
List<IRubyElement> children = parent.getChildrenOfType(type);
for (IRubyElement element : children)
{
if (element.getElementName().equals(name))
return (RubyType) element;
}
}
catch (RubyModelException e)
{
RubyCore.log(e);
}
return null;
}
/**
* @return
*/
private RubyElement getCurrentType()
{
List<IRubyElement> extras = new ArrayList<IRubyElement>();
IRubyElement element = modelStack.peek();
while (!element.isType(IRubyElement.TYPE))
{
extras.add(modelStack.pop());
element = modelStack.peek();
if (element == null)
break;
}
Collections.reverse(extras); // Need to reverse elements before pushing back onto stack!
for (Iterator<IRubyElement> iter = extras.iterator(); iter.hasNext();)
{
modelStack.push(iter.next());
}
if (element == null)
return (RubyScript) script;
return (RubyElement) element;
}
public void acceptConstructorReference(String name, int argCount, int offset)
{
}
public void acceptFieldReference(String name, int offset)
{
}
public void acceptImport(String value, int startOffset, int endOffset)
{
ImportContainer importContainer = (ImportContainer) script.getImportContainer();
// create the import container and its info
if (this.importContainerInfo == null)
{
this.importContainerInfo = new RubyElementInfo();
scriptInfo.addChild(importContainer);
this.newElements.put(importContainer, this.importContainerInfo);
}
RubyImport handle = new RubyImport(importContainer, value);
ImportDeclarationElementInfo info = new ImportDeclarationElementInfo();
info.setNameSourceStart(startOffset);
info.setNameSourceEnd(endOffset);
info.setSourceRangeStart(startOffset);
info.setSourceRangeEnd(endOffset);
info.name = value;
this.importContainerInfo.addChild(handle);
this.newElements.put(handle, info);
}
public void acceptMethodReference(String name, int argCount, int offset)
{
}
public void acceptProblem(CategorizedProblem problem)
{
}
public void acceptTypeReference(String name, int startOffset, int endOffset)
{
}
public void acceptUnknownReference(String name, int startOffset, int endOffset)
{
}
public void enterConstructor(MethodInfo constructor)
{
enterMethod(constructor);
}
public void enterField(FieldInfo field)
{
RubyField handle;
if (field.name.startsWith("@@"))
{
handle = new RubyClassVar(getCurrentType(), field.name);
}
else if (field.name.startsWith("@"))
{
handle = new RubyInstVar(getCurrentType(), field.name);
}
else if (field.name.startsWith("$"))
{
handle = new RubyGlobal(script, field.name);
}
else if (Character.isUpperCase(field.name.charAt(0)))
{
handle = new RubyConstant(getCurrentType(), field.name);
}
else
{
int start = field.declarationStart - field.name.length() + 1;
int end = start + field.name.length();
if (field.isDynamic)
{
handle = new RubyDynamicVar(modelStack.peek(), field.name, start, end);
}
else
{
handle = new LocalVariable(modelStack.peek(), field.name, start, end);
}
}
modelStack.push(handle);
// Add to enclosing type
RubyElementInfo parentInfo;
if (handle instanceof LocalVariable || handle instanceof RubyDynamicVar)
{
parentInfo = infoStack.peek();
}
else if (handle instanceof RubyGlobal)
{
parentInfo = scriptInfo; // FIXME Grab the project info?
}
else
{
parentInfo = getCurrentTypeInfo();
}
parentInfo.addChild(handle);
RubyFieldElementInfo info = new RubyFieldElementInfo();
info.setSourceRangeStart(field.declarationStart);
info.setNameSourceStart(field.nameSourceStart);
info.setNameSourceEnd(field.nameSourceEnd);
infoStack.push(info);
newElements.put(handle, info);
}
public void enterMethod(MethodInfo methodInfo)
{
RubyMethod method = new RubyMethod(getCurrentType(), methodInfo.name, methodInfo.parameterNames);
modelStack.push(method);
infoStack.peek().addChild(method);
RubyMethodElementInfo info = new RubyMethodElementInfo();
info.setArgumentNames(methodInfo.parameterNames);
info.setVisibility(methodInfo.visibility);
info.setNameSourceStart(methodInfo.nameSourceStart);
info.setNameSourceEnd(methodInfo.nameSourceEnd);
info.setSourceRangeStart(methodInfo.declarationStart);
info.setIsSingleton(methodInfo.isClassLevel);
infoStack.push(info);
newElements.put(method, info);
}
public void acceptYield(String name)
{
((RubyMethodElementInfo) infoStack.peek()).addBlockVar(name);
}
public void enterScript()
{
// do nothing
}
public void enterType(TypeInfo type)
{
RubyType handle;
if (type.isModule)
{
handle = new RubyModule(modelStack.peek(), type.name);
}
else
{
handle = new RubyType(modelStack.peek(), type.name);
}
RubyElement parent = modelStack.peek();
RubyType existing = findChild(parent, IRubyElement.TYPE, type.name);
if (existing != null)
{
// FIXME Should we just increment the occurence count like I do
// here, or should we conglomerate the types into one LogicalType?
handle.occurrenceCount = existing.occurrenceCount + 1;
}
modelStack.push(handle);
infoStack.peek().addChild(handle);
RubyTypeElementInfo info = new RubyTypeElementInfo();
info.setHandle(handle);
info.setNameSourceStart(type.nameSourceStart);
info.setNameSourceEnd(type.nameSourceEnd);
info.setSourceRangeStart(type.declarationStart);
info.setSuperclassName(type.superclass);
info.setIncludedModuleNames(type.modules);
infoStack.push(info);
newElements.put(handle, info);
}
public void exitConstructor(int endOffset)
{
exitMethod(endOffset);
}
public void exitField(int endOffset)
{
RubyFieldElementInfo info = (RubyFieldElementInfo) infoStack.pop();
info.setSourceRangeEnd(endOffset);
modelStack.pop();
}
public void exitMethod(int endOffset)
{
RubyMethodElementInfo info = (RubyMethodElementInfo) infoStack.pop();
info.setSourceRangeEnd(endOffset);
modelStack.pop();
}
public void exitScript(int endOffset)
{
modelStack.pop();
infoStack.pop();
}
public void exitType(int endOffset)
{
RubyTypeElementInfo info = (RubyTypeElementInfo) infoStack.pop();
info.setSourceRangeEnd(endOffset);
modelStack.pop();
}
public void acceptMixin(String string)
{
// Push mixins into parent type, if available
RubyElementInfo info = getCurrentTypeInfo();
if (!(info instanceof RubyTypeElementInfo))
return; // FIXME Include this in a default toplevel type for the script?!
RubyTypeElementInfo parentType = (RubyTypeElementInfo) info;
// Get existing imported module names
String[] importedModuleNames = parentType.getIncludedModuleNames();
List<String> mergedModuleNames = new LinkedList<String>();
// Merge newly found module name(s)
if (importedModuleNames != null)
{
mergedModuleNames.addAll((Arrays.asList(importedModuleNames)));
}
mergedModuleNames.add(string);
// Apply included module names back to parent type info
String[] newIncludedModuleNames = mergedModuleNames.toArray(new String[] {});
parentType.setIncludedModuleNames(newIncludedModuleNames);
}
public void acceptMethodVisibilityChange(String methodName, int visibility)
{
RubyElementInfo info = getCurrentTypeInfo();
if (!(info instanceof RubyTypeElementInfo))
return;
RubyTypeElementInfo parentType = (RubyTypeElementInfo) info;
IMethod[] methods = parentType.getMethods();
for (int i = 0; i < methods.length; i++)
{
RubyMethod method = (RubyMethod) methods[i];
if (!method.getElementName().equals(methodName))
continue;
;
try
{
RubyMethodElementInfo methodInfo = (RubyMethodElementInfo) method.getElementInfo();
methodInfo.setVisibility(visibility);
return;
}
catch (RubyModelException e)
{
RubyCore.log(e);
}
}
}
public void acceptModuleFunction(String methodName)
{
RubyElementInfo info = getCurrentTypeInfo();
if (!(info instanceof RubyTypeElementInfo))
return;
RubyTypeElementInfo parentType = (RubyTypeElementInfo) info;
IMethod[] methods = parentType.getMethods();
for (int i = 0; i < methods.length; i++)
{
RubyMethod method = (RubyMethod) methods[i];
if (!method.getElementName().equals(methodName))
continue;
try
{
RubyMethodElementInfo methodInfo = (RubyMethodElementInfo) method.getElementInfo();
methodInfo.setIsSingleton(true);
return;
}
catch (RubyModelException e)
{
RubyCore.log(e);
}
}
}
public void acceptBlock(int startOffset, int endOffset)
{
RubyBlock method = new RubyBlock(modelStack.peek());
infoStack.peek().addChild(method);
SourceRefElementInfo info = new SourceRefElementInfo();
info.setSourceRangeStart(startOffset);
info.setSourceRangeEnd(endOffset);
newElements.put(method, info);
}
}