001    /*
002     $Id: ModuleNode.java,v 1.27 2005/05/27 10:13:08 russel 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.Script;
049    import groovy.lang.Binding;
050    
051    import java.io.File;
052    import java.util.ArrayList;
053    import java.util.HashMap;
054    import java.util.Iterator;
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.27 $
077     */
078    public class ModuleNode extends ASTNode implements Opcodes {
079    
080        private BlockStatement statementBlock = new BlockStatement();
081        List classes = new ArrayList();
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 SourceUnit getContext() {
181            return context;
182        }
183    
184        /**
185         * @return the underlying character stream description
186         */
187        public String getDescription() {
188            if( context != null )
189            {
190                return context.getName();
191            }
192            else
193            {
194                return this.description;
195            }
196        }
197    
198        public void setDescription(String description) {
199            // DEPRECATED -- context.getName() is now sufficient
200            this.description = description;
201        }
202    
203        public CompileUnit getUnit() {
204            return unit;
205        }
206    
207        void setUnit(CompileUnit unit) {
208            this.unit = unit;
209        }
210    
211        protected ClassNode createStatementsClass() {
212            String name = getPackageName();
213            if (name == null) {
214                name = "";
215            }
216            else {
217                name = name + ".";
218            }
219            // now lets use the file name to determine the class name
220            if (getDescription() == null) {
221                throw new RuntimeException("Cannot generate main(String[]) class for statements when we have no file description");
222            }
223            name += extractClassFromFileDescription();
224    
225            String baseClass = null;
226            if (unit != null) {
227                baseClass = unit.getConfig().getScriptBaseClass();
228            }
229            if (baseClass == null) {
230                baseClass = Script.class.getName();
231            }
232            ClassNode classNode = new ClassNode(name, ACC_PUBLIC, baseClass);
233            classNode.setScript(true);
234    
235            // return new Foo(new ShellContext(args)).run()
236            classNode.addMethod(
237                new MethodNode(
238                    "main",
239                    ACC_PUBLIC | ACC_STATIC,
240                    "void",
241                    new Parameter[] { new Parameter("java.lang.String[]", "args")},
242                    new ExpressionStatement(
243                        new MethodCallExpression(
244                            new ClassExpression(InvokerHelper.class.getName()),
245                            "runScript",
246                            new ArgumentListExpression(
247                                new Expression[] {
248                                    new ClassExpression(classNode.getName()),
249                                    new VariableExpression("args")})))));
250    
251            classNode.addMethod(
252                new MethodNode("run", ACC_PUBLIC, Object.class.getName(), Parameter.EMPTY_ARRAY, statementBlock));
253    
254            classNode.addConstructor(ACC_PUBLIC, Parameter.EMPTY_ARRAY, new BlockStatement());
255            Statement stmt = new ExpressionStatement(
256                            new MethodCallExpression(
257                                new VariableExpression("super"),
258                                            "setBinding",
259                                            new ArgumentListExpression(
260                                        new Expression[] {
261                                            new VariableExpression("context")})));
262    
263            classNode.addConstructor(
264                ACC_PUBLIC,
265                new Parameter[] { new Parameter(Binding.class.getName(), "context")},
266                            stmt);
267    
268            for (Iterator iter = methods.iterator(); iter.hasNext();) {
269                MethodNode node = (MethodNode) iter.next();
270                int modifiers = node.getModifiers();
271                if ((modifiers & ACC_ABSTRACT) != 0) {
272                    throw new RuntimeException(
273                        "Cannot use abstract methods in a script, they are only available inside classes. Method: "
274                            + node.getName());
275                }
276                // br: the old logic seems to add static to all def f().... in a script, which makes enclosing
277                // inner classes (including closures) in a def function difficult. Comment it out.
278                node.setModifiers(modifiers /*| ACC_STATIC*/);
279    
280                classNode.addMethod(node);
281            }
282            return classNode;
283        }
284    
285        protected String extractClassFromFileDescription() {
286            // lets strip off everything after the last .
287            String answer = getDescription();
288            int idx = answer.lastIndexOf('.');
289            if (idx > 0) {
290                answer = answer.substring(0, idx);
291            }
292            // new lets trip the path separators
293            idx = answer.lastIndexOf('/');
294            if (idx >= 0) {
295                answer = answer.substring(idx + 1);
296            }
297            idx = answer.lastIndexOf(File.separatorChar);
298            if (idx >= 0) {
299                answer = answer.substring(idx + 1);
300            }
301            return answer;
302        }
303    
304        public boolean isEmpty() {
305            return classes.isEmpty() && statementBlock.getStatements().isEmpty();
306        }
307    
308    }