/** * 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.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Stack; import com.aptana.core.logging.IdeLog; import com.aptana.parsing.ast.IParseNode; import com.aptana.ruby.core.IRubyMethod.Visibility; import com.aptana.ruby.internal.core.RubyBlock; import com.aptana.ruby.internal.core.RubyClassVariable; import com.aptana.ruby.internal.core.RubyConstant; import com.aptana.ruby.internal.core.RubyDynamicVariable; import com.aptana.ruby.internal.core.RubyElement; import com.aptana.ruby.internal.core.RubyField; import com.aptana.ruby.internal.core.RubyGlobal; import com.aptana.ruby.internal.core.RubyImport; import com.aptana.ruby.internal.core.RubyImportContainer; import com.aptana.ruby.internal.core.RubyInstanceVariable; import com.aptana.ruby.internal.core.RubyLocalVariable; import com.aptana.ruby.internal.core.RubyMethod; import com.aptana.ruby.internal.core.RubyModule; import com.aptana.ruby.internal.core.RubyScript; import com.aptana.ruby.internal.core.RubyType; /** * This class is public for Ruby CA in the ruble right now! * * @author Chris Williams * @author Michael Xia */ public class RubyStructureBuilder implements ISourceElementRequestor { private RubyScript script; private Stack<RubyElement> modelStack; public RubyStructureBuilder(RubyScript script) { this.script = script; modelStack = new Stack<RubyElement>(); modelStack.push(script); } public void enterBlock(int startOffset, int endOffset) { RubyElement parent = modelStack.peek(); RubyBlock block = new RubyBlock(startOffset, endOffset); parent.addChild(block); modelStack.push(block); } 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) { IImportContainer importContainer = script.getImportContainer(); if (importContainer instanceof RubyImportContainer) { ((RubyImportContainer) importContainer).addChild(new RubyImport(value, startOffset, endOffset)); } } public void acceptMethodReference(String name, int argCount, int offset) { } public void acceptMethodVisibilityChange(String methodName, Visibility visibility) { RubyElement element = getCurrentType(); if (!(element instanceof RubyType)) { return; } RubyType parentType = (RubyType) element; IRubyMethod[] methods = parentType.getMethods(); for (IRubyMethod method : methods) { if (!method.getName().equals(methodName)) { continue; } if (method instanceof RubyMethod) { ((RubyMethod) method).setVisibility(visibility); } } } public void acceptMixin(String string) { // pushes mixins into parent type, if available RubyElement element = getCurrentType(); if (!(element instanceof RubyType)) { return; } RubyType parentType = (RubyType) element; List<String> moduleNames = new LinkedList<String>(); moduleNames.addAll(Arrays.asList(parentType.getIncludedModuleNames())); moduleNames.add(string); // applies included module names back to the parent type parentType.setIncludedModuleNames(moduleNames.toArray(new String[moduleNames.size()])); } public void acceptModuleFunction(String function) { RubyElement element = getCurrentType(); if (!(element instanceof RubyType)) { return; } RubyType parentType = (RubyType) element; IRubyMethod[] methods = parentType.getMethods(); for (IRubyMethod method : methods) { if (!method.getName().equals(function)) { continue; } if (method instanceof RubyMethod) { ((RubyMethod) method).setIsSingleton(true); } } } public void acceptTypeReference(String name, int startOffset, int endOffset) { } public void acceptYield(String name) { if (!modelStack.isEmpty()) { RubyElement element = modelStack.peek(); if (element instanceof RubyMethod) { ((RubyMethod) element).addBlockVar(name); } } } public void enterConstructor(MethodInfo constructor) { enterMethod(constructor); } public void enterField(FieldInfo fieldInfo) { // ignore fields with empty names if (fieldInfo == null || fieldInfo.name == null || fieldInfo.name.length() == 0) { return; } RubyField handle; RubyElement parent = getCurrentType(); if (fieldInfo.name.startsWith("@@")) //$NON-NLS-1$ { handle = new RubyClassVariable(fieldInfo.name, fieldInfo.declarationStart, fieldInfo.nameSourceStart, fieldInfo.nameSourceEnd); } else if (fieldInfo.name.length() > 0 && fieldInfo.name.charAt(0) == '@') { handle = new RubyInstanceVariable(fieldInfo.name, fieldInfo.declarationStart, fieldInfo.nameSourceStart, fieldInfo.nameSourceEnd); } else if (fieldInfo.name.length() > 0 && fieldInfo.name.charAt(0) == '$') { parent = script; handle = new RubyGlobal(fieldInfo.name, fieldInfo.declarationStart, fieldInfo.nameSourceStart, fieldInfo.nameSourceEnd); } else if (Character.isUpperCase(fieldInfo.name.charAt(0))) { handle = new RubyConstant(fieldInfo.name, fieldInfo.declarationStart, fieldInfo.nameSourceStart, fieldInfo.nameSourceEnd); } else { parent = modelStack.peek(); if (fieldInfo.isDynamic) { handle = new RubyDynamicVariable(fieldInfo.name, fieldInfo.declarationStart, fieldInfo.nameSourceStart, fieldInfo.nameSourceEnd); } else { handle = new RubyLocalVariable(fieldInfo.name, fieldInfo.declarationStart, fieldInfo.nameSourceStart, fieldInfo.nameSourceEnd); } } // for field, checks if it has been stored in the parent once if (!hasChild(parent, handle)) { parent.addChild(handle); } modelStack.push(handle); } public void enterMethod(MethodInfo methodInfo) { RubyMethod method = new RubyMethod(methodInfo.name, methodInfo.parameterNames, methodInfo.declarationStart, methodInfo.nameSourceStart, methodInfo.nameSourceEnd); method.setVisibility(methodInfo.visibility); method.setIsSingleton(methodInfo.isClassLevel); getCurrentType().addChild(method); modelStack.push(method); } public void enterScript() { } public void enterType(TypeInfo typeInfo) { RubyType handle; if (typeInfo.isModule) { handle = new RubyModule(typeInfo.name, typeInfo.declarationStart, typeInfo.nameSourceStart, typeInfo.nameSourceEnd); } else { handle = new RubyType(typeInfo.name, typeInfo.declarationStart, typeInfo.nameSourceStart, typeInfo.nameSourceEnd); } handle.setSuperclassName(typeInfo.superclass); handle.setIncludedModuleNames(typeInfo.modules); RubyElement parent = modelStack.peek(); RubyType existing = (RubyType) findChild(parent, IRubyElement.TYPE, typeInfo.name); if (existing != null) { handle.incrementOccurrence(); } parent.addChild(handle); modelStack.push(handle); } public void exitConstructor(int endOffset) { exitMethod(endOffset); } public void exitField(int endOffset) { if (modelStack.isEmpty()) { IdeLog.logError(RubyCorePlugin.getDefault(), "AST stack was empty upon exiting field declaration, but should have contained the field."); //$NON-NLS-1$ return; } RubyElement element = modelStack.pop(); if (!(element instanceof RubyField)) { IdeLog.logError(RubyCorePlugin.getDefault(), "Expected field decl on top of stack, but was: " + element); //$NON-NLS-1$ } element.setLocation(element.getStartingOffset(), endOffset + 1); } public void exitMethod(int endOffset) { if (modelStack.isEmpty()) { IdeLog.logError(RubyCorePlugin.getDefault(), "AST stack was empty upon exiting method declaration, but should have contained the method."); //$NON-NLS-1$ return; } RubyElement element = modelStack.pop(); if (!(element instanceof RubyMethod)) { IdeLog.logError(RubyCorePlugin.getDefault(), "Expected method decl on top of stack, but was: " + element); //$NON-NLS-1$ } element.setLocation(element.getStartingOffset(), endOffset + 1); } public void exitScript(int endOffset) { if (modelStack.isEmpty()) { IdeLog.logError(RubyCorePlugin.getDefault(), "AST stack was empty upon exiting script, but should have contained the script."); //$NON-NLS-1$ return; } RubyElement element = modelStack.pop(); if (!(element instanceof RubyScript)) { IdeLog.logError(RubyCorePlugin.getDefault(), "Expected script on top of stack, but was: " + element.getClass().getName()); //$NON-NLS-1$ } element.setLocation(element.getStartingOffset(), endOffset + 1); } public void exitType(int endOffset) { if (modelStack.isEmpty()) { IdeLog.logError(RubyCorePlugin.getDefault(), "AST stack was empty upon exiting type declaration, but should have contained the type."); //$NON-NLS-1$ return; } RubyElement element = modelStack.pop(); if (!(element instanceof RubyType)) { IdeLog.logError(RubyCorePlugin.getDefault(), "Expected type decl on top of stack, but was: " + element); //$NON-NLS-1$ } element.setLocation(element.getStartingOffset(), endOffset + 1); } public void exitBlock(int endOffset) { if (modelStack.isEmpty()) { IdeLog.logError(RubyCorePlugin.getDefault(), "AST stack was empty upon exiting block, but should have contained the block."); //$NON-NLS-1$ return; } RubyElement element = modelStack.pop(); if (!(element instanceof RubyBlock)) { IdeLog.logError(RubyCorePlugin.getDefault(), "Expected block on top of stack, but was: " + element); //$NON-NLS-1$ } element.setLocation(element.getStartingOffset(), endOffset); } private static IRubyElement findChild(RubyElement parent, int type, String name) { IRubyElement[] elements = parent.getChildrenOfType(type); for (IRubyElement element : elements) { if (element.getName().equals(name)) { return element; } } return null; } private RubyElement getCurrentType() { List<RubyElement> extras = new ArrayList<RubyElement>(); RubyElement element = null; if (!modelStack.isEmpty()) { element = modelStack.peek(); while (!(element instanceof RubyType)) { extras.add(modelStack.pop()); if (modelStack.isEmpty()) { break; } element = modelStack.peek(); } } // needs to reverse the extras list before pushing the elements back onto the stack Collections.reverse(extras); for (RubyElement extra : extras) { modelStack.push(extra); } if (element == null) { return script; } return element; } private static boolean hasChild(RubyElement parent, IRubyElement child) { IParseNode[] nodes = parent.getChildren(); IRubyElement element; for (IParseNode node : nodes) { element = (IRubyElement) node; if (element.getName().equals(child.getName()) && element.getNodeType() == child.getNodeType()) { return true; } } return false; } }