001: /*
002: $Id: ModuleNode.java 3841 2006-06-15 20:42:01Z blackdrag $
003:
004: Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005:
006: Redistribution and use of this software and associated documentation
007: ("Software"), with or without modification, are permitted provided
008: that the following conditions are met:
009:
010: 1. Redistributions of source code must retain copyright
011: statements and notices. Redistributions must also contain a
012: copy of this document.
013:
014: 2. Redistributions in binary form must reproduce the
015: above copyright notice, this list of conditions and the
016: following disclaimer in the documentation and/or other
017: materials provided with the distribution.
018:
019: 3. The name "groovy" must not be used to endorse or promote
020: products derived from this Software without prior written
021: permission of The Codehaus. For written permission,
022: please contact info@codehaus.org.
023:
024: 4. Products derived from this Software may not be called "groovy"
025: nor may "groovy" appear in their names without prior written
026: permission of The Codehaus. "groovy" is a registered
027: trademark of The Codehaus.
028:
029: 5. Due credit should be given to The Codehaus -
030: http://groovy.codehaus.org/
031:
032: THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033: ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034: NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036: THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043: OF THE POSSIBILITY OF SUCH DAMAGE.
044:
045: */
046: package org.codehaus.groovy.ast;
047:
048: import groovy.lang.Binding;
049:
050: import java.io.File;
051: import java.util.ArrayList;
052: import java.util.HashMap;
053: import java.util.Iterator;
054: import java.util.LinkedList;
055: import java.util.List;
056: import java.util.Map;
057:
058: import org.codehaus.groovy.ast.expr.ArgumentListExpression;
059: import org.codehaus.groovy.ast.expr.ClassExpression;
060: import org.codehaus.groovy.ast.expr.Expression;
061: import org.codehaus.groovy.ast.expr.MethodCallExpression;
062: import org.codehaus.groovy.ast.expr.VariableExpression;
063: import org.codehaus.groovy.ast.stmt.BlockStatement;
064: import org.codehaus.groovy.ast.stmt.ExpressionStatement;
065: import org.codehaus.groovy.ast.stmt.Statement;
066: import org.codehaus.groovy.control.SourceUnit;
067: import org.codehaus.groovy.runtime.InvokerHelper;
068: import org.objectweb.asm.Opcodes;
069:
070: /**
071: * Represents a module, which consists typically of a class declaration
072: * but could include some imports, some statements and multiple classes
073: * intermixed with statements like scripts in Python or Ruby
074: *
075: * @author Jochen Theodorou
076: * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
077: * @version $Revision: 3841 $
078: */
079: public class ModuleNode extends ASTNode implements Opcodes {
080:
081: private BlockStatement statementBlock = new BlockStatement();
082: List classes = new LinkedList();
083: private List methods = new ArrayList();
084: private List imports = new ArrayList();
085: private List importPackages = new ArrayList();
086: private Map importIndex = new HashMap();
087: private CompileUnit unit;
088: private String packageName;
089: private String description;
090: private boolean createClassForStatements = true;
091: private transient SourceUnit context;
092: private boolean importsResolved = false;
093:
094: public ModuleNode(SourceUnit context) {
095: this .context = context;
096: }
097:
098: public ModuleNode(CompileUnit unit) {
099: this .unit = unit;
100: }
101:
102: public BlockStatement getStatementBlock() {
103: return statementBlock;
104: }
105:
106: public List getMethods() {
107: return methods;
108: }
109:
110: public List getClasses() {
111: if (createClassForStatements
112: && (!statementBlock.isEmpty() || !methods.isEmpty())) {
113: ClassNode mainClass = createStatementsClass();
114: createClassForStatements = false;
115: classes.add(0, mainClass);
116: mainClass.setModule(this );
117: addToCompileUnit(mainClass);
118: }
119: return classes;
120: }
121:
122: public List getImports() {
123: return imports;
124: }
125:
126: public List getImportPackages() {
127: return importPackages;
128: }
129:
130: /**
131: * @return the class name for the given alias or null if none is available
132: */
133: public ClassNode getImport(String alias) {
134: return (ClassNode) importIndex.get(alias);
135: }
136:
137: public void addImport(String alias, ClassNode type) {
138: imports.add(new ImportNode(type, alias));
139: importIndex.put(alias, type);
140: }
141:
142: public String[] addImportPackage(String packageName) {
143: importPackages.add(packageName);
144: return new String[] { /* class names, not qualified */};
145: }
146:
147: public void addStatement(Statement node) {
148: statementBlock.addStatement(node);
149: }
150:
151: public void addClass(ClassNode node) {
152: classes.add(node);
153: node.setModule(this );
154: addToCompileUnit(node);
155: }
156:
157: /**
158: * @param node
159: */
160: private void addToCompileUnit(ClassNode node) {
161: // register the new class with the compile unit
162: if (unit != null) {
163: unit.addClass(node);
164: }
165: }
166:
167: public void addMethod(MethodNode node) {
168: methods.add(node);
169: }
170:
171: public void visit(GroovyCodeVisitor visitor) {
172: }
173:
174: public String getPackageName() {
175: return packageName;
176: }
177:
178: public void setPackageName(String packageName) {
179: this .packageName = packageName;
180: }
181:
182: public boolean hasPackageName() {
183: return this .packageName != null;
184: }
185:
186: public SourceUnit getContext() {
187: return context;
188: }
189:
190: /**
191: * @return the underlying character stream description
192: */
193: public String getDescription() {
194: if (context != null) {
195: return context.getName();
196: } else {
197: return this .description;
198: }
199: }
200:
201: public void setDescription(String description) {
202: // DEPRECATED -- context.getName() is now sufficient
203: this .description = description;
204: }
205:
206: public CompileUnit getUnit() {
207: return unit;
208: }
209:
210: void setUnit(CompileUnit unit) {
211: this .unit = unit;
212: }
213:
214: protected ClassNode createStatementsClass() {
215: String name = getPackageName();
216: if (name == null) {
217: name = "";
218: }
219: // now lets use the file name to determine the class name
220: if (getDescription() == null) {
221: throw new RuntimeException(
222: "Cannot generate main(String[]) class for statements when we have no file description");
223: }
224: name += extractClassFromFileDescription();
225:
226: String baseClassName = null;
227: if (unit != null)
228: baseClassName = unit.getConfig().getScriptBaseClass();
229: ClassNode baseClass = null;
230: if (baseClassName != null) {
231: baseClass = ClassHelper.make(baseClassName);
232: }
233: if (baseClass == null) {
234: baseClass = ClassHelper.SCRIPT_TYPE;
235: }
236: ClassNode classNode = new ClassNode(name, ACC_PUBLIC, baseClass);
237: classNode.setScript(true);
238: classNode.setScriptBody(true);
239:
240: // return new Foo(new ShellContext(args)).run()
241: classNode.addMethod(new MethodNode("main", ACC_PUBLIC
242: | ACC_STATIC, ClassHelper.VOID_TYPE,
243: new Parameter[] { new Parameter(ClassHelper.STRING_TYPE
244: .makeArray(), "args") }, ClassNode.EMPTY_ARRAY,
245: new ExpressionStatement(new MethodCallExpression(
246: new ClassExpression(ClassHelper
247: .make(InvokerHelper.class)),
248: "runScript",
249: new ArgumentListExpression(new Expression[] {
250: new ClassExpression(classNode),
251: new VariableExpression("args") })))));
252:
253: classNode.addMethod(new MethodNode("run", ACC_PUBLIC,
254: ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY,
255: ClassNode.EMPTY_ARRAY, statementBlock));
256:
257: classNode.addConstructor(ACC_PUBLIC, Parameter.EMPTY_ARRAY,
258: ClassNode.EMPTY_ARRAY, new BlockStatement());
259: Statement stmt = new ExpressionStatement(
260: new MethodCallExpression(
261: new VariableExpression("super"),
262: "setBinding",
263: new ArgumentListExpression(
264: new Expression[] { new VariableExpression(
265: "context") })));
266:
267: classNode.addConstructor(ACC_PUBLIC,
268: new Parameter[] { new Parameter(ClassHelper
269: .make(Binding.class), "context") },
270: ClassNode.EMPTY_ARRAY, stmt);
271:
272: for (Iterator iter = methods.iterator(); iter.hasNext();) {
273: MethodNode node = (MethodNode) iter.next();
274: int modifiers = node.getModifiers();
275: if ((modifiers & ACC_ABSTRACT) != 0) {
276: throw new RuntimeException(
277: "Cannot use abstract methods in a script, they are only available inside classes. Method: "
278: + node.getName());
279: }
280: // br: the old logic seems to add static to all def f().... in a script, which makes enclosing
281: // inner classes (including closures) in a def function difficult. Comment it out.
282: node.setModifiers(modifiers /*| ACC_STATIC*/);
283:
284: classNode.addMethod(node);
285: }
286: return classNode;
287: }
288:
289: protected String extractClassFromFileDescription() {
290: // lets strip off everything after the last .
291: String answer = getDescription();
292: int idx = answer.lastIndexOf('.');
293: if (idx > 0) {
294: answer = answer.substring(0, idx);
295: }
296: // new lets trip the path separators
297: idx = answer.lastIndexOf('/');
298: if (idx >= 0) {
299: answer = answer.substring(idx + 1);
300: }
301: idx = answer.lastIndexOf(File.separatorChar);
302: if (idx >= 0) {
303: answer = answer.substring(idx + 1);
304: }
305: return answer;
306: }
307:
308: public boolean isEmpty() {
309: return classes.isEmpty()
310: && statementBlock.getStatements().isEmpty();
311: }
312:
313: public void sortClasses() {
314: if (isEmpty())
315: return;
316: List classes = getClasses();
317: LinkedList sorted = new LinkedList();
318: int level = 1;
319: while (!classes.isEmpty()) {
320: for (Iterator cni = classes.iterator(); cni.hasNext();) {
321: ClassNode cn = (ClassNode) cni.next();
322: ClassNode sn = cn;
323: for (int i = 0; sn != null && i < level; i++)
324: sn = sn.getSuperClass();
325: if (sn != null && sn.isPrimaryClassNode())
326: continue;
327: cni.remove();
328: sorted.addLast(cn);
329: }
330: level++;
331: }
332: this .classes = sorted;
333: }
334:
335: public boolean hasImportsResolved() {
336: return importsResolved;
337: }
338:
339: public void setImportsResolved(boolean importsResolved) {
340: this.importsResolved = importsResolved;
341: }
342:
343: }
|