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    
095        public ModuleNode (SourceUnit context ) {
096            this.context = context;
097        }
098    
099        public ModuleNode (CompileUnit unit) {
100            this.unit = unit;
101        }
102    
103        public BlockStatement getStatementBlock() {
104            return statementBlock;
105        }
106    
107        public List getMethods() {
108            return methods;
109        }
110    
111        public List getClasses() {
112            if (createClassForStatements && (!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            {
196                return context.getName();
197            }
198            else
199            {
200                return this.description;
201            }
202        }
203    
204        public void setDescription(String description) {
205            // DEPRECATED -- context.getName() is now sufficient
206            this.description = description;
207        }
208    
209        public CompileUnit getUnit() {
210            return unit;
211        }
212    
213        void setUnit(CompileUnit unit) {
214            this.unit = unit;
215        }
216    
217        protected ClassNode createStatementsClass() {
218            String name = getPackageName();
219            if (name == null) {
220                name = "";
221            }
222            // now lets use the file name to determine the class name
223            if (getDescription() == null) {
224                throw new RuntimeException("Cannot generate main(String[]) class for statements when we have no file description");
225            }
226            name += extractClassFromFileDescription();
227    
228            String baseClassName = null;
229            if (unit != null) baseClassName = unit.getConfig().getScriptBaseClass();
230            ClassNode baseClass = null;
231            if (baseClassName!=null) {
232                baseClass = ClassHelper.make(baseClassName);
233            }
234            if (baseClass == null) {
235                baseClass = ClassHelper.SCRIPT_TYPE;
236            }
237            ClassNode classNode = new ClassNode(name, ACC_PUBLIC, baseClass);
238            classNode.setScript(true);
239            classNode.setScriptBody(true);
240    
241            // return new Foo(new ShellContext(args)).run()
242            classNode.addMethod(
243                new MethodNode(
244                    "main",
245                    ACC_PUBLIC | ACC_STATIC,
246                    ClassHelper.VOID_TYPE,
247                    new Parameter[] { new Parameter(ClassHelper.STRING_TYPE.makeArray(), "args")},
248                    ClassNode.EMPTY_ARRAY,
249                    new ExpressionStatement(
250                        new MethodCallExpression(
251                            new ClassExpression(ClassHelper.make(InvokerHelper.class)),
252                            "runScript",
253                            new ArgumentListExpression(
254                                new Expression[] {
255                                    new ClassExpression(classNode),
256                                    new VariableExpression("args")})))));
257    
258            classNode.addMethod(
259                new MethodNode("run", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, statementBlock));
260    
261            classNode.addConstructor(ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement());
262            Statement stmt = new ExpressionStatement(
263                            new MethodCallExpression(
264                                new VariableExpression("super"),
265                                            "setBinding",
266                                            new ArgumentListExpression(
267                                        new Expression[] {
268                                            new VariableExpression("context")})));
269    
270            classNode.addConstructor(
271                ACC_PUBLIC,
272                new Parameter[] { new Parameter(ClassHelper.make(Binding.class), "context")},
273                            ClassNode.EMPTY_ARRAY,
274                stmt);
275    
276            for (Iterator iter = methods.iterator(); iter.hasNext();) {
277                MethodNode node = (MethodNode) iter.next();
278                int modifiers = node.getModifiers();
279                if ((modifiers & ACC_ABSTRACT) != 0) {
280                    throw new RuntimeException(
281                        "Cannot use abstract methods in a script, they are only available inside classes. Method: "
282                            + node.getName());
283                }
284                // br: the old logic seems to add static to all def f().... in a script, which makes enclosing
285                // inner classes (including closures) in a def function difficult. Comment it out.
286                node.setModifiers(modifiers /*| ACC_STATIC*/);
287    
288                classNode.addMethod(node);
289            }
290            return classNode;
291        }
292    
293        protected String extractClassFromFileDescription() {
294            // lets strip off everything after the last .
295            String answer = getDescription();
296            int idx = answer.lastIndexOf('.');
297            if (idx > 0) {
298                answer = answer.substring(0, idx);
299            }
300            // new lets trip the path separators
301            idx = answer.lastIndexOf('/');
302            if (idx >= 0) {
303                answer = answer.substring(idx + 1);
304            }
305            idx = answer.lastIndexOf(File.separatorChar);
306            if (idx >= 0) {
307                answer = answer.substring(idx + 1);
308            }
309            return answer;
310        }
311    
312        public boolean isEmpty() {
313            return classes.isEmpty() && statementBlock.getStatements().isEmpty();
314        }
315        
316        public void sortClasses(){
317            if (isEmpty()) return;
318            List classes = getClasses();
319            LinkedList sorted = new LinkedList();
320            int level=1;
321            while (!classes.isEmpty()) {
322                    for (Iterator cni = classes.iterator(); cni.hasNext();) {
323                                    ClassNode cn = (ClassNode) cni.next();
324                                    ClassNode sn = cn;
325                                    for (int i=0; sn!=null && i<level; i++) sn = sn.getSuperClass();
326                                    if (sn!=null && sn.isPrimaryClassNode()) 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    }