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}