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    }