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 }