001    /*
002     $Id: Verifier.java,v 1.40 2005/06/10 09:55:31 cstein Exp $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package org.codehaus.groovy.classgen;
047    
048    import groovy.lang.Closure;
049    import groovy.lang.GString;
050    import groovy.lang.GroovyObject;
051    import groovy.lang.MetaClass;
052    
053    import java.lang.reflect.Modifier;
054    import java.util.ArrayList;
055    import java.util.Iterator;
056    import java.util.List;
057    
058    import org.codehaus.groovy.ast.ClassNode;
059    import org.codehaus.groovy.ast.CodeVisitorSupport;
060    import org.codehaus.groovy.ast.ConstructorNode;
061    import org.codehaus.groovy.ast.FieldNode;
062    import org.codehaus.groovy.ast.GroovyClassVisitor;
063    import org.codehaus.groovy.ast.InnerClassNode;
064    import org.codehaus.groovy.ast.MethodNode;
065    import org.codehaus.groovy.ast.Parameter;
066    import org.codehaus.groovy.ast.PropertyNode;
067    import org.codehaus.groovy.ast.expr.ArgumentListExpression;
068    import org.codehaus.groovy.ast.expr.BinaryExpression;
069    import org.codehaus.groovy.ast.expr.BooleanExpression;
070    import org.codehaus.groovy.ast.expr.ClosureExpression;
071    import org.codehaus.groovy.ast.expr.ConstantExpression;
072    import org.codehaus.groovy.ast.expr.Expression;
073    import org.codehaus.groovy.ast.expr.FieldExpression;
074    import org.codehaus.groovy.ast.expr.MethodCallExpression;
075    import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
076    import org.codehaus.groovy.ast.expr.VariableExpression;
077    import org.codehaus.groovy.ast.stmt.BlockStatement;
078    import org.codehaus.groovy.ast.stmt.EmptyStatement;
079    import org.codehaus.groovy.ast.stmt.ExpressionStatement;
080    import org.codehaus.groovy.ast.stmt.IfStatement;
081    import org.codehaus.groovy.ast.stmt.ReturnStatement;
082    import org.codehaus.groovy.ast.stmt.Statement;
083    import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
084    import org.codehaus.groovy.syntax.Types;
085    import org.codehaus.groovy.syntax.Token;
086    import org.codehaus.groovy.syntax.RuntimeParserException;
087    import org.objectweb.asm.Opcodes;
088    
089    /**
090     * Verifies the AST node and adds any defaulted AST code before
091     * bytecode generation occurs.
092     * 
093     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
094     * @version $Revision: 1.40 $
095     */
096    public class Verifier implements GroovyClassVisitor, Opcodes {
097    
098        public static final String __TIMESTAMP = "__timeStamp";
099            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    }