001 /*
002 $Id: Verifier.java 4598 2006-12-22 20:21:21Z blackdrag $
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.GroovyObject;
049 import groovy.lang.MetaClass;
050
051 import java.lang.reflect.Modifier;
052 import java.util.ArrayList;
053 import java.util.Iterator;
054 import java.util.List;
055
056 import org.codehaus.groovy.ast.ClassHelper;
057 import org.codehaus.groovy.ast.ClassNode;
058 import org.codehaus.groovy.ast.CodeVisitorSupport;
059 import org.codehaus.groovy.ast.ConstructorNode;
060 import org.codehaus.groovy.ast.FieldNode;
061 import org.codehaus.groovy.ast.GroovyClassVisitor;
062 import org.codehaus.groovy.ast.InnerClassNode;
063 import org.codehaus.groovy.ast.MethodNode;
064 import org.codehaus.groovy.ast.Parameter;
065 import org.codehaus.groovy.ast.PropertyNode;
066 import org.codehaus.groovy.ast.VariableScope;
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.ConstructorCallExpression;
073 import org.codehaus.groovy.ast.expr.Expression;
074 import org.codehaus.groovy.ast.expr.FieldExpression;
075 import org.codehaus.groovy.ast.expr.MethodCallExpression;
076 import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
077 import org.codehaus.groovy.ast.expr.VariableExpression;
078 import org.codehaus.groovy.ast.stmt.BlockStatement;
079 import org.codehaus.groovy.ast.stmt.EmptyStatement;
080 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
081 import org.codehaus.groovy.ast.stmt.IfStatement;
082 import org.codehaus.groovy.ast.stmt.ReturnStatement;
083 import org.codehaus.groovy.ast.stmt.Statement;
084 import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
085 import org.codehaus.groovy.syntax.Types;
086 import org.codehaus.groovy.syntax.Token;
087 import org.codehaus.groovy.syntax.RuntimeParserException;
088 import org.objectweb.asm.Opcodes;
089
090 /**
091 * Verifies the AST node and adds any defaulted AST code before
092 * bytecode generation occurs.
093 *
094 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
095 * @version $Revision: 4598 $
096 */
097 public class Verifier implements GroovyClassVisitor, Opcodes {
098
099 public static final String __TIMESTAMP = "__timeStamp";
100 private ClassNode classNode;
101 private MethodNode methodNode;
102
103 public ClassNode getClassNode() {
104 return classNode;
105 }
106
107 public MethodNode getMethodNode() {
108 return methodNode;
109 }
110
111 /**
112 * add code to implement GroovyObject
113 * @param node
114 */
115 public void visitClass(ClassNode node) {
116 this.classNode = node;
117
118 if ((classNode.getModifiers() & Opcodes.ACC_INTERFACE) >0) {
119 //interfaces have no construcotrs, but this code expects one,
120 //so creta a dummy and don't add it to the class node
121 ConstructorNode dummy = new ConstructorNode(0,null);
122 addInitialization(node, dummy);
123 node.visitContents(this);
124 return;
125 }
126
127 addDefaultParameterMethods(node);
128 addDefaultParameterConstructors(node);
129
130 if (!node.isDerivedFromGroovyObject()) {
131 node.addInterface(ClassHelper.make(GroovyObject.class));
132
133 // lets add a new field for the metaclass
134 StaticMethodCallExpression initMetaClassCall =
135 new StaticMethodCallExpression(
136 ClassHelper.make(ScriptBytecodeAdapter.class),
137 "initMetaClass",
138 VariableExpression.THIS_EXPRESSION);
139
140 PropertyNode metaClassProperty =
141 node.addProperty("metaClass", ACC_PUBLIC, ClassHelper.make(MetaClass.class), initMetaClassCall, null, null);
142 metaClassProperty.setSynthetic(true);
143 FieldNode metaClassField = metaClassProperty.getField();
144 metaClassField.setModifiers(metaClassField.getModifiers() | ACC_TRANSIENT);
145
146 FieldExpression metaClassVar = new FieldExpression(metaClassField);
147 IfStatement initMetaClassField =
148 new IfStatement(
149 new BooleanExpression(
150 new BinaryExpression(metaClassVar, Token.newSymbol( Types.COMPARE_EQUAL, -1, -1), ConstantExpression.NULL)),
151 new ExpressionStatement(new BinaryExpression(metaClassVar, Token.newSymbol( Types.EQUAL, -1, -1), initMetaClassCall)),
152 EmptyStatement.INSTANCE);
153
154 node.addSyntheticMethod(
155 "getMetaClass",
156 ACC_PUBLIC,
157 ClassHelper.make(MetaClass.class),
158 Parameter.EMPTY_ARRAY,
159 ClassNode.EMPTY_ARRAY,
160 new BlockStatement(new Statement[] { initMetaClassField, new ReturnStatement(metaClassVar)}, new VariableScope())
161 );
162
163 // @todo we should check if the base class implements the invokeMethod method
164
165 // lets add the invokeMethod implementation
166 ClassNode superClass = node.getSuperClass();
167 boolean addDelegateObject =
168 (node instanceof InnerClassNode && superClass.equals(ClassHelper.CLOSURE_TYPE))
169 || superClass.equals(ClassHelper.GSTRING_TYPE);
170
171 // don't do anything as the base class implements the invokeMethod
172 if (!addDelegateObject) {
173
174 VariableExpression vMethods = new VariableExpression("method");
175 VariableExpression vArguments = new VariableExpression("arguments");
176 VariableScope blockScope = new VariableScope();
177 blockScope.getReferencedLocalVariables().put("method",vMethods);
178 blockScope.getReferencedLocalVariables().put("arguments",vArguments);
179
180 node.addSyntheticMethod(
181 "invokeMethod",
182 ACC_PUBLIC,
183 ClassHelper.OBJECT_TYPE,
184 new Parameter[] {
185 new Parameter(ClassHelper.STRING_TYPE, "method"),
186 new Parameter(ClassHelper.OBJECT_TYPE, "arguments")
187 },
188 ClassNode.EMPTY_ARRAY,
189 new BlockStatement(
190 new Statement[] {
191 initMetaClassField,
192 new ReturnStatement(
193 new MethodCallExpression(
194 metaClassVar,
195 "invokeMethod",
196 new ArgumentListExpression(
197 new Expression[] {
198 VariableExpression.THIS_EXPRESSION,
199 vMethods,
200 vArguments}
201 )
202 )
203 )
204 },
205 blockScope
206 )
207 );
208
209
210 if (!node.isScript()) {
211 node.addSyntheticMethod(
212 "getProperty",
213 ACC_PUBLIC,
214 ClassHelper.OBJECT_TYPE,
215 new Parameter[] { new Parameter(ClassHelper.STRING_TYPE, "property")},
216 ClassNode.EMPTY_ARRAY,
217 new BlockStatement(
218 new Statement[] {
219 initMetaClassField,
220 new ReturnStatement(
221 new MethodCallExpression(
222 metaClassVar,
223 "getProperty",
224 new ArgumentListExpression(
225 new Expression[] {
226 VariableExpression.THIS_EXPRESSION,
227 new VariableExpression("property")})))
228 },
229 new VariableScope()
230 ));
231 VariableExpression vProp = new VariableExpression("property");
232 VariableExpression vValue = new VariableExpression("value");
233 blockScope = new VariableScope();
234 blockScope.getReferencedLocalVariables().put("property",vProp);
235 blockScope.getReferencedLocalVariables().put("value",vValue);
236
237 node.addSyntheticMethod(
238 "setProperty",
239 ACC_PUBLIC,
240 ClassHelper.VOID_TYPE,
241 new Parameter[] {
242 new Parameter(ClassHelper.STRING_TYPE, "property"),
243 new Parameter(ClassHelper.OBJECT_TYPE, "value")
244 },
245 ClassNode.EMPTY_ARRAY,
246 new BlockStatement(
247 new Statement[] {
248 initMetaClassField,
249 new ExpressionStatement(
250 new MethodCallExpression(
251 metaClassVar,
252 "setProperty",
253 new ArgumentListExpression(
254 new Expression[] {
255 VariableExpression.THIS_EXPRESSION,
256 vProp,
257 vValue})))
258 },
259 blockScope
260 ));
261 }
262 }
263 }
264
265 if (node.getDeclaredConstructors().isEmpty()) {
266 ConstructorNode constructor = new ConstructorNode(ACC_PUBLIC, null);
267 constructor.setSynthetic(true);
268 node.addConstructor(constructor);
269 }
270
271 if (!(node instanceof InnerClassNode)) {// add a static timestamp field to the class
272 FieldNode timeTagField = new FieldNode(
273 Verifier.__TIMESTAMP,
274 Modifier.PUBLIC | Modifier.STATIC,
275 ClassHelper.Long_TYPE,
276 //"",
277 node,
278 new ConstantExpression(new Long(System.currentTimeMillis())));
279 // alternatively , FieldNode timeTagField = SourceUnit.createFieldNode("public static final long __timeStamp = " + System.currentTimeMillis() + "L");
280 timeTagField.setSynthetic(true);
281 node.addField(timeTagField);
282 }
283
284 addInitialization(node);
285 node.getObjectInitializerStatements().clear();
286 node.visitContents(this);
287 }
288 public void visitConstructor(ConstructorNode node) {
289 CodeVisitorSupport checkSuper = new CodeVisitorSupport() {
290 boolean firstMethodCall = true;
291 String type=null;
292 public void visitMethodCallExpression(MethodCallExpression call) {
293 if (!firstMethodCall) return;
294 firstMethodCall = false;
295 String name = call.getMethodAsString();
296 if (!name.equals("super") && !name.equals("this")) return;
297 type=name;
298 call.getArguments().visit(this);
299 type=null;
300 }
301 public void visitVariableExpression(VariableExpression expression) {
302 if (type==null) return;
303 String name = expression.getName();
304 if (!name.equals("this") && !name.equals("super")) return;
305 throw new RuntimeParserException("cannot reference "+name+" inside of "+type+"(....) before supertype constructor has been called",expression);
306 }
307 };
308 Statement s = node.getCode();
309 //todo why can a statement can be null?
310 if (s == null) return;
311 s.visit(checkSuper);
312 }
313
314 public void visitMethod(MethodNode node) {
315 this.methodNode = node;
316 Statement statement = node.getCode();
317 if (!node.isVoidMethod()) {
318 if (statement instanceof ExpressionStatement) {
319 ExpressionStatement expStmt = (ExpressionStatement) statement;
320 node.setCode(new ReturnStatement(expStmt.getExpression()));
321 }
322 else if (statement instanceof BlockStatement) {
323 BlockStatement block = (BlockStatement) statement;
324
325 // lets copy the list so we create a new block
326 List list = new ArrayList(block.getStatements());
327 if (!list.isEmpty()) {
328 int idx = list.size() - 1;
329 Statement last = (Statement) list.get(idx);
330 if (last instanceof ExpressionStatement) {
331 ExpressionStatement expStmt = (ExpressionStatement) last;
332 list.set(idx, new ReturnStatement(expStmt));
333 }
334 else if (!(last instanceof ReturnStatement)) {
335 list.add(new ReturnStatement(ConstantExpression.NULL));
336 }
337 }
338 else {
339 list.add(new ReturnStatement(ConstantExpression.NULL));
340 }
341
342 node.setCode(new BlockStatement(filterStatements(list),block.getVariableScope()));
343 }
344 }
345 else if (!node.isAbstract()) {
346 BlockStatement newBlock = new BlockStatement();
347 if (statement instanceof BlockStatement) {
348 newBlock.addStatements(filterStatements(((BlockStatement)statement).getStatements()));
349 }
350 else {
351 newBlock.addStatement(filterStatement(statement));
352 }
353 newBlock.addStatement(ReturnStatement.RETURN_NULL_OR_VOID);
354 node.setCode(newBlock);
355 }
356 if (node.getName().equals("main") && node.isStatic()) {
357 Parameter[] params = node.getParameters();
358 if (params.length == 1) {
359 Parameter param = params[0];
360 if (param.getType() == null || param.getType()==ClassHelper.OBJECT_TYPE) {
361 param.setType(ClassHelper.STRING_TYPE.makeArray());
362 }
363 }
364 }
365 statement = node.getCode();
366 if (statement!=null) statement.visit(new VerifierCodeVisitor(this));
367 }
368
369 public void visitField(FieldNode node) {
370 }
371
372 public void visitProperty(PropertyNode node) {
373 String name = node.getName();
374 FieldNode field = node.getField();
375
376 String getterName = "get" + capitalize(name);
377 String setterName = "set" + capitalize(name);
378
379 Statement getterBlock = node.getGetterBlock();
380 if (getterBlock == null) {
381 if (!node.isPrivate() && classNode.getGetterMethod(getterName) == null) {
382 getterBlock = createGetterBlock(node, field);
383 }
384 }
385 Statement setterBlock = node.getSetterBlock();
386 if (setterBlock == null) {
387 if (!node.isPrivate() && (node.getModifiers()&ACC_FINAL)==0 && classNode.getSetterMethod(setterName) == null) {
388 setterBlock = createSetterBlock(node, field);
389 }
390 }
391
392 if (getterBlock != null) {
393 MethodNode getter =
394 new MethodNode(getterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, getterBlock);
395 getter.setSynthetic(true);
396 classNode.addMethod(getter);
397 visitMethod(getter);
398
399 if (ClassHelper.boolean_TYPE==node.getType() || ClassHelper.Boolean_TYPE==node.getType()) {
400 String secondGetterName = "is" + capitalize(name);
401 MethodNode secondGetter =
402 new MethodNode(secondGetterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, getterBlock);
403 secondGetter.setSynthetic(true);
404 classNode.addMethod(secondGetter);
405 visitMethod(secondGetter);
406 }
407 }
408 if (setterBlock != null) {
409 Parameter[] setterParameterTypes = { new Parameter(node.getType(), "value")};
410 MethodNode setter =
411 new MethodNode(setterName, node.getModifiers(), ClassHelper.VOID_TYPE, setterParameterTypes, ClassNode.EMPTY_ARRAY, setterBlock);
412 setter.setSynthetic(true);
413 classNode.addMethod(setter);
414 visitMethod(setter);
415 }
416 }
417
418 // Implementation methods
419 //-------------------------------------------------------------------------
420
421 private interface DefaultArgsAction {
422 public void call(ArgumentListExpression arguments, Parameter[] newParams, MethodNode method);
423 }
424
425 /**
426 * Creates a new helper method for each combination of default parameter expressions
427 */
428 protected void addDefaultParameterMethods(final ClassNode node) {
429 List methods = new ArrayList(node.getMethods());
430 addDefaultParameters(methods, new DefaultArgsAction(){
431 public void call(ArgumentListExpression arguments, Parameter[] newParams, MethodNode method) {
432 MethodCallExpression expression = new MethodCallExpression(VariableExpression.THIS_EXPRESSION, method.getName(), arguments);
433 expression.setImplicitThis(true);
434 Statement code = null;
435 if (method.isVoidMethod()) {
436 code = new ExpressionStatement(expression);
437 } else {
438 code = new ReturnStatement(expression);
439 }
440 node.addMethod(method.getName(), method.getModifiers(), method.getReturnType(), newParams, method.getExceptions(), code);
441 }
442 });
443 }
444
445 protected void addDefaultParameterConstructors(final ClassNode node) {
446 List methods = new ArrayList(node.getDeclaredConstructors());
447 addDefaultParameters(methods, new DefaultArgsAction(){
448 public void call(ArgumentListExpression arguments, Parameter[] newParams, MethodNode method) {
449 ConstructorNode ctor = (ConstructorNode) method;
450 ConstructorCallExpression expression = new ConstructorCallExpression(ClassNode.THIS, arguments);
451 Statement code = new ExpressionStatement(expression);
452 node.addConstructor(ctor.getModifiers(), newParams, ctor.getExceptions(), code);
453 }
454 });
455 }
456
457 /**
458 * Creates a new helper method for each combination of default parameter expressions
459 */
460 protected void addDefaultParameters(List methods, DefaultArgsAction action) {
461 for (Iterator iter = methods.iterator(); iter.hasNext();) {
462 MethodNode method = (MethodNode) iter.next();
463 if (method.hasDefaultValue()) {
464 Parameter[] parameters = method.getParameters();
465 int counter = 0;
466 ArrayList paramValues = new ArrayList();
467 int size = parameters.length;
468 for (int i = size - 1; i >= 0; i--) {
469 Parameter parameter = parameters[i];
470 if (parameter != null && parameter.hasInitialExpression()) {
471 paramValues.add(new Integer(i));
472 paramValues.add(parameter.getInitialExpression());
473 counter++;
474 }
475 }
476
477 for (int j = 1; j <= counter; j++) {
478 Parameter[] newParams = new Parameter[parameters.length - j];
479 ArgumentListExpression arguments = new ArgumentListExpression();
480 int index = 0;
481 int k = 1;
482 for (int i = 0; i < parameters.length; i++) {
483 if (k > counter - j && parameters[i] != null && parameters[i].hasInitialExpression()) {
484 arguments.addExpression(parameters[i].getInitialExpression());
485 k++;
486 }
487 else if (parameters[i] != null && parameters[i].hasInitialExpression()) {
488 newParams[index++] = parameters[i];
489 arguments.addExpression(new VariableExpression(parameters[i].getName()));
490 k++;
491 }
492 else {
493 newParams[index++] = parameters[i];
494 arguments.addExpression(new VariableExpression(parameters[i].getName()));
495 }
496 }
497 action.call(arguments,newParams,method);
498 }
499 }
500 }
501 }
502
503 protected void addClosureCode(InnerClassNode node) {
504 // add a new invoke
505 }
506
507 protected void addInitialization(ClassNode node) {
508 for (Iterator iter = node.getDeclaredConstructors().iterator(); iter.hasNext();) {
509 addInitialization(node, (ConstructorNode) iter.next());
510 }
511 }
512
513 protected void addInitialization(ClassNode node, ConstructorNode constructorNode) {
514 Statement firstStatement = constructorNode.getFirstStatement();
515 ConstructorCallExpression first = getFirstIfSpecialConstructorCall(firstStatement);
516
517 // in case of this(...) let the other constructor do the intit
518 if (first!=null && first.isThisCall()) return;
519
520 List statements = new ArrayList();
521 List staticStatements = new ArrayList();
522 for (Iterator iter = node.getFields().iterator(); iter.hasNext();) {
523 addFieldInitialization(statements, staticStatements, (FieldNode) iter.next());
524 }
525 statements.addAll(node.getObjectInitializerStatements());
526 if (!statements.isEmpty()) {
527 Statement code = constructorNode.getCode();
528 BlockStatement block = new BlockStatement();
529 List otherStatements = block.getStatements();
530 if (code instanceof BlockStatement) {
531 block = (BlockStatement) code;
532 otherStatements=block.getStatements();
533 }
534 else if (code != null) {
535 otherStatements.add(code);
536 }
537 if (!otherStatements.isEmpty()) {
538 if (first!=null) {
539 // it is super(..) since this(..) is already covered
540 otherStatements.remove(0);
541 statements.add(0, firstStatement);
542 }
543 statements.addAll(otherStatements);
544 }
545 constructorNode.setCode(new BlockStatement(statements, block.getVariableScope()));
546 }
547
548 if (!staticStatements.isEmpty()) {
549 node.addStaticInitializerStatements(staticStatements,true);
550 }
551 }
552
553 private ConstructorCallExpression getFirstIfSpecialConstructorCall(Statement code) {
554 if (code == null || !(code instanceof ExpressionStatement)) return null;
555
556 Expression expression = ((ExpressionStatement)code).getExpression();
557 if (!(expression instanceof ConstructorCallExpression)) return null;
558 ConstructorCallExpression cce = (ConstructorCallExpression) expression;
559 if (cce.isSpecialCall()) return cce;
560 return null;
561 }
562
563 protected void addFieldInitialization(
564 List list,
565 List staticList,
566 FieldNode fieldNode) {
567 Expression expression = fieldNode.getInitialExpression();
568 if (expression != null) {
569 ExpressionStatement statement =
570 new ExpressionStatement(
571 new BinaryExpression(
572 new FieldExpression(fieldNode),
573 Token.newSymbol(Types.EQUAL, fieldNode.getLineNumber(), fieldNode.getColumnNumber()),
574 expression));
575 if (fieldNode.isStatic()) {
576 staticList.add(statement);
577 }
578 else {
579 list.add(statement);
580 }
581 }
582 }
583
584 /**
585 * Capitalizes the start of the given bean property name
586 */
587 public static String capitalize(String name) {
588 return name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
589 }
590
591 protected Statement createGetterBlock(PropertyNode propertyNode, FieldNode field) {
592 Expression expression = new FieldExpression(field);
593 return new ReturnStatement(expression);
594 }
595
596 protected Statement createSetterBlock(PropertyNode propertyNode, FieldNode field) {
597 Expression expression = new FieldExpression(field);
598 return new ExpressionStatement(
599 new BinaryExpression(expression, Token.newSymbol(Types.EQUAL, 0, 0), new VariableExpression("value")));
600 }
601
602 /**
603 * Filters the given statements
604 */
605 protected List filterStatements(List list) {
606 List answer = new ArrayList(list.size());
607 for (Iterator iter = list.iterator(); iter.hasNext();) {
608 answer.add(filterStatement((Statement) iter.next()));
609 }
610 return answer;
611 }
612
613 protected Statement filterStatement(Statement statement) {
614 if (statement instanceof ExpressionStatement) {
615 ExpressionStatement expStmt = (ExpressionStatement) statement;
616 Expression expression = expStmt.getExpression();
617 if (expression instanceof ClosureExpression) {
618 ClosureExpression closureExp = (ClosureExpression) expression;
619 if (!closureExp.isParameterSpecified()) {
620 return closureExp.getCode();
621 }
622 }
623 }
624 return statement;
625 }
626
627 }