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 }