View Javadoc

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