View Javadoc

1   /*
2    $Id: Verifier.java,v 1.32 2004/07/10 03:31:39 bran 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.ConstructorNode;
60  import org.codehaus.groovy.ast.FieldNode;
61  import org.codehaus.groovy.ast.GroovyClassVisitor;
62  import org.codehaus.groovy.ast.InnerClassNode;
63  import org.codehaus.groovy.ast.MethodNode;
64  import org.codehaus.groovy.ast.Parameter;
65  import org.codehaus.groovy.ast.PropertyNode;
66  import org.codehaus.groovy.ast.expr.ArgumentListExpression;
67  import org.codehaus.groovy.ast.expr.BinaryExpression;
68  import org.codehaus.groovy.ast.expr.BooleanExpression;
69  import org.codehaus.groovy.ast.expr.ClosureExpression;
70  import org.codehaus.groovy.ast.expr.ConstantExpression;
71  import org.codehaus.groovy.ast.expr.Expression;
72  import org.codehaus.groovy.ast.expr.FieldExpression;
73  import org.codehaus.groovy.ast.expr.MethodCallExpression;
74  import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
75  import org.codehaus.groovy.ast.expr.VariableExpression;
76  import org.codehaus.groovy.ast.stmt.BlockStatement;
77  import org.codehaus.groovy.ast.stmt.EmptyStatement;
78  import org.codehaus.groovy.ast.stmt.ExpressionStatement;
79  import org.codehaus.groovy.ast.stmt.IfStatement;
80  import org.codehaus.groovy.ast.stmt.ReturnStatement;
81  import org.codehaus.groovy.ast.stmt.Statement;
82  import org.codehaus.groovy.runtime.InvokerHelper;
83  import org.codehaus.groovy.syntax.Types;
84  import org.codehaus.groovy.syntax.Token;
85  import org.codehaus.groovy.syntax.parser.RuntimeParserException;
86  import org.objectweb.asm.Constants;
87  
88  /***
89   * Verifies the AST node and adds any defaulted AST code before
90   * bytecode generation occurs.
91   * 
92   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
93   * @version $Revision: 1.32 $
94   */
95  public class Verifier implements GroovyClassVisitor, Constants {
96  
97      public static final String __TIMESTAMP = "__timeStamp";
98  	private ClassNode classNode;
99      private MethodNode methodNode;
100 
101     public ClassNode getClassNode() {
102         return classNode;
103     }
104 
105     public MethodNode getMethodNode() {
106         return methodNode;
107     }
108 
109     /***
110      * add code to implement GroovyObject
111      * @param node
112      */
113     public void visitClass(ClassNode node) {
114         this.classNode = node;
115 
116         addDefaultParameterMethods(node);
117 
118         if (!node.isDerivedFromGroovyObject()) {
119             node.addInterface(GroovyObject.class.getName());
120 
121             // lets add a new field for the metaclass
122             StaticMethodCallExpression initMetaClassCall =
123                 new StaticMethodCallExpression(
124                     InvokerHelper.class.getName(),
125                     "getMetaClass",
126                     VariableExpression.THIS_EXPRESSION);
127 
128             PropertyNode metaClassProperty =
129                 node.addProperty("metaClass", ACC_PUBLIC, MetaClass.class.getName(), initMetaClassCall, null, null);
130             metaClassProperty.setSynthetic(true);
131             FieldNode metaClassField = metaClassProperty.getField();
132             metaClassField.setModifiers(metaClassField.getModifiers() | ACC_TRANSIENT);
133 
134             FieldExpression metaClassVar = new FieldExpression(metaClassField);
135             IfStatement initMetaClassField =
136                 new IfStatement(
137                     new BooleanExpression(
138                         new BinaryExpression(metaClassVar, Token.newSymbol( Types.COMPARE_EQUAL, -1, -1), ConstantExpression.NULL)),
139                     new ExpressionStatement(new BinaryExpression(metaClassVar, Token.newSymbol( Types.EQUAL, -1, -1), initMetaClassCall)),
140                     EmptyStatement.INSTANCE);
141 
142             node.addSyntheticMethod(
143                 "getMetaClass",
144                 ACC_PUBLIC,
145                 MetaClass.class.getName(),
146                 Parameter.EMPTY_ARRAY,
147                 new BlockStatement(new Statement[] { initMetaClassField, new ReturnStatement(metaClassVar)}));
148 
149             // @todo we should check if the base class implements the invokeMethod method
150 
151             // lets add the invokeMethod implementation
152             String superClass = node.getSuperClass();
153             boolean addDelegateObject =
154                 (node instanceof InnerClassNode && superClass.equals(Closure.class.getName()))
155                     || superClass.equals(GString.class.getName());
156 
157             // don't do anything as the base class implements the invokeMethod
158             if (!addDelegateObject) {
159                 node.addSyntheticMethod(
160                     "invokeMethod",
161                     ACC_PUBLIC,
162                     Object.class.getName(),
163                     new Parameter[] {
164                         new Parameter(String.class.getName(), "method"),
165                         new Parameter(Object.class.getName(), "arguments")},
166                     new BlockStatement(
167                         new Statement[] {
168                             initMetaClassField,
169                             new ReturnStatement(
170                                 new MethodCallExpression(
171                                     metaClassVar,
172                                     "invokeMethod",
173                                     new ArgumentListExpression(
174                                         new Expression[] {
175                                             VariableExpression.THIS_EXPRESSION,
176                                             new VariableExpression("method"),
177                                             new VariableExpression("arguments")})))
178                 }));
179 
180                 if (!node.isScript()) {
181                     node.addSyntheticMethod(
182                         "getProperty",
183                         ACC_PUBLIC,
184                         Object.class.getName(),
185                         new Parameter[] { new Parameter(String.class.getName(), "property")},
186                         new BlockStatement(
187                             new Statement[] {
188                                 initMetaClassField,
189                                 new ReturnStatement(
190                                     new MethodCallExpression(
191                                         metaClassVar,
192                                         "getProperty",
193                                         new ArgumentListExpression(
194                                             new Expression[] {
195                                                 VariableExpression.THIS_EXPRESSION,
196                                                 new VariableExpression("property")})))
197                     }));
198 
199                     node.addSyntheticMethod(
200                         "setProperty",
201                         ACC_PUBLIC,
202                         "void",
203                         new Parameter[] {
204                             new Parameter(String.class.getName(), "property"),
205                             new Parameter(Object.class.getName(), "value")},
206                         new BlockStatement(
207                             new Statement[] {
208                                 initMetaClassField,
209                                 new ExpressionStatement(
210                                     new MethodCallExpression(
211                                         metaClassVar,
212                                         "setProperty",
213                                         new ArgumentListExpression(
214                                             new Expression[] {
215                                                 VariableExpression.THIS_EXPRESSION,
216                                                 new VariableExpression("property"),
217                                                 new VariableExpression("value")})))
218                     }));
219                 }
220             }
221         }
222 
223         if (node.getDeclaredConstructors().isEmpty()) {
224             ConstructorNode constructor = new ConstructorNode(ACC_PUBLIC, null);
225             constructor.setSynthetic(true);
226             node.addConstructor(constructor);
227         }
228         
229         if (!(node instanceof InnerClassNode)) {// add a static timestamp field to the class
230             FieldNode timeTagField = new FieldNode(
231                     Verifier.__TIMESTAMP,
232                     Modifier.PUBLIC | Modifier.STATIC,
233                     "java.lang.Long",
234                     //"",
235                     node.getName(),
236                     new ConstantExpression(new Long(System.currentTimeMillis())));
237             // alternatively , FieldNode timeTagField = SourceUnit.createFieldNode("public static final long __timeStamp = " + System.currentTimeMillis() + "L");
238             timeTagField.setSynthetic(true);
239             node.addField(timeTagField);
240         }
241 
242         addFieldInitialization(node);
243 
244         node.visitContents(this);
245     }
246 
247     public void visitConstructor(ConstructorNode node) {
248     }
249 
250     public void visitMethod(MethodNode node) {
251         this.methodNode = node;
252 
253         Statement statement = node.getCode();
254         if (!node.isVoidMethod()) {
255             if (statement instanceof ExpressionStatement) {
256                 ExpressionStatement expStmt = (ExpressionStatement) statement;
257                 node.setCode(new ReturnStatement(expStmt.getExpression()));
258             }
259             else if (statement instanceof BlockStatement) {
260                 BlockStatement block = (BlockStatement) statement;
261 
262                 // lets copy the list so we create a new block
263                 List list = new ArrayList(block.getStatements());
264                 if (!list.isEmpty()) {
265                     int idx = list.size() - 1;
266                     Statement last = (Statement) list.get(idx);
267                     if (last instanceof ExpressionStatement) {
268                         ExpressionStatement expStmt = (ExpressionStatement) last;
269                         list.set(idx, new ReturnStatement(expStmt.getExpression()));
270                     }
271                     else if (!(last instanceof ReturnStatement)) {
272                         list.add(new ReturnStatement(ConstantExpression.NULL));
273                     }
274                 }
275                 else {
276                     list.add(new ReturnStatement(ConstantExpression.NULL));
277                 }
278 
279                 node.setCode(new BlockStatement(filterStatements(list)));
280             }
281         }
282         else {
283         	BlockStatement newBlock = new BlockStatement();
284             if (statement instanceof BlockStatement) {
285                 newBlock.addStatements(filterStatements(((BlockStatement)statement).getStatements()));
286             }
287             else {
288                 newBlock.addStatement(filterStatement(statement));
289             }
290             newBlock.addStatement(ReturnStatement.RETURN_NULL_OR_VOID);
291             node.setCode(newBlock);
292         }
293         if (node.getName().equals("main") && node.isStatic()) {
294             Parameter[] params = node.getParameters();
295             if (params.length == 1) {
296                 Parameter param = params[0];
297                 if (param.getType() == null || param.getType().equals("java.lang.Object")) {
298                     param.setType("java.lang.String[]");
299                 }
300             }
301         }
302         node.getCode().visit(new VerifierCodeVisitor(this));
303     }
304 
305     public void visitField(FieldNode node) {
306     }
307 
308     public void visitProperty(PropertyNode node) {
309         String name = node.getName();
310         FieldNode field = node.getField();
311 
312         
313         String getterPrefix = "get";
314         if ("boolean".equals(node.getType())) {
315             getterPrefix = "is";
316         }
317         String getterName = getterPrefix + capitalize(name);
318         String setterName = "set" + capitalize(name);
319 
320         Statement getterBlock = node.getGetterBlock();
321         if (getterBlock == null) {
322             if (!node.isPrivate() && classNode.getGetterMethod(getterName) == null) {
323                 getterBlock = createGetterBlock(node, field);
324             }
325         }
326         Statement setterBlock = node.getGetterBlock();
327         if (setterBlock == null) {
328             if (!node.isPrivate() && classNode.getSetterMethod(setterName) == null) {
329                 setterBlock = createSetterBlock(node, field);
330             }
331         }
332 
333         if (getterBlock != null) {
334             MethodNode getter =
335                 new MethodNode(getterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, getterBlock);
336             getter.setSynthetic(true);
337             classNode.addMethod(getter);
338             visitMethod(getter);
339 
340             if ("java.lang.Boolean".equals(node.getType())) {
341                 String secondGetterName = "is" + capitalize(name);
342                 MethodNode secondGetter =
343                     new MethodNode(secondGetterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, getterBlock);
344                 secondGetter.setSynthetic(true);
345                 classNode.addMethod(secondGetter);
346                 visitMethod(secondGetter);
347             }
348         }
349         if (setterBlock != null) {
350             Parameter[] setterParameterTypes = { new Parameter(node.getType(), "value")};
351             MethodNode setter =
352                 new MethodNode(setterName, node.getModifiers(), "void", setterParameterTypes, setterBlock);
353             setter.setSynthetic(true);
354             classNode.addMethod(setter);
355             visitMethod(setter);
356         }
357     }
358 
359     // Implementation methods
360     //-------------------------------------------------------------------------
361 
362     /***
363      * Creates a new helper method for each combination of default parameter expressions 
364      */
365     protected void addDefaultParameterMethods(ClassNode node) {
366         List methods = new ArrayList(node.getMethods());
367         for (Iterator iter = methods.iterator(); iter.hasNext();) {
368             MethodNode method = (MethodNode) iter.next();
369             Parameter[] parameters = method.getParameters();
370             int size = parameters.length;
371             for (int i = 0; i < size; i++) {
372                 Parameter parameter = parameters[i];
373                 Expression exp = parameter.getDefaultValue();
374                 if (exp != null) {
375                     addDefaultParameterMethod(node, method, parameters, i);
376                 }
377             }
378         }
379     }
380 
381     /***
382      * Adds a new method which defaults the values for all the parameters starting 
383      * from and including the given index
384      * 
385      * @param node the class to add the method
386      * @param method the given method to add a helper of
387      * @param parameters the parameters of the method to add a helper for
388      * @param index the index of the first default value expression parameter to use
389      */
390     protected void addDefaultParameterMethod(ClassNode node, MethodNode method, Parameter[] parameters, int index) {
391         // lets create a method using this expression
392         Parameter[] newParams = new Parameter[index];
393         System.arraycopy(parameters, 0, newParams, 0, index);
394 
395         ArgumentListExpression arguments = new ArgumentListExpression();
396         int size = parameters.length;
397         for (int i = 0; i < size; i++) {
398             if (i < index) {
399                 arguments.addExpression(new VariableExpression(parameters[i].getName()));
400             }
401             else {
402                 Expression defaultValue = parameters[i].getDefaultValue();
403                 if (defaultValue == null) {
404                     throw new RuntimeParserException(
405                         "The " + parameters[i].getName() + " parameter must have a default value",
406                         method);
407                 }
408                 else {
409                     arguments.addExpression(defaultValue);
410                 }
411             }
412         }
413 
414         MethodCallExpression expression =
415             new MethodCallExpression(VariableExpression.THIS_EXPRESSION, method.getName(), arguments);
416         Statement code = null;
417         if (method.isVoidMethod()) {
418             code = new ExpressionStatement(expression);
419             }
420         else {
421             code = new ReturnStatement(expression);
422         }
423 
424         node.addMethod(method.getName(), method.getModifiers(), method.getReturnType(), newParams, code);
425     }
426 
427     protected void addClosureCode(InnerClassNode node) {
428         // add a new invoke
429     }
430 
431     protected void addFieldInitialization(ClassNode node) {
432         for (Iterator iter = node.getDeclaredConstructors().iterator(); iter.hasNext();) {
433             addFieldInitialization(node, (ConstructorNode) iter.next());
434         }
435     }
436 
437     protected void addFieldInitialization(ClassNode node, ConstructorNode constructorNode) {
438         List statements = new ArrayList();
439         List staticStatements = new ArrayList();
440         for (Iterator iter = node.getFields().iterator(); iter.hasNext();) {
441             addFieldInitialization(statements, staticStatements, constructorNode, (FieldNode) iter.next());
442         }
443         if (!statements.isEmpty()) {
444             Statement code = constructorNode.getCode();
445             List otherStatements = new ArrayList();
446             if (code instanceof BlockStatement) {
447                 BlockStatement block = (BlockStatement) code;
448                 otherStatements.addAll(block.getStatements());
449             }
450             else if (code != null) {
451                 otherStatements.add(code);
452             }
453             if (!otherStatements.isEmpty()) {
454                 Statement first = (Statement) otherStatements.get(0);
455                 if (isSuperMethodCall(first)) {
456                     otherStatements.remove(0);
457                     statements.add(0, first);
458                 }
459                 statements.addAll(otherStatements);
460             }
461             constructorNode.setCode(new BlockStatement(statements));
462         }
463 
464         if (!staticStatements.isEmpty()) {
465             node.addStaticInitializerStatements(staticStatements);
466         }
467     }
468 
469     protected void addFieldInitialization(
470         List list,
471         List staticList,
472         ConstructorNode constructorNode,
473         FieldNode fieldNode) {
474         Expression expression = fieldNode.getInitialValueExpression();
475         if (expression != null) {
476             ExpressionStatement statement =
477                 new ExpressionStatement(
478                     new BinaryExpression(
479                         new FieldExpression(fieldNode),
480                         Token.newSymbol(Types.EQUAL, fieldNode.getLineNumber(), fieldNode.getColumnNumber()),
481                         expression));
482             if (fieldNode.isStatic()) {
483                 staticList.add(statement);
484             }
485             else {
486                 list.add(statement);
487             }
488         }
489     }
490 
491     protected boolean isSuperMethodCall(Statement first) {
492         if (first instanceof ExpressionStatement) {
493             ExpressionStatement exprStmt = (ExpressionStatement) first;
494             Expression expr = exprStmt.getExpression();
495             if (expr instanceof MethodCallExpression) {
496                 return MethodCallExpression.isSuperMethodCall((MethodCallExpression) expr);
497             }
498         }
499         return false;
500     }
501 
502     /***
503      * Capitalizes the start of the given bean property name
504      */
505     public static String capitalize(String name) {
506         return name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
507     }
508 
509     protected Statement createGetterBlock(PropertyNode propertyNode, FieldNode field) {
510         Expression expression = new FieldExpression(field);
511         return new ReturnStatement(expression);
512     }
513 
514     protected Statement createSetterBlock(PropertyNode propertyNode, FieldNode field) {
515         Expression expression = new FieldExpression(field);
516         return new ExpressionStatement(
517             new BinaryExpression(expression, Token.newSymbol(Types.EQUAL, 0, 0), new VariableExpression("value")));
518     }
519 
520     /***
521      * Filters the given statements
522      */
523     protected List filterStatements(List list) {
524         List answer = new ArrayList(list.size());
525         for (Iterator iter = list.iterator(); iter.hasNext();) {
526             answer.add(filterStatement((Statement) iter.next()));
527         }
528         return answer;
529     }
530 
531     protected Statement filterStatement(Statement statement) {
532         if (statement instanceof ExpressionStatement) {
533             ExpressionStatement expStmt = (ExpressionStatement) statement;
534             Expression expression = expStmt.getExpression();
535             if (expression instanceof ClosureExpression) {
536                 ClosureExpression closureExp = (ClosureExpression) expression;
537                 if (!closureExp.isParameterSpecified()) {
538                     return closureExp.getCode();
539                 }
540             }
541         }
542         return statement;
543     }
544 }