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 }