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.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Set;
024
025import org.codehaus.groovy.ast.AnnotatedNode;
026import org.codehaus.groovy.ast.AnnotationNode;
027import org.codehaus.groovy.ast.ClassNode;
028import org.codehaus.groovy.ast.FieldNode;
029import org.codehaus.groovy.ast.MethodNode;
030import org.codehaus.groovy.ast.expr.ArgumentListExpression;
031import org.codehaus.groovy.ast.expr.ClosureExpression;
032import org.codehaus.groovy.ast.expr.ConstantExpression;
033import org.codehaus.groovy.ast.expr.Expression;
034import org.codehaus.groovy.ast.expr.MethodCallExpression;
035import org.codehaus.groovy.ast.stmt.BlockStatement;
036import org.codehaus.groovy.ast.stmt.ExpressionStatement;
037import org.codehaus.groovy.ast.stmt.Statement;
038
039import org.springframework.util.PatternMatchUtils;
040
041/**
042 * General purpose AST utilities.
043 *
044 * @author Phillip Webb
045 * @author Dave Syer
046 * @author Greg Turnquist
047 */
048public abstract class AstUtils {
049
050        /**
051         * Determine if a {@link ClassNode} has one or more of the specified annotations on
052         * the class or any of its methods. N.B. the type names are not normally fully
053         * qualified.
054         * @param node the class to examine
055         * @param annotations the annotations to look for
056         * @return {@code true} if at least one of the annotations is found, otherwise
057         * {@code false}
058         */
059        public static boolean hasAtLeastOneAnnotation(ClassNode node, String... annotations) {
060                if (hasAtLeastOneAnnotation((AnnotatedNode) node, annotations)) {
061                        return true;
062                }
063                for (MethodNode method : node.getMethods()) {
064                        if (hasAtLeastOneAnnotation(method, annotations)) {
065                                return true;
066                        }
067                }
068                return false;
069        }
070
071        /**
072         * Determine if an {@link AnnotatedNode} has one or more of the specified annotations.
073         * N.B. the annotation type names are not normally fully qualified.
074         * @param node the node to examine
075         * @param annotations the annotations to look for
076         * @return {@code true} if at least one of the annotations is found, otherwise
077         * {@code false}
078         */
079        public static boolean hasAtLeastOneAnnotation(AnnotatedNode node,
080                        String... annotations) {
081                for (AnnotationNode annotationNode : node.getAnnotations()) {
082                        for (String annotation : annotations) {
083                                if (PatternMatchUtils.simpleMatch(annotation,
084                                                annotationNode.getClassNode().getName())) {
085                                        return true;
086                                }
087                        }
088                }
089                return false;
090        }
091
092        /**
093         * Determine if a {@link ClassNode} has one or more fields of the specified types or
094         * method returning one or more of the specified types. N.B. the type names are not
095         * normally fully qualified.
096         * @param node the class to examine
097         * @param types the types to look for
098         * @return {@code true} if at least one of the types is found, otherwise {@code false}
099         */
100        public static boolean hasAtLeastOneFieldOrMethod(ClassNode node, String... types) {
101                Set<String> typesSet = new HashSet<String>(Arrays.asList(types));
102                for (FieldNode field : node.getFields()) {
103                        if (typesSet.contains(field.getType().getName())) {
104                                return true;
105                        }
106                }
107                for (MethodNode method : node.getMethods()) {
108                        if (typesSet.contains(method.getReturnType().getName())) {
109                                return true;
110                        }
111                }
112                return false;
113        }
114
115        /**
116         * Determine if a {@link ClassNode} subclasses any of the specified types N.B. the
117         * type names are not normally fully qualified.
118         * @param node the class to examine
119         * @param types the types that may have been sub-classed
120         * @return {@code true} if the class subclasses any of the specified types, otherwise
121         * {@code false}
122         */
123        public static boolean subclasses(ClassNode node, String... types) {
124                for (String type : types) {
125                        if (node.getSuperClass().getName().equals(type)) {
126                                return true;
127                        }
128                }
129                return false;
130        }
131
132        public static boolean hasAtLeastOneInterface(ClassNode classNode, String... types) {
133                Set<String> typesSet = new HashSet<String>(Arrays.asList(types));
134                for (ClassNode inter : classNode.getInterfaces()) {
135                        if (typesSet.contains(inter.getName())) {
136                                return true;
137                        }
138                }
139                return false;
140        }
141
142        /**
143         * Extract a top-level {@code name} closure from inside this block if there is one,
144         * optionally removing it from the block at the same time.
145         * @param block a block statement (class definition)
146         * @param name the name to look for
147         * @param remove whether or not the extracted closure should be removed
148         * @return a beans Closure if one can be found, null otherwise
149         */
150        public static ClosureExpression getClosure(BlockStatement block, String name,
151                        boolean remove) {
152                for (ExpressionStatement statement : getExpressionStatements(block)) {
153                        Expression expression = statement.getExpression();
154                        if (expression instanceof MethodCallExpression) {
155                                ClosureExpression closure = getClosure(name,
156                                                (MethodCallExpression) expression);
157                                if (closure != null) {
158                                        if (remove) {
159                                                block.getStatements().remove(statement);
160                                        }
161                                        return closure;
162                                }
163                        }
164                }
165                return null;
166        }
167
168        private static List<ExpressionStatement> getExpressionStatements(
169                        BlockStatement block) {
170                ArrayList<ExpressionStatement> statements = new ArrayList<ExpressionStatement>();
171                for (Statement statement : block.getStatements()) {
172                        if (statement instanceof ExpressionStatement) {
173                                statements.add((ExpressionStatement) statement);
174                        }
175                }
176                return statements;
177        }
178
179        private static ClosureExpression getClosure(String name,
180                        MethodCallExpression expression) {
181                Expression method = expression.getMethod();
182                if (method instanceof ConstantExpression) {
183                        if (name.equals(((ConstantExpression) method).getValue())) {
184                                return (ClosureExpression) ((ArgumentListExpression) expression
185                                                .getArguments()).getExpression(0);
186                        }
187                }
188                return null;
189        }
190
191        public static ClosureExpression getClosure(BlockStatement block, String name) {
192                return getClosure(block, name, false);
193        }
194
195}