blob: febb1fdef7e0403931c6ea1b1058bd9b51368e54 [file] [log] [blame]
/*
* Copyright 2003-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.ast;
import groovy.lang.Binding;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.objectweb.asm.Opcodes;
import java.io.File;
import java.util.*;
/**
* Represents a module, which consists typically of a class declaration
* but could include some imports, some statements and multiple classes
* intermixed with statements like scripts in Python or Ruby
*
* @author Jochen Theodorou
* @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
* @version $Revision$
*/
public class ModuleNode extends ASTNode implements Opcodes {
private BlockStatement statementBlock = new BlockStatement();
List classes = new LinkedList();
private List methods = new ArrayList();
private List imports = new ArrayList();
private List importPackages = new ArrayList();
private Map importIndex = new HashMap();
private Map staticImportAliases = new HashMap();
private Map staticImportFields = new LinkedHashMap();
private Map staticImportClasses = new LinkedHashMap();
private CompileUnit unit;
private String packageName;
private String description;
private boolean createClassForStatements = true;
private transient SourceUnit context;
private boolean importsResolved = false;
public ModuleNode (SourceUnit context ) {
this.context = context;
}
public ModuleNode (CompileUnit unit) {
this.unit = unit;
}
public BlockStatement getStatementBlock() {
return statementBlock;
}
public List getMethods() {
return methods;
}
public List getClasses() {
if (createClassForStatements && (!statementBlock.isEmpty() || !methods.isEmpty())) {
ClassNode mainClass = createStatementsClass();
createClassForStatements = false;
classes.add(0, mainClass);
mainClass.setModule(this);
addToCompileUnit(mainClass);
}
return classes;
}
public List getImports() {
return imports;
}
public List getImportPackages() {
return importPackages;
}
/**
* @return the class name for the given alias or null if none is available
*/
public ClassNode getImport(String alias) {
return (ClassNode) importIndex.get(alias);
}
public void addImport(String alias, ClassNode type) {
imports.add(new ImportNode(type, alias));
importIndex.put(alias, type);
}
public String[] addImportPackage(String packageName) {
importPackages.add(packageName);
return new String[] { /* class names, not qualified */ };
}
public void addStatement(Statement node) {
statementBlock.addStatement(node);
}
public void addClass(ClassNode node) {
classes.add(node);
node.setModule(this);
addToCompileUnit(node);
}
/**
* @param node
*/
private void addToCompileUnit(ClassNode node) {
// register the new class with the compile unit
if (unit != null) {
unit.addClass(node);
}
}
public void addMethod(MethodNode node) {
methods.add(node);
}
public void visit(GroovyCodeVisitor visitor) {
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public boolean hasPackageName(){
return this.packageName != null;
}
public SourceUnit getContext() {
return context;
}
/**
* @return the underlying character stream description
*/
public String getDescription() {
if( context != null )
{
return context.getName();
}
else
{
return this.description;
}
}
public void setDescription(String description) {
// DEPRECATED -- context.getName() is now sufficient
this.description = description;
}
public CompileUnit getUnit() {
return unit;
}
void setUnit(CompileUnit unit) {
this.unit = unit;
}
protected ClassNode createStatementsClass() {
String name = getPackageName();
if (name == null) {
name = "";
}
// now lets use the file name to determine the class name
if (getDescription() == null) {
throw new RuntimeException("Cannot generate main(String[]) class for statements when we have no file description");
}
name += extractClassFromFileDescription();
String baseClassName = null;
if (unit != null) baseClassName = unit.getConfig().getScriptBaseClass();
ClassNode baseClass = null;
if (baseClassName!=null) {
baseClass = ClassHelper.make(baseClassName);
}
if (baseClass == null) {
baseClass = ClassHelper.SCRIPT_TYPE;
}
ClassNode classNode = new ClassNode(name, ACC_PUBLIC, baseClass);
classNode.setScript(true);
classNode.setScriptBody(true);
// return new Foo(new ShellContext(args)).run()
classNode.addMethod(
new MethodNode(
"main",
ACC_PUBLIC | ACC_STATIC,
ClassHelper.VOID_TYPE,
new Parameter[] { new Parameter(ClassHelper.STRING_TYPE.makeArray(), "args")},
ClassNode.EMPTY_ARRAY,
new ExpressionStatement(
new MethodCallExpression(
new ClassExpression(ClassHelper.make(InvokerHelper.class)),
"runScript",
new ArgumentListExpression(
new ClassExpression(classNode),
new VariableExpression("args"))))));
classNode.addMethod(
new MethodNode("run", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, statementBlock));
classNode.addConstructor(ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement());
Statement stmt = new ExpressionStatement(
new MethodCallExpression(
new VariableExpression("super"),
"setBinding",
new ArgumentListExpression(
new VariableExpression("context"))));
classNode.addConstructor(
ACC_PUBLIC,
new Parameter[] { new Parameter(ClassHelper.make(Binding.class), "context")},
ClassNode.EMPTY_ARRAY,
stmt);
for (Iterator iter = methods.iterator(); iter.hasNext();) {
MethodNode node = (MethodNode) iter.next();
int modifiers = node.getModifiers();
if ((modifiers & ACC_ABSTRACT) != 0) {
throw new RuntimeException(
"Cannot use abstract methods in a script, they are only available inside classes. Method: "
+ node.getName());
}
// br: the old logic seems to add static to all def f().... in a script, which makes enclosing
// inner classes (including closures) in a def function difficult. Comment it out.
node.setModifiers(modifiers /*| ACC_STATIC*/);
classNode.addMethod(node);
}
return classNode;
}
protected String extractClassFromFileDescription() {
// lets strip off everything after the last .
String answer = getDescription();
int idx = answer.lastIndexOf('.');
if (idx > 0) {
answer = answer.substring(0, idx);
}
// new lets trip the path separators
idx = answer.lastIndexOf('/');
if (idx >= 0) {
answer = answer.substring(idx + 1);
}
idx = answer.lastIndexOf(File.separatorChar);
if (idx >= 0) {
answer = answer.substring(idx + 1);
}
return answer;
}
public boolean isEmpty() {
return classes.isEmpty() && statementBlock.getStatements().isEmpty();
}
public void sortClasses(){
if (isEmpty()) return;
List classes = getClasses();
LinkedList sorted = new LinkedList();
int level=1;
while (!classes.isEmpty()) {
for (Iterator cni = classes.iterator(); cni.hasNext();) {
ClassNode cn = (ClassNode) cni.next();
ClassNode sn = cn;
for (int i=0; sn!=null && i<level; i++) sn = sn.getSuperClass();
if (sn!=null && sn.isPrimaryClassNode()) continue;
cni.remove();
sorted.addLast(cn);
}
level++;
}
this.classes = sorted;
}
public boolean hasImportsResolved() {
return importsResolved;
}
public void setImportsResolved(boolean importsResolved) {
this.importsResolved = importsResolved;
}
public Map getStaticImportAliases() {
return staticImportAliases;
}
public Map getStaticImportClasses() {
return staticImportClasses;
}
public Map getStaticImportFields() {
return staticImportFields;
}
public void addStaticMethodOrField(ClassNode type, String fieldName, String alias) {
staticImportAliases.put(alias, type);
staticImportFields.put(alias, fieldName);
}
public void addStaticImportClass(String name, ClassNode type) {
staticImportClasses.put(name, type);
}
}