001/* 002 * Copyright 2012-2015 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.cli.compiler; 018 019import java.lang.reflect.Modifier; 020import java.util.ArrayList; 021 022import org.codehaus.groovy.ast.ASTNode; 023import org.codehaus.groovy.ast.ClassCodeVisitorSupport; 024import org.codehaus.groovy.ast.ClassHelper; 025import org.codehaus.groovy.ast.ClassNode; 026import org.codehaus.groovy.ast.ModuleNode; 027import org.codehaus.groovy.ast.PropertyNode; 028import org.codehaus.groovy.ast.expr.ClosureExpression; 029import org.codehaus.groovy.ast.stmt.BlockStatement; 030import org.codehaus.groovy.control.SourceUnit; 031import org.codehaus.groovy.transform.ASTTransformation; 032 033import org.springframework.core.annotation.Order; 034 035/** 036 * {@link ASTTransformation} to resolve beans declarations inside application source 037 * files. Users only need to define a <code>beans{}</code> DSL element, and this 038 * transformation will remove it and make it accessible to the Spring application via an 039 * interface. 040 * 041 * @author Dave Syer 042 */ 043@Order(GroovyBeansTransformation.ORDER) 044public class GroovyBeansTransformation implements ASTTransformation { 045 046 /** 047 * The order of the transformation. 048 */ 049 public static final int ORDER = DependencyManagementBomTransformation.ORDER + 200; 050 051 @Override 052 public void visit(ASTNode[] nodes, SourceUnit source) { 053 for (ASTNode node : nodes) { 054 if (node instanceof ModuleNode) { 055 ModuleNode module = (ModuleNode) node; 056 for (ClassNode classNode : new ArrayList<ClassNode>( 057 module.getClasses())) { 058 if (classNode.isScript()) { 059 classNode.visitContents(new ClassVisitor(source, classNode)); 060 } 061 } 062 } 063 } 064 } 065 066 private class ClassVisitor extends ClassCodeVisitorSupport { 067 068 private static final String SOURCE_INTERFACE = "org.springframework.boot.BeanDefinitionLoader.GroovyBeanDefinitionSource"; 069 070 private static final String BEANS = "beans"; 071 072 private final SourceUnit source; 073 074 private final ClassNode classNode; 075 076 private boolean xformed = false; 077 078 ClassVisitor(SourceUnit source, ClassNode classNode) { 079 this.source = source; 080 this.classNode = classNode; 081 } 082 083 @Override 084 protected SourceUnit getSourceUnit() { 085 return this.source; 086 } 087 088 @Override 089 public void visitBlockStatement(BlockStatement block) { 090 if (block.isEmpty() || this.xformed) { 091 return; 092 } 093 ClosureExpression closure = beans(block); 094 if (closure != null) { 095 // Add a marker interface to the current script 096 this.classNode.addInterface(ClassHelper.make(SOURCE_INTERFACE)); 097 // Implement the interface by adding a public read-only property with the 098 // same name as the method in the interface (getBeans). Make it return the 099 // closure. 100 this.classNode.addProperty( 101 new PropertyNode(BEANS, Modifier.PUBLIC | Modifier.FINAL, 102 ClassHelper.CLOSURE_TYPE.getPlainNodeReference(), 103 this.classNode, closure, null, null)); 104 // Only do this once per class 105 this.xformed = true; 106 } 107 } 108 109 /** 110 * Extract a top-level <code>beans{}</code> closure from inside this block if 111 * there is one. Removes it from the block at the same time. 112 * @param block a block statement (class definition) 113 * @return a beans Closure if one can be found, null otherwise 114 */ 115 private ClosureExpression beans(BlockStatement block) { 116 return AstUtils.getClosure(block, BEANS, true); 117 } 118 119 } 120 121}