View Javadoc

1   package org.springframework.roo.classpath.itd;
2   
3   import java.lang.reflect.Modifier;
4   import java.util.List;
5   import java.util.SortedSet;
6   import java.util.TreeSet;
7   
8   import org.springframework.roo.classpath.details.AnnotationMetadataUtils;
9   import org.springframework.roo.classpath.details.ConstructorMetadata;
10  import org.springframework.roo.classpath.details.DeclaredFieldAnnotationDetails;
11  import org.springframework.roo.classpath.details.DeclaredMethodAnnotationDetails;
12  import org.springframework.roo.classpath.details.FieldMetadata;
13  import org.springframework.roo.classpath.details.ItdTypeDetails;
14  import org.springframework.roo.classpath.details.MethodMetadata;
15  import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
16  import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
17  import org.springframework.roo.model.ImportRegistrationResolver;
18  import org.springframework.roo.model.ImportRegistrationResolverImpl;
19  import org.springframework.roo.model.JavaSymbolName;
20  import org.springframework.roo.model.JavaType;
21  import org.springframework.roo.support.util.Assert;
22  
23  /**
24   * A simple way of producing an inter-type declaration source file.
25   * 
26   * @author Ben Alex
27   * @author Stefan Schmidt
28   * @since 1.0
29   *
30   */
31  public class ItdSourceFileComposer {
32  	
33  	private int indentLevel = 0;
34  	
35  	private JavaType introductionTo;
36  	private StringBuilder pw = new StringBuilder();
37  	private boolean content;
38  	private ItdTypeDetails itdTypeDetails;
39  	private ImportRegistrationResolver resolver;
40  	private JavaType aspect;
41  
42  	/**
43  	 * Constructs an {@link ItdSourceFileComposer} containing the members that were requested in
44  	 * the passed object.
45  	 * 
46  	 * @param itdTypeDetails to construct (required)
47  	 */
48  	public ItdSourceFileComposer(ItdTypeDetails itdTypeDetails) {
49  		Assert.notNull(itdTypeDetails, "ITD type details required");
50  
51  		this.itdTypeDetails = itdTypeDetails;
52  		Assert.notNull(itdTypeDetails.getName(), "Introduction to is required");
53  		this.introductionTo = itdTypeDetails.getName();
54  		
55  		this.aspect = itdTypeDetails.getAspect();
56  
57  		// Create my own resolver, so we can add items to it as we process
58  		resolver = new ImportRegistrationResolverImpl(itdTypeDetails.getAspect().getPackage());
59  		
60  		for (JavaType registeredImport : itdTypeDetails.getRegisteredImports()) {
61  			// Do a sanity check in case the user misused it
62  			if (resolver.isAdditionLegal(registeredImport)) {
63  				resolver.addImport(registeredImport);
64  			}
65  		}
66  
67  		appendTypeDeclaration();
68  		appendExtendsTypes();
69  		appendImplementsTypes();
70  		appendTypeAnnotations();
71  		appendFieldAnnotations();
72  		appendMethodAnnotations();
73  		appendFields();
74  		appendConstructors();
75  		appendMethods();
76  		appendTerminator();
77  		
78  		// Now prepend the package declaration and any imports
79  		// We need to do this ** at the end ** so we can ensure our compilation unit imports are correct, as they're built as we traverse over the other members
80  		prependCompilationUnitDetails();
81  	}
82  	
83  	private void prependCompilationUnitDetails() {
84  		StringBuilder topOfFile = new StringBuilder();
85  
86  		// Note we're directly interacting with the top of file string builder
87  		if (!aspect.isDefaultPackage()) {
88  			topOfFile.append("package " + aspect.getPackage().getFullyQualifiedPackageName() + ";").append(getNewLine());
89  			topOfFile.append(getNewLine());
90  		}
91  		
92  		// Ordered to ensure consistency of output
93  		SortedSet<JavaType> types = new TreeSet<JavaType>();
94  		types.addAll(resolver.getRegisteredImports());
95  		if (types.size() > 0) {
96  			for (JavaType importType : types) {
97  				topOfFile.append("import " + importType.getFullyQualifiedTypeName() + ";").append(getNewLine());
98  			}
99  			
100 			topOfFile.append(getNewLine());
101 		}
102 		
103 		// Now append the normal file to the bottom
104 		topOfFile.append(pw.toString());
105 		
106 		// Replace the old writer with out new writer
107 		this.pw = topOfFile;
108 	}
109 	
110 	private void appendTypeDeclaration() {
111 		Assert.isTrue(introductionTo.getPackage().equals(aspect.getPackage()), "Aspect and introduction must be in identical packages");
112 		
113 		this.appendIndent();
114 		if (itdTypeDetails.isPrivilegedAspect()) {
115 			this.append("privileged ");
116 		}
117 		this.append("aspect " + aspect.getSimpleTypeName() + " {");
118 		this.newLine(false);
119 		this.indent();
120 		this.newLine();
121 
122 		// Set to false, as it was set true during the above operations
123 		content = false;
124 	}
125 
126 	private void outputAnnotation(AnnotationMetadata annotation) {
127 		this.append(AnnotationMetadataUtils.toSourceForm(annotation, resolver));
128 	}
129 	
130 	private void appendTypeAnnotations() {
131 		List<? extends AnnotationMetadata> typeAnnotations = itdTypeDetails.getTypeAnnotations();
132 		if (typeAnnotations == null || typeAnnotations.size() == 0) {
133 			return;
134 		}
135 		
136 		content = true;
137 		
138 		for (AnnotationMetadata typeAnnotation : typeAnnotations) {
139 			this.appendIndent();
140 			this.append("declare @type: ");
141 			this.append(introductionTo.getSimpleTypeName());
142 			this.append(": ");
143 			outputAnnotation(typeAnnotation);
144 			this.append(";");
145 			this.newLine(false);
146 			this.newLine();
147 		}
148 	}
149 	
150 	private void appendFieldAnnotations() {
151 		List<DeclaredFieldAnnotationDetails> fieldAnnotations = itdTypeDetails.getFieldAnnotations();
152 		if (fieldAnnotations == null || fieldAnnotations.size() == 0) {
153 			return;
154 		}
155 		
156 		content = true;
157 		
158 		for (DeclaredFieldAnnotationDetails fieldDetails : fieldAnnotations) {	
159 			this.appendIndent();
160 			this.append("declare @field: * ");
161 			this.append(introductionTo.getSimpleTypeName());
162 			this.append(".");
163 			this.append(fieldDetails.getFieldMetadata().getFieldName().getSymbolName());
164 			this.append(": ");
165 			outputAnnotation(fieldDetails.getFieldAnnotation());
166 			this.append(";");
167 			this.newLine(false);
168 			this.newLine();
169 		}
170 	}
171 	
172 	private void appendMethodAnnotations() {
173 		List<DeclaredMethodAnnotationDetails> methodAnnotations = itdTypeDetails.getMethodAnnotations();
174 		if (methodAnnotations == null || methodAnnotations.size() == 0) {
175 			return;
176 		}
177 		
178 		content = true;
179 		
180 		for (DeclaredMethodAnnotationDetails methodDetails : methodAnnotations) {	
181 			this.appendIndent();
182 			this.append("declare @method: ");
183 			this.append(Modifier.toString(methodDetails.getMethodMetadata().getModifier()));
184 			this.append(" ");
185 			this.append(methodDetails.getMethodMetadata().getReturnType().getNameIncludingTypeParameters());
186 			this.append(" ");
187 			this.append(introductionTo.getSimpleTypeName());
188 			this.append(".");
189 			this.append(methodDetails.getMethodMetadata().getMethodName().getSymbolName());
190 			this.append("(");
191 			for (int i = 0; i < methodDetails.getMethodMetadata().getParameterTypes().size(); i++) {
192 				this.append(methodDetails.getMethodMetadata().getParameterTypes().get(i).getJavaType().getNameIncludingTypeParameters(false, resolver));
193 				if (i != methodDetails.getMethodMetadata().getParameterTypes().size() - 1) {
194 					this.append(",");
195 				}
196 			}
197 			this.append("): ");
198 			outputAnnotation(methodDetails.getMethodAnnotation());
199 			this.append(";");
200 			this.newLine(false);
201 			this.newLine();
202 		}
203 	}
204 
205 	private void appendExtendsTypes() {
206 		List<JavaType> extendsTypes = itdTypeDetails.getExtendsTypes();
207 		if (extendsTypes == null || extendsTypes.size() == 0) {
208 			return;
209 		}
210 		
211 		content = true;
212 		
213 		for (JavaType extendsType : extendsTypes) {
214 			this.appendIndent();
215 			this.append("declare parents: ");
216 			this.append(introductionTo.getSimpleTypeName());
217 			this.append(" extends ");
218 			if (resolver.isFullyQualifiedFormRequiredAfterAutoImport(extendsType)) {
219 				this.append(extendsType.getNameIncludingTypeParameters());
220 			} else {
221 				this.append(extendsType.getNameIncludingTypeParameters(false, resolver));
222 			}
223 			this.append(";");
224 			this.newLine(false);
225 			this.newLine();
226 		}
227 	}
228 
229 	private void appendImplementsTypes() {
230 		List<JavaType> implementsTypes = itdTypeDetails.getImplementsTypes();
231 		
232 		if (implementsTypes == null || implementsTypes.size() == 0) {
233 			return;
234 		}
235 		
236 		content = true;
237 		
238 		for (JavaType extendsType : implementsTypes) {
239 			this.appendIndent();
240 			this.append("declare parents: ");
241 			this.append(introductionTo.getSimpleTypeName());
242 			this.append(" implements ");
243 			if (resolver.isFullyQualifiedFormRequiredAfterAutoImport(extendsType)) {
244 				this.append(extendsType.getNameIncludingTypeParameters());
245 			} else {
246 				this.append(extendsType.getNameIncludingTypeParameters(false, resolver));
247 			}
248 			this.append(";");
249 			this.newLine(false);
250 			this.newLine();
251 		}
252 	}
253 
254 	private void appendConstructors() {
255 		List<? extends ConstructorMetadata> constructors = itdTypeDetails.getDeclaredConstructors();
256 		if (constructors == null || constructors.size() == 0) {
257 			return;
258 		}
259 		content = true;
260 		for (ConstructorMetadata constructor : constructors) {
261 			Assert.isTrue(constructor.getParameterTypes().size() == constructor.getParameterNames().size(), "Mismatched parameter names against parameter types");
262 			
263 			// Append annotations
264 			for (AnnotationMetadata annotation : constructor.getAnnotations()) {
265 				this.appendIndent();
266 				outputAnnotation(annotation);
267 				this.newLine(false);
268 			}
269 			
270 			// Append "<modifier> <TargetOfIntroduction>.new" portion
271 			this.appendIndent();
272 			if (constructor.getModifier() != 0) {
273 				this.append(Modifier.toString(constructor.getModifier()));
274 				this.append(" ");
275 			}
276 			this.append(introductionTo.getSimpleTypeName());
277 			this.append(".");
278 			this.append("new");
279 
280 			// Append parameter types and names
281 			this.append("(");
282 			List<AnnotatedJavaType> paramTypes = constructor.getParameterTypes();
283 			List<JavaSymbolName> paramNames = constructor.getParameterNames();
284 			for (int i = 0 ; i < paramTypes.size(); i++) {
285 				AnnotatedJavaType paramType = paramTypes.get(i);
286 				JavaSymbolName paramName = paramNames.get(i);
287 				for (AnnotationMetadata methodParameterAnnotation : paramType.getAnnotations()) {
288 					this.append(AnnotationMetadataUtils.toSourceForm(methodParameterAnnotation));
289 					this.append(" ");
290 				}
291 				this.append(paramType.getJavaType().getNameIncludingTypeParameters(false, resolver));
292 				this.append(" ");
293 				this.append(paramName.getSymbolName());
294 				if (i < paramTypes.size() - 1) {
295 					this.append(", ");
296 				}
297 			}
298 			this.append(") {");
299 			this.newLine(false);
300 			this.indent();
301 
302 			// Add body
303 			this.append(constructor.getBody());
304 			this.indentRemove();
305 			this.appendFormalLine("}");
306 			this.newLine(false);
307 		}
308 	}
309 	
310 	private void appendMethods() {
311 		List<? extends MethodMetadata> methods = itdTypeDetails.getDeclaredMethods();
312 		if (methods == null || methods.size() == 0) {
313 			return;
314 		}
315 		content = true;
316 		for (MethodMetadata method : methods) {
317 			Assert.isTrue(method.getParameterTypes().size() == method.getParameterNames().size(), "Mismatched parameter names against parameter types");
318 			
319 			// Append annotations
320 			for (AnnotationMetadata annotation : method.getAnnotations()) {
321 				this.appendIndent();
322 				outputAnnotation(annotation);
323 				this.newLine(false);
324 			}
325 			
326 			// Append "<modifier> <returntype> <methodName>" portion
327 			this.appendIndent();
328 			if (method.getModifier() != 0) {
329 				this.append(Modifier.toString(method.getModifier()));
330 				this.append(" ");
331 			}
332 
333 			// return type
334 			boolean staticMethod = Modifier.isStatic(method.getModifier());
335 			this.append(method.getReturnType().getNameIncludingTypeParameters(staticMethod, resolver));
336 			this.append(" ");
337 			this.append(introductionTo.getSimpleTypeName());
338 			this.append(".");
339 			this.append(method.getMethodName().getSymbolName());
340 
341 			// Append parameter types and names
342 			this.append("(");
343 			List<AnnotatedJavaType> paramTypes = method.getParameterTypes();
344 			List<JavaSymbolName> paramNames = method.getParameterNames();
345 			for (int i = 0 ; i < paramTypes.size(); i++) {
346 				AnnotatedJavaType paramType = paramTypes.get(i);
347 				JavaSymbolName paramName = paramNames.get(i);
348 				for (AnnotationMetadata methodParameterAnnotation : paramType.getAnnotations()) {
349 					outputAnnotation(methodParameterAnnotation);
350 					this.append(" ");
351 				}
352 				this.append(paramType.getJavaType().getNameIncludingTypeParameters(false, resolver));
353 				this.append(" ");
354 				this.append(paramName.getSymbolName());
355 				if (i < paramTypes.size() - 1) {
356 					this.append(", ");
357 				}
358 			}
359 			
360 			// add exceptions to be thrown
361 			List<JavaType> throwsTypes = method.getThrowsTypes();
362 			if (throwsTypes.size() > 0) {
363 				this.append(") throws ");
364 				for (int i = 0; i < throwsTypes.size(); i++) {
365 					this.append(throwsTypes.get(i).getNameIncludingTypeParameters(false, resolver));
366 					if (throwsTypes.size() > (i+1)) {
367 						this.append(", ");
368 					}
369 				}
370 				this.append(" {");
371 			} else {
372 				this.append(") {");
373 			}
374 			
375 			this.newLine(false);
376 			this.indent();
377 
378 			// Add body
379 			this.append(method.getBody());
380 			this.indentRemove();
381 			this.appendFormalLine("}");
382 			this.newLine();
383 		}
384 	}
385 	
386 	private void appendFields() {
387 		List<? extends FieldMetadata> fields = itdTypeDetails.getDeclaredFields();
388 		if (fields == null || fields.size() == 0) {
389 			return;
390 		}
391 		content = true;
392 		for (FieldMetadata field : fields) {
393 			
394 			// Append annotations
395 			for (AnnotationMetadata annotation : field.getAnnotations()) {
396 				this.appendIndent();
397 				outputAnnotation(annotation);
398 				this.newLine(false);
399 			}
400 			
401 			// Append "<modifier> <fieldtype> <fieldName>" portion
402 			this.appendIndent();
403 			if (field.getModifier() != 0) {
404 				this.append(Modifier.toString(field.getModifier()));
405 				this.append(" ");
406 			}
407 			this.append(field.getFieldType().getNameIncludingTypeParameters(false, resolver));
408 			this.append(" ");
409 			this.append(introductionTo.getSimpleTypeName());
410 			this.append(".");
411 			this.append(field.getFieldName().getSymbolName());
412 
413 			// Append initializer, if present
414 			if (field.getFieldInitializer() != null) {
415 				this.append(" = ");
416 				this.append(field.getFieldInitializer());
417 			}
418 			
419 			// Complete the field declaration
420 			this.append(";");
421 			this.newLine(false);
422 			this.newLine();
423 		}
424 	}
425 
426 	/**
427 	 * Increases the indent by one level.
428 	 */
429 	private ItdSourceFileComposer indent() {
430 		indentLevel++;
431 		return this;
432 	}
433 
434 	/**
435 	 * Decreases the indent by one level.
436 	 */
437 	private ItdSourceFileComposer indentRemove() {
438 		indentLevel--;
439 		return this;
440 	}
441 
442 	/**
443 	 * Prints a blank line, ensuring any indent is included before doing so.
444 	 */
445 	private ItdSourceFileComposer newLine() {
446 		return newLine(true);
447 	}
448 	
449 	/**
450 	 * Prints a blank line, ensuring any indent is included before doing so.
451 	 */
452 	private ItdSourceFileComposer newLine(boolean indent) {
453 		if (indent) appendIndent();
454         // We use \n for consistency with JavaParser's DumpVisitor, which always uses \n
455 		pw.append(getNewLine());
456 		//pw.append(System.getProperty("line.separator"));
457 		return this;
458 	}
459 
460 	private String getNewLine() {
461         // We use \n for consistency with JavaParser's DumpVisitor, which always uses \n
462 		return ("\n");
463 	}
464 	
465 	/**
466 	 * Prints the message, WITHOUT ANY INDENTATION.
467 	 */
468 	private ItdSourceFileComposer append(String message) {
469 		if (message != null && !"".equals(message)) {
470 			pw.append(message);
471 			content = true;
472 		}
473 		return this;
474 	}
475 
476 	/**
477 	 * Prints the message, after adding indents and returns to a new line. This is the most commonly used method.
478 	 */
479 	private ItdSourceFileComposer appendFormalLine(String message) {
480 		appendIndent();
481 		if (message != null && !"".equals(message)) {
482 			pw.append(message);
483 			content = true;
484 		}
485 		return newLine(false);
486 	}
487 
488 	/**
489 	 * Prints the relevant number of indents.
490 	 */
491 	private ItdSourceFileComposer appendIndent() {
492 		for (int i = 0 ; i < indentLevel; i++) {
493 			pw.append("    ");
494 		}
495 		return this;
496 	}
497 	
498 	private void appendTerminator() {
499 		Assert.isTrue(this.indentLevel == 1, "Indent level must be 1 (not " + indentLevel + ") to conclude!");
500 		this.indentRemove();
501 		
502 		// Ensure we present the content flag, as it will be set true during the formal line append
503 		boolean contentBefore = content;
504 		this.appendFormalLine("}");
505 		content = contentBefore;
506 		
507 	}
508 	
509 	public String getOutput() {
510 		return pw.toString();
511 	}
512 
513 	/**
514 	 * Indicates whether any content was added to the ITD, aside from the formal ITD declaration.
515 	 * 
516 	 * @return true if there is actual content in the ITD, false otherwise
517 	 */
518 	public boolean isContent() {
519 		return content;
520 	}
521 }