001 /*
002 $Id: ModuleNode.java,v 1.29 2005/11/13 16:42:09 blackdrag Exp $
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 <a href="mailto:james@coredevelopers.net">James Strachan</a>
076 * @version $Revision: 1.29 $
077 */
078 public class ModuleNode extends ASTNode implements Opcodes {
079
080 private BlockStatement statementBlock = new BlockStatement();
081 List classes = new LinkedList();
082 private List methods = new ArrayList();
083 private List imports = new ArrayList();
084 private List importPackages = new ArrayList();
085 private Map importIndex = new HashMap();
086 private CompileUnit unit;
087 private String packageName;
088 private String description;
089 private boolean createClassForStatements = true;
090 private transient SourceUnit context;
091
092
093 public ModuleNode (SourceUnit context ) {
094 this.context = context;
095 }
096
097 public ModuleNode (CompileUnit unit) {
098 this.unit = unit;
099 }
100
101 public BlockStatement getStatementBlock() {
102 return statementBlock;
103 }
104
105 public List getMethods() {
106 return methods;
107 }
108
109 public List getClasses() {
110 if (createClassForStatements && (!statementBlock.isEmpty() || !methods.isEmpty())) {
111 ClassNode mainClass = createStatementsClass();
112 createClassForStatements = false;
113 classes.add(0, mainClass);
114 mainClass.setModule(this);
115 addToCompileUnit(mainClass);
116 }
117 return classes;
118 }
119
120 public List getImports() {
121 return imports;
122 }
123
124 public List getImportPackages() {
125 return importPackages;
126 }
127
128 /**
129 * @return the class name for the given alias or null if none is available
130 */
131 public String getImport(String alias) {
132 return (String) importIndex.get(alias);
133 }
134
135 public void addImport(String alias, String className) {
136 imports.add(new ImportNode(className, alias));
137 importIndex.put(alias, className);
138 }
139
140 public String[] addImportPackage(String packageName) {
141 importPackages.add(packageName);
142 return new String[] { /* class names, not qualified */ };
143 }
144
145 public void addStatement(Statement node) {
146 statementBlock.addStatement(node);
147 }
148
149 public void addClass(ClassNode node) {
150 classes.add(node);
151 node.setModule(this);
152 addToCompileUnit(node);
153 }
154
155 /**
156 * @param node
157 */
158 private void addToCompileUnit(ClassNode node) {
159 // register the new class with the compile unit
160 if (unit != null) {
161 unit.addClass(node);
162 }
163 }
164
165 public void addMethod(MethodNode node) {
166 methods.add(node);
167 }
168
169 public void visit(GroovyCodeVisitor visitor) {
170 }
171
172 public String getPackageName() {
173 return packageName;
174 }
175
176 public void setPackageName(String packageName) {
177 this.packageName = packageName;
178 }
179
180 public boolean hasPackageName(){
181 return this.packageName != null;
182 }
183
184 public SourceUnit getContext() {
185 return context;
186 }
187
188 /**
189 * @return the underlying character stream description
190 */
191 public String getDescription() {
192 if( context != null )
193 {
194 return context.getName();
195 }
196 else
197 {
198 return this.description;
199 }
200 }
201
202 public void setDescription(String description) {
203 // DEPRECATED -- context.getName() is now sufficient
204 this.description = description;
205 }
206
207 public CompileUnit getUnit() {
208 return unit;
209 }
210
211 void setUnit(CompileUnit unit) {
212 this.unit = unit;
213 }
214
215 protected ClassNode createStatementsClass() {
216 String name = getPackageName();
217 if (name == null) {
218 name = "";
219 }
220 // now lets use the file name to determine the class name
221 if (getDescription() == null) {
222 throw new RuntimeException("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) baseClassName = unit.getConfig().getScriptBaseClass();
228 ClassNode baseClass = null;
229 if (baseClassName!=null) {
230 baseClass = ClassHelper.make(baseClassName);
231 }
232 if (baseClass == null) {
233 baseClass = ClassHelper.SCRIPT_TYPE;
234 }
235 ClassNode classNode = new ClassNode(name, ACC_PUBLIC, baseClass);
236 classNode.setScript(true);
237
238 // return new Foo(new ShellContext(args)).run()
239 classNode.addMethod(
240 new MethodNode(
241 "main",
242 ACC_PUBLIC | ACC_STATIC,
243 ClassHelper.VOID_TYPE,
244 new Parameter[] { new Parameter(ClassHelper.STRING_TYPE.makeArray(), "args")},
245 new ExpressionStatement(
246 new MethodCallExpression(
247 new ClassExpression(ClassHelper.make(InvokerHelper.class)),
248 "runScript",
249 new ArgumentListExpression(
250 new Expression[] {
251 new ClassExpression(classNode),
252 new VariableExpression("args")})))));
253
254 classNode.addMethod(
255 new MethodNode("run", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, statementBlock));
256
257 classNode.addConstructor(ACC_PUBLIC, Parameter.EMPTY_ARRAY, new BlockStatement());
258 Statement stmt = new ExpressionStatement(
259 new MethodCallExpression(
260 new VariableExpression("super"),
261 "setBinding",
262 new ArgumentListExpression(
263 new Expression[] {
264 new VariableExpression("context")})));
265
266 classNode.addConstructor(
267 ACC_PUBLIC,
268 new Parameter[] { new Parameter(ClassHelper.make(Binding.class), "context")},
269 stmt);
270
271 for (Iterator iter = methods.iterator(); iter.hasNext();) {
272 MethodNode node = (MethodNode) iter.next();
273 int modifiers = node.getModifiers();
274 if ((modifiers & ACC_ABSTRACT) != 0) {
275 throw new RuntimeException(
276 "Cannot use abstract methods in a script, they are only available inside classes. Method: "
277 + node.getName());
278 }
279 // br: the old logic seems to add static to all def f().... in a script, which makes enclosing
280 // inner classes (including closures) in a def function difficult. Comment it out.
281 node.setModifiers(modifiers /*| ACC_STATIC*/);
282
283 classNode.addMethod(node);
284 }
285 return classNode;
286 }
287
288 protected String extractClassFromFileDescription() {
289 // lets strip off everything after the last .
290 String answer = getDescription();
291 int idx = answer.lastIndexOf('.');
292 if (idx > 0) {
293 answer = answer.substring(0, idx);
294 }
295 // new lets trip the path separators
296 idx = answer.lastIndexOf('/');
297 if (idx >= 0) {
298 answer = answer.substring(idx + 1);
299 }
300 idx = answer.lastIndexOf(File.separatorChar);
301 if (idx >= 0) {
302 answer = answer.substring(idx + 1);
303 }
304 return answer;
305 }
306
307 public boolean isEmpty() {
308 return classes.isEmpty() && statementBlock.getStatements().isEmpty();
309 }
310
311 public void sortClasses(){
312 if (isEmpty()) return;
313 List classes = getClasses();
314 LinkedList sorted = new LinkedList();
315 int level=1;
316 while (!classes.isEmpty()) {
317 for (Iterator cni = classes.iterator(); cni.hasNext();) {
318 ClassNode cn = (ClassNode) cni.next();
319 ClassNode sn = cn;
320 for (int i=0; sn!=null && i<level; i++) sn = sn.getSuperClass();
321 if (sn!=null && sn.isPrimaryClassNode()) continue;
322 cni.remove();
323 sorted.addLast(cn);
324 }
325 level++;
326 }
327 this.classes = sorted;
328 }
329
330 }