View Javadoc

1   /*
2    $Id: Verifier.java,v 1.40 2005/06/10 09:55:31 cstein Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package org.codehaus.groovy.classgen;
47  
48  import groovy.lang.Closure;
49  import groovy.lang.GString;
50  import groovy.lang.GroovyObject;
51  import groovy.lang.MetaClass;
52  
53  import java.lang.reflect.Modifier;
54  import java.util.ArrayList;
55  import java.util.Iterator;
56  import java.util.List;
57  
58  import org.codehaus.groovy.ast.ClassNode;
59  import org.codehaus.groovy.ast.CodeVisitorSupport;
60  import org.codehaus.groovy.ast.ConstructorNode;
61  import org.codehaus.groovy.ast.FieldNode;
62  import org.codehaus.groovy.ast.GroovyClassVisitor;
63  import org.codehaus.groovy.ast.InnerClassNode;
64  import org.codehaus.groovy.ast.MethodNode;
65  import org.codehaus.groovy.ast.Parameter;
66  import org.codehaus.groovy.ast.PropertyNode;
67  import org.codehaus.groovy.ast.expr.ArgumentListExpression;
68  import org.codehaus.groovy.ast.expr.BinaryExpression;
69  import org.codehaus.groovy.ast.expr.BooleanExpression;
70  import org.codehaus.groovy.ast.expr.ClosureExpression;
71  import org.codehaus.groovy.ast.expr.ConstantExpression;
72  import org.codehaus.groovy.ast.expr.Expression;
73  import org.codehaus.groovy.ast.expr.FieldExpression;
74  import org.codehaus.groovy.ast.expr.MethodCallExpression;
75  import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
76  import org.codehaus.groovy.ast.expr.VariableExpression;
77  import org.codehaus.groovy.ast.stmt.BlockStatement;
78  import org.codehaus.groovy.ast.stmt.EmptyStatement;
79  import org.codehaus.groovy.ast.stmt.ExpressionStatement;
80  import org.codehaus.groovy.ast.stmt.IfStatement;
81  import org.codehaus.groovy.ast.stmt.ReturnStatement;
82  import org.codehaus.groovy.ast.stmt.Statement;
83  import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
84  import org.codehaus.groovy.syntax.Types;
85  import org.codehaus.groovy.syntax.Token;
86  import org.codehaus.groovy.syntax.RuntimeParserException;
87  import org.objectweb.asm.Opcodes;
88  
89  /***
90   * Verifies the AST node and adds any defaulted AST code before
91   * bytecode generation occurs.
92   * 
93   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
94   * @version $Revision: 1.40 $
95   */
96  public class Verifier implements GroovyClassVisitor, Opcodes {
97  
98      public static final String __TIMESTAMP = "__timeStamp";
99  	private ClassNode classNode;
100     private MethodNode methodNode;
101 
102     public ClassNode getClassNode() {
103         return classNode;
104     }
105 
106     public MethodNode getMethodNode() {
107         return methodNode;
108     }
109 
110     /***
111      * add code to implement GroovyObject
112      * @param node
113      */
114     public void visitClass(ClassNode node) {
115         this.classNode = node;
116                 
117         if ((classNode.getModifiers() & Opcodes.ACC_INTERFACE) >0) {
118             node.visitContents(this);
119             return;
120         }
121         
122         addDefaultParameterMethods(node);
123 
124         if (!node.isDerivedFromGroovyObject()) {
125             node.addInterface(GroovyObject.class.getName());
126 
127             // lets add a new field for the metaclass
128             StaticMethodCallExpression initMetaClassCall =
129                 new StaticMethodCallExpression(
130                     ScriptBytecodeAdapter.class.getName(),
131                     "getMetaClass",
132                     VariableExpression.THIS_EXPRESSION);
133 
134             PropertyNode metaClassProperty =
135                 node.addProperty("metaClass", ACC_PUBLIC, MetaClass.class.getName(), initMetaClassCall, null, null);
136             metaClassProperty.setSynthetic(true);
137             FieldNode metaClassField = metaClassProperty.getField();
138             metaClassField.setModifiers(metaClassField.getModifiers() | ACC_TRANSIENT);
139 
140             FieldExpression metaClassVar = new FieldExpression(metaClassField);
141             IfStatement initMetaClassField =
142                 new IfStatement(
143                     new BooleanExpression(
144                         new BinaryExpression(metaClassVar, Token.newSymbol( Types.COMPARE_EQUAL, -1, -1), ConstantExpression.NULL)),
145                     new ExpressionStatement(new BinaryExpression(metaClassVar, Token.newSymbol( Types.EQUAL, -1, -1), initMetaClassCall)),
146                     EmptyStatement.INSTANCE);
147 
148             node.addSyntheticMethod(
149                 "getMetaClass",
150                 ACC_PUBLIC,
151                 MetaClass.class.getName(),
152                 Parameter.EMPTY_ARRAY,
153                 new BlockStatement(new Statement[] { initMetaClassField, new ReturnStatement(metaClassVar)}));
154 
155             // @todo we should check if the base class implements the invokeMethod method
156 
157             // lets add the invokeMethod implementation
158             String superClass = node.getSuperClass();
159             boolean addDelegateObject =
160                 (node instanceof InnerClassNode && superClass.equals(Closure.class.getName()))
161                     || superClass.equals(GString.class.getName());
162 
163             // don't do anything as the base class implements the invokeMethod
164             if (!addDelegateObject) {
165                 node.addSyntheticMethod(
166                     "invokeMethod",
167                     ACC_PUBLIC,
168                     Object.class.getName(),
169                     new Parameter[] {
170                         new Parameter(String.class.getName(), "method"),
171                         new Parameter(Object.class.getName(), "arguments")},
172                     new BlockStatement(
173                         new Statement[] {
174                             initMetaClassField,
175                             new ReturnStatement(
176                                 new MethodCallExpression(
177                                     metaClassVar,
178                                     "invokeMethod",
179                                     new ArgumentListExpression(
180                                         new Expression[] {
181                                             VariableExpression.THIS_EXPRESSION,
182                                             new VariableExpression("method"),
183                                             new VariableExpression("arguments")})))
184                 }));
185 
186                 if (!node.isScript()) {
187                     node.addSyntheticMethod(
188                         "getProperty",
189                         ACC_PUBLIC,
190                         Object.class.getName(),
191                         new Parameter[] { new Parameter(String.class.getName(), "property")},
192                         new BlockStatement(
193                             new Statement[] {
194                                 initMetaClassField,
195                                 new ReturnStatement(
196                                     new MethodCallExpression(
197                                         metaClassVar,
198                                         "getProperty",
199                                         new ArgumentListExpression(
200                                             new Expression[] {
201                                                 VariableExpression.THIS_EXPRESSION,
202                                                 new VariableExpression("property")})))
203                     }));
204 
205                     node.addSyntheticMethod(
206                         "setProperty",
207                         ACC_PUBLIC,
208                         "void",
209                         new Parameter[] {
210                             new Parameter(String.class.getName(), "property"),
211                             new Parameter(Object.class.getName(), "value")},
212                         new BlockStatement(
213                             new Statement[] {
214                                 initMetaClassField,
215                                 new ExpressionStatement(
216                                     new MethodCallExpression(
217                                         metaClassVar,
218                                         "setProperty",
219                                         new ArgumentListExpression(
220                                             new Expression[] {
221                                                 VariableExpression.THIS_EXPRESSION,
222                                                 new VariableExpression("property"),
223                                                 new VariableExpression("value")})))
224                     }));
225                 }
226             }
227         }
228 
229         if (node.getDeclaredConstructors().isEmpty()) {
230             ConstructorNode constructor = new ConstructorNode(ACC_PUBLIC, null);
231             constructor.setSynthetic(true);
232             node.addConstructor(constructor);
233         }
234         
235         if (!(node instanceof InnerClassNode)) {// add a static timestamp field to the class
236             FieldNode timeTagField = new FieldNode(
237                     Verifier.__TIMESTAMP,
238                     Modifier.PUBLIC | Modifier.STATIC,
239                     "java.lang.Long",
240                     //"",
241                     node.getName(),
242                     new ConstantExpression(new Long(System.currentTimeMillis())));
243             // alternatively , FieldNode timeTagField = SourceUnit.createFieldNode("public static final long __timeStamp = " + System.currentTimeMillis() + "L");
244             timeTagField.setSynthetic(true);
245             node.addField(timeTagField);
246         }
247 
248         addFieldInitialization(node);
249 
250         node.visitContents(this);
251     }
252     public void visitConstructor(ConstructorNode node) {
253         CodeVisitorSupport checkSuper = new CodeVisitorSupport() {
254             boolean firstMethodCall = true;
255             String type=null;
256             public void visitMethodCallExpression(MethodCallExpression call) {
257                 if (!firstMethodCall) return;
258                 firstMethodCall = false;
259                 String name = call.getMethod();
260                 if (!name.equals("super") && !name.equals("this")) return;
261                 type=name;
262                 call.getArguments().visit(this);
263                 type=null;
264             }
265             public void visitVariableExpression(VariableExpression expression) {
266                 if (type==null) return;
267                 String name = expression.getVariable();
268                 if (!name.equals("this") && !name.equals("super")) return;
269                 throw new RuntimeParserException("cannot reference "+name+" inside of "+type+"(....) before supertype constructor has been called",expression);
270             }            
271         };
272         Statement s = node.getCode();
273         //todo why can a statement can be null?
274         if (s == null) return;
275         s.visit(checkSuper);
276     }
277 
278     public void visitMethod(MethodNode node) {
279         this.methodNode = node;
280         Statement statement = node.getCode();
281         if (!node.isVoidMethod()) {
282             if (statement instanceof ExpressionStatement) {
283                 ExpressionStatement expStmt = (ExpressionStatement) statement;
284                 node.setCode(new ReturnStatement(expStmt.getExpression()));
285             }
286             else if (statement instanceof BlockStatement) {
287                 BlockStatement block = (BlockStatement) statement;
288 
289                 // lets copy the list so we create a new block
290                 List list = new ArrayList(block.getStatements());
291                 if (!list.isEmpty()) {
292                     int idx = list.size() - 1;
293                     Statement last = (Statement) list.get(idx);
294                     if (last instanceof ExpressionStatement) {
295                         ExpressionStatement expStmt = (ExpressionStatement) last;
296                         list.set(idx, new ReturnStatement(expStmt.getExpression()));
297                     }
298                     else if (!(last instanceof ReturnStatement)) {
299                         list.add(new ReturnStatement(ConstantExpression.NULL));
300                     }
301                 }
302                 else {
303                     list.add(new ReturnStatement(ConstantExpression.NULL));
304                 }
305 
306                 node.setCode(new BlockStatement(filterStatements(list)));
307             }
308         }
309         else if (!node.isAbstract()) {
310         	BlockStatement newBlock = new BlockStatement();
311             if (statement instanceof BlockStatement) {
312                 newBlock.addStatements(filterStatements(((BlockStatement)statement).getStatements()));
313             }
314             else {
315                 newBlock.addStatement(filterStatement(statement));
316             }
317             newBlock.addStatement(ReturnStatement.RETURN_NULL_OR_VOID);
318             node.setCode(newBlock);
319         }
320         if (node.getName().equals("main") && node.isStatic()) {
321             Parameter[] params = node.getParameters();
322             if (params.length == 1) {
323                 Parameter param = params[0];
324                 if (param.getType() == null || param.getType().equals("java.lang.Object")) {
325                     param.setType("java.lang.String[]");
326                 }
327             }
328         }
329         statement = node.getCode();
330         if (statement!=null) statement.visit(new VerifierCodeVisitor(this));
331     }
332 
333     public void visitField(FieldNode node) {
334     }
335 
336     public void visitProperty(PropertyNode node) {
337         String name = node.getName();
338         FieldNode field = node.getField();
339 
340         
341         String getterPrefix = "get";
342         if ("boolean".equals(node.getType())) {
343             getterPrefix = "is";
344         }
345         String getterName = getterPrefix + capitalize(name);
346         String setterName = "set" + capitalize(name);
347 
348         Statement getterBlock = node.getGetterBlock();
349         if (getterBlock == null) {
350             if (!node.isPrivate() && classNode.getGetterMethod(getterName) == null) {
351                 getterBlock = createGetterBlock(node, field);
352             }
353         }
354         Statement setterBlock = node.getGetterBlock();
355         if (setterBlock == null) {
356             if (!node.isPrivate() && classNode.getSetterMethod(setterName) == null) {
357                 setterBlock = createSetterBlock(node, field);
358             }
359         }
360 
361         if (getterBlock != null) {
362             MethodNode getter =
363                 new MethodNode(getterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, getterBlock);
364             getter.setSynthetic(true);
365             classNode.addMethod(getter);
366             visitMethod(getter);
367 
368             if ("java.lang.Boolean".equals(node.getType())) {
369                 String secondGetterName = "is" + capitalize(name);
370                 MethodNode secondGetter =
371                     new MethodNode(secondGetterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, getterBlock);
372                 secondGetter.setSynthetic(true);
373                 classNode.addMethod(secondGetter);
374                 visitMethod(secondGetter);
375             }
376         }
377         if (setterBlock != null) {
378             Parameter[] setterParameterTypes = { new Parameter(node.getType(), "value")};
379             MethodNode setter =
380                 new MethodNode(setterName, node.getModifiers(), "void", setterParameterTypes, setterBlock);
381             setter.setSynthetic(true);
382             classNode.addMethod(setter);
383             visitMethod(setter);
384         }
385     }
386 
387     // Implementation methods
388     //-------------------------------------------------------------------------
389     
390     /***
391      * Creates a new helper method for each combination of default parameter expressions 
392      */
393     protected void addDefaultParameterMethods(ClassNode node) {
394         List methods = new ArrayList(node.getMethods());
395         for (Iterator iter = methods.iterator(); iter.hasNext();) {
396             MethodNode method = (MethodNode) iter.next();
397             Parameter[] parameters = method.getParameters();
398             int size = parameters.length;
399             for (int i = 0; i < size; i++) {
400                 Parameter parameter = parameters[i];
401                 Expression exp = parameter.getDefaultValue();
402                 if (exp != null) {
403                     addDefaultParameterMethod(node, method, parameters, i);
404                 }
405             }
406         }
407     }
408 
409     /***
410      * Adds a new method which defaults the values for all the parameters starting 
411      * from and including the given index
412      * 
413      * @param node the class to add the method
414      * @param method the given method to add a helper of
415      * @param parameters the parameters of the method to add a helper for
416      * @param index the index of the first default value expression parameter to use
417      */
418     protected void addDefaultParameterMethod(ClassNode node, MethodNode method, Parameter[] parameters, int index) {
419         // lets create a method using this expression
420         Parameter[] newParams = new Parameter[index];
421         System.arraycopy(parameters, 0, newParams, 0, index);
422 
423         ArgumentListExpression arguments = new ArgumentListExpression();
424         int size = parameters.length;
425         for (int i = 0; i < size; i++) {
426             if (i < index) {
427                 arguments.addExpression(new VariableExpression(parameters[i].getName()));
428             }
429             else {
430                 Expression defaultValue = parameters[i].getDefaultValue();
431                 if (defaultValue == null) {
432                     throw new RuntimeParserException(
433                         "The " + parameters[i].getName() + " parameter must have a default value",
434                         method);
435                 }
436                 else {
437                     arguments.addExpression(defaultValue);
438                 }
439             }
440         }
441 
442         MethodCallExpression expression =
443             new MethodCallExpression(VariableExpression.THIS_EXPRESSION, method.getName(), arguments);
444         Statement code = null;
445         if (method.isVoidMethod()) {
446             code = new ExpressionStatement(expression);
447             }
448         else {
449             code = new ReturnStatement(expression);
450         }
451 
452         node.addMethod(method.getName(), method.getModifiers(), method.getReturnType(), newParams, code);
453     }
454 
455     protected void addClosureCode(InnerClassNode node) {
456         // add a new invoke
457     }
458 
459     protected void addFieldInitialization(ClassNode node) {
460         for (Iterator iter = node.getDeclaredConstructors().iterator(); iter.hasNext();) {
461             addFieldInitialization(node, (ConstructorNode) iter.next());
462         }
463     }
464 
465     protected void addFieldInitialization(ClassNode node, ConstructorNode constructorNode) {
466         List statements = new ArrayList();
467         List staticStatements = new ArrayList();
468         for (Iterator iter = node.getFields().iterator(); iter.hasNext();) {
469             addFieldInitialization(statements, staticStatements, constructorNode, (FieldNode) iter.next());
470         }
471         if (!statements.isEmpty()) {
472             Statement code = constructorNode.getCode();
473             List otherStatements = new ArrayList();
474             if (code instanceof BlockStatement) {
475                 BlockStatement block = (BlockStatement) code;
476                 otherStatements.addAll(block.getStatements());
477             }
478             else if (code != null) {
479                 otherStatements.add(code);
480             }
481             if (!otherStatements.isEmpty()) {
482                 Statement first = (Statement) otherStatements.get(0);
483                 if (isSuperMethodCall(first)) {
484                     otherStatements.remove(0);
485                     statements.add(0, first);
486                 }
487                 statements.addAll(otherStatements);
488             }
489             constructorNode.setCode(new BlockStatement(statements));
490         }
491 
492         if (!staticStatements.isEmpty()) {
493             node.addStaticInitializerStatements(staticStatements);
494         }
495     }
496 
497     protected void addFieldInitialization(
498         List list,
499         List staticList,
500         ConstructorNode constructorNode,
501         FieldNode fieldNode) {
502         Expression expression = fieldNode.getInitialValueExpression();
503         if (expression != null) {
504             ExpressionStatement statement =
505                 new ExpressionStatement(
506                     new BinaryExpression(
507                         new FieldExpression(fieldNode),
508                         Token.newSymbol(Types.EQUAL, fieldNode.getLineNumber(), fieldNode.getColumnNumber()),
509                         expression));
510             if (fieldNode.isStatic()) {
511                 staticList.add(statement);
512             }
513             else {
514                 list.add(statement);
515             }
516         }
517     }
518 
519     protected boolean isSuperMethodCall(Statement first) {
520         if (first instanceof ExpressionStatement) {
521             ExpressionStatement exprStmt = (ExpressionStatement) first;
522             Expression expr = exprStmt.getExpression();
523             if (expr instanceof MethodCallExpression) {
524                 return MethodCallExpression.isSuperMethodCall((MethodCallExpression) expr);
525             }
526         }
527         return false;
528     }
529 
530     /***
531      * Capitalizes the start of the given bean property name
532      */
533     public static String capitalize(String name) {
534         return name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
535     }
536 
537     protected Statement createGetterBlock(PropertyNode propertyNode, FieldNode field) {
538         Expression expression = new FieldExpression(field);
539         return new ReturnStatement(expression);
540     }
541 
542     protected Statement createSetterBlock(PropertyNode propertyNode, FieldNode field) {
543         Expression expression = new FieldExpression(field);
544         return new ExpressionStatement(
545             new BinaryExpression(expression, Token.newSymbol(Types.EQUAL, 0, 0), new VariableExpression("value")));
546     }
547 
548     /***
549      * Filters the given statements
550      */
551     protected List filterStatements(List list) {
552         List answer = new ArrayList(list.size());
553         for (Iterator iter = list.iterator(); iter.hasNext();) {
554             answer.add(filterStatement((Statement) iter.next()));
555         }
556         return answer;
557     }
558 
559     protected Statement filterStatement(Statement statement) {
560         if (statement instanceof ExpressionStatement) {
561             ExpressionStatement expStmt = (ExpressionStatement) statement;
562             Expression expression = expStmt.getExpression();
563             if (expression instanceof ClosureExpression) {
564                 ClosureExpression closureExp = (ClosureExpression) expression;
565                 if (!closureExp.isParameterSpecified()) {
566                     return closureExp.getCode();
567                 }
568             }
569         }
570         return statement;
571     }
572 }