View Javadoc

1   package org.springframework.roo.classpath.javaparser.details;
2   
3   import japa.parser.JavaParser;
4   import japa.parser.ParseException;
5   import japa.parser.ast.CompilationUnit;
6   import japa.parser.ast.TypeParameter;
7   import japa.parser.ast.body.BodyDeclaration;
8   import japa.parser.ast.body.MethodDeclaration;
9   import japa.parser.ast.body.Parameter;
10  import japa.parser.ast.body.TypeDeclaration;
11  import japa.parser.ast.body.VariableDeclaratorId;
12  import japa.parser.ast.expr.AnnotationExpr;
13  import japa.parser.ast.expr.NameExpr;
14  import japa.parser.ast.stmt.BlockStmt;
15  import japa.parser.ast.type.ClassOrInterfaceType;
16  import japa.parser.ast.type.ReferenceType;
17  import japa.parser.ast.type.Type;
18  
19  import java.io.ByteArrayInputStream;
20  import java.lang.reflect.Modifier;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  
27  import org.springframework.roo.classpath.details.MethodMetadata;
28  import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
29  import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
30  import org.springframework.roo.classpath.javaparser.CompilationUnitServices;
31  import org.springframework.roo.classpath.javaparser.JavaParserUtils;
32  import org.springframework.roo.model.JavaSymbolName;
33  import org.springframework.roo.model.JavaType;
34  import org.springframework.roo.support.style.ToStringCreator;
35  import org.springframework.roo.support.util.Assert;
36  
37  /**
38   * Java Parser implementation of {@link MethodMetadata}.
39   * 
40   * @author Ben Alex
41   * @since 1.0
42   *
43   */
44  public class JavaParserMethodMetadata implements MethodMetadata {
45  
46  	private List<AnnotationMetadata> annotations = new ArrayList<AnnotationMetadata>();
47  	private List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
48  	private List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
49  	private List<JavaType> throwsTypes = new ArrayList<JavaType>();
50  	private JavaType returnType;
51  	private JavaSymbolName methodName;
52  	private String body;
53  	private String declaredByMetadataId;
54  	private int modifier;
55  	
56  	public JavaParserMethodMetadata(String declaredByMetadataId, MethodDeclaration methodDeclaration, CompilationUnitServices compilationUnitServices, Set<JavaSymbolName> typeParameters) {
57  		Assert.hasText(declaredByMetadataId, "Declared by metadata ID required");
58  		Assert.notNull(methodDeclaration, "Method declaration is mandatory");
59  		Assert.notNull(compilationUnitServices, "Compilation unit services are required");
60  		
61  		// Convert Java Parser modifier into JDK modifier
62  		this.modifier = JavaParserUtils.getJdkModifier(methodDeclaration.getModifiers());
63  		
64  		// Add method-declared type parameters (if any) to the list of type parameters
65  		Set<JavaSymbolName> fullTypeParameters = new HashSet<JavaSymbolName>();
66  		fullTypeParameters.addAll(typeParameters);
67  		List<TypeParameter> params = methodDeclaration.getTypeParameters();
68  		if (params != null) {
69  			for (TypeParameter candidate : params) {
70  				JavaSymbolName currentTypeParam = new JavaSymbolName(candidate.getName());
71  				fullTypeParameters.add(currentTypeParam);
72  			}
73  		}
74  		
75  		// Compute the return type
76  		Type rt = methodDeclaration.getType();
77  		this.returnType = JavaParserUtils.getJavaType(compilationUnitServices, rt, fullTypeParameters);
78  		
79  		// Compute the method name
80  		this.methodName = new JavaSymbolName(methodDeclaration.getName());
81  		
82  		// Get the body
83  		this.body = methodDeclaration.getBody() == null ? null : methodDeclaration.getBody().toString();
84  		
85  		// Lookup the parameters and their names
86  		if (methodDeclaration.getParameters() != null) {
87  			for (Parameter p : methodDeclaration.getParameters()) {
88  				Type pt = p.getType();
89  				JavaType parameterType = JavaParserUtils.getJavaType(compilationUnitServices, pt, fullTypeParameters);
90  				
91  				List<AnnotationExpr> annotationsList = p.getAnnotations();
92  				List<AnnotationMetadata> annotations = new ArrayList<AnnotationMetadata>();
93  				if (annotationsList != null) {
94  					for (AnnotationExpr candidate : annotationsList) {
95  						JavaParserAnnotationMetadata md = new JavaParserAnnotationMetadata(candidate, compilationUnitServices);
96  						annotations.add(md);
97  					}
98  				}
99  				
100 				parameterTypes.add(new AnnotatedJavaType(parameterType, annotations));
101 				parameterNames.add(new JavaSymbolName(p.getId().getName()));
102 			}
103 		}
104 		
105 		if (methodDeclaration.getThrows() != null) {
106 			for (NameExpr throwsType: methodDeclaration.getThrows()) {
107 				throwsTypes.add(new JavaType(JavaParserUtils.getClassOrInterfaceType(throwsType).getName()));
108 			}
109 		}
110 		
111 		if (methodDeclaration.getAnnotations() != null) {
112 			for (AnnotationExpr annotation : methodDeclaration.getAnnotations()) {
113 				this.annotations.add(new JavaParserAnnotationMetadata(annotation, compilationUnitServices));
114 			}
115 		}
116 		
117 	}
118 
119 	public int getModifier() {
120 		return modifier;
121 	}
122 
123 	public String getDeclaredByMetadataId() {
124 		return declaredByMetadataId;
125 	}
126 
127 	public List<AnnotationMetadata> getAnnotations() {
128 		return Collections.unmodifiableList(annotations);
129 	}
130 	
131 	public List<JavaSymbolName> getParameterNames() {
132 		return Collections.unmodifiableList(parameterNames);
133 	}
134 
135 	public List<AnnotatedJavaType> getParameterTypes() {
136 		return Collections.unmodifiableList(parameterTypes);
137 	}
138 
139 	public JavaType getReturnType() {
140 		return returnType;
141 	}
142 
143 	public JavaSymbolName getMethodName() {
144 		return methodName;
145 	}
146 
147 	public List<JavaType> getThrowsTypes() {
148 		return throwsTypes;
149 	}
150 
151 	public String getBody() {
152 		return body;
153 	}
154 
155 	public String toString() {
156 		ToStringCreator tsc = new ToStringCreator(this);
157 		tsc.append("declaredByMetadataId", declaredByMetadataId);
158 		tsc.append("modifier", Modifier.toString(modifier));
159 		tsc.append("methodName", methodName);
160 		tsc.append("parameterTypes", parameterTypes);
161 		tsc.append("parameterNames", parameterNames);
162 		tsc.append("returnType", returnType);
163 		tsc.append("annotations", annotations);
164 		tsc.append("body", body);
165 		return tsc.toString();
166 	}
167 
168 	public static void addMethod(CompilationUnitServices compilationUnitServices, List<BodyDeclaration> members, MethodMetadata method, boolean permitFlush, Set<JavaSymbolName> typeParameters) {
169 		Assert.notNull(compilationUnitServices, "Compilation unit services required");
170 		Assert.notNull(members, "Members required");
171 		Assert.notNull(method, "Method required");
172 		
173 		if (typeParameters == null) {
174 			typeParameters = new HashSet<JavaSymbolName>();
175 		}
176 		
177 		// Create the return type we should use
178 		Type returnType = null;
179 		if (method.getReturnType().isPrimitive()) {
180 			returnType = JavaParserUtils.getType(method.getReturnType());
181 		} else {
182 			NameExpr importedType = JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), method.getReturnType());
183 			ClassOrInterfaceType cit = JavaParserUtils.getClassOrInterfaceType(importedType);
184 			
185 			// Add any type arguments presented for the return type
186 			if (method.getReturnType().getParameters().size() > 0) {
187 				List<Type> typeArgs = new ArrayList<Type>();
188 				cit.setTypeArgs(typeArgs);
189 				for (JavaType parameter : method.getReturnType().getParameters()) {
190 					//NameExpr importedParameterType = JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), parameter);
191 					//typeArgs.add(JavaParserUtils.getReferenceType(importedParameterType));
192 					typeArgs.add(JavaParserUtils.importParametersForType(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), parameter));
193 				}
194 			}
195 			
196 			// Handle arrays
197 			if (method.getReturnType().isArray()) {
198 				ReferenceType rt = new ReferenceType();
199 				rt.setArrayCount(method.getReturnType().getArray());
200 				rt.setType(cit);
201 				returnType = rt;
202 			} else {
203 				returnType = cit;
204 			}
205 		}
206 		
207 		// Start with the basic method
208 		MethodDeclaration d = new MethodDeclaration();
209 		d.setModifiers(JavaParserUtils.getJavaParserModifier(method.getModifier()));
210 		d.setName(method.getMethodName().getSymbolName());
211 		d.setType(returnType);
212 		
213 		// Add any method-level annotations (not parameter annotations)
214 		List<AnnotationExpr> annotations = new ArrayList<AnnotationExpr>();
215 		d.setAnnotations(annotations);
216 		for (AnnotationMetadata annotation : method.getAnnotations()) {
217 			JavaParserAnnotationMetadata.addAnnotationToList(compilationUnitServices, annotations, annotation, false);
218 		}
219 	
220 		// Add any method parameters, including their individual annotations and type parameters
221 		List<Parameter> parameters = new ArrayList<Parameter>();
222 		d.setParameters(parameters);
223 		int index = -1;
224 		for (AnnotatedJavaType methodParameter : method.getParameterTypes()) {
225 			index++;
226 
227 			// Add the parameter annotations applicable for this parameter type
228 			List<AnnotationExpr> parameterAnnotations = new ArrayList<AnnotationExpr>();
229 	
230 			for (AnnotationMetadata parameterAnnotation : methodParameter.getAnnotations()) {
231 				JavaParserAnnotationMetadata.addAnnotationToList(compilationUnitServices, parameterAnnotations, parameterAnnotation, false);
232 			}
233 			
234 			// Compute the parameter name
235 			String parameterName = method.getParameterNames().get(index).getSymbolName();
236 			
237 			// Compute the parameter type
238 			Type parameterType = null;
239 			if (methodParameter.getJavaType().isPrimitive()) {
240 				parameterType = JavaParserUtils.getType(methodParameter.getJavaType());
241 			} else {
242 				NameExpr importedType = JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), methodParameter.getJavaType());
243 				ClassOrInterfaceType cit = JavaParserUtils.getClassOrInterfaceType(importedType);
244 				
245 				// Add any type arguments presented for the return type
246 				if (methodParameter.getJavaType().getParameters().size() > 0) {
247 					List<Type> typeArgs = new ArrayList<Type>();
248 					cit.setTypeArgs(typeArgs);
249 					for (JavaType parameter : methodParameter.getJavaType().getParameters()) {
250 						NameExpr importedParameterType = JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), parameter);
251 						typeArgs.add(JavaParserUtils.getReferenceType(importedParameterType));
252 					}
253 					
254 				}
255 				parameterType = cit;
256 			}
257 
258 			// Add exceptions which the method my throw
259 			if (method.getThrowsTypes().size() > 0) {
260 				List<NameExpr> throwsTypes = new ArrayList<NameExpr>();
261 				for (JavaType javaType: method.getThrowsTypes()) {
262 					NameExpr importedType = JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), javaType);
263 					throwsTypes.add(importedType);
264 				}
265 				d.setThrows(throwsTypes);
266 			}
267 			
268 			// Create a Java Parser method parameter and add it to the list of parameters
269 			Parameter p = new Parameter(parameterType, new VariableDeclaratorId(parameterName));
270 			p.setAnnotations(parameterAnnotations);
271 			parameters.add(p);
272 		}
273 		
274 		// Set the body
275 		if (method.getBody() == null || method.getBody().length() == 0) {
276 			// Never set the body if an abstract method
277 			if (!Modifier.isAbstract(method.getModifier())) {
278 				d.setBody(new BlockStmt());
279 			}
280 		} else {
281 			// There is a body.
282 			// We need to make a fake method that we can have JavaParser parse.
283 			// Easiest way to do that is to build a simple source class containing the required method and re-parse it.
284 			StringBuilder sb = new StringBuilder();
285 			sb.append("class TemporaryClass {\n");
286 			sb.append("  public void temporaryMethod() {\n");
287 			sb.append(method.getBody());
288 			sb.append("\n");
289 			sb.append("  }\n");
290 			sb.append("}\n");
291 			ByteArrayInputStream bais = new ByteArrayInputStream(sb.toString().getBytes());
292 			CompilationUnit ci;
293 			try {
294 				ci = JavaParser.parse(bais);
295 			} catch (ParseException pe) {
296 				throw new IllegalStateException("Illegal state: JavaParser did not parse correctly", pe);
297 			}
298 			List<TypeDeclaration> types = ci.getTypes();
299 			if (types == null || types.size() != 1) {
300 				throw new IllegalArgumentException("Method body invalid");
301 			}
302 			TypeDeclaration td = types.get(0);
303 			List<BodyDeclaration> bodyDeclarations = td.getMembers();
304 			if (bodyDeclarations == null || bodyDeclarations.size() != 1) {
305 				throw new IllegalStateException("Illegal state: JavaParser did not return body declarations correctly");
306 			}
307 			BodyDeclaration bd = bodyDeclarations.get(0);
308 			if (!(bd instanceof MethodDeclaration)) {
309 				throw new IllegalStateException("Illegal state: JavaParser did not return a method declaration correctly");
310 			}
311 			MethodDeclaration md = (MethodDeclaration) bd;
312 			d.setBody(md.getBody());
313 		}
314 	
315 		// Locate where to add this method; also verify if this method already exists
316 		for (BodyDeclaration bd : members) {
317 			if (bd instanceof MethodDeclaration) {
318 				// Next method should appear after this current method
319 				MethodDeclaration md = (MethodDeclaration) bd;
320 				if (md.getName().equals(d.getName()) && md.getParameters().size() == d.getParameters().size()) {
321 					// Possible match, we need to consider parameter types as well now
322 					JavaParserMethodMetadata jpmm = new JavaParserMethodMetadata(method.getDeclaredByMetadataId(), md, compilationUnitServices, typeParameters);
323 					boolean matchesFully = true;
324 					for (AnnotatedJavaType existingParameter : jpmm.getParameterTypes()) {
325 						if (!existingParameter.getJavaType().equals(method.getParameterTypes().get(index))) {
326 							matchesFully = false;
327 							break;
328 						}
329 					}
330 					if (matchesFully) {
331 						throw new IllegalStateException("Method '" + method.getMethodName().getSymbolName() + "' already exists with identical parameters");
332 					}
333 				}
334 			}
335 		}
336 	
337 		// Add the method to the end of the compilation unit
338 		members.add(d);
339 		
340 		if (permitFlush) {
341 			compilationUnitServices.flush();
342 		}
343 	}
344 }