View Javadoc

1   package org.springframework.roo.classpath.javaparser.details;
2   
3   import japa.parser.ASTHelper;
4   import japa.parser.JavaParser;
5   import japa.parser.ParseException;
6   import japa.parser.ast.CompilationUnit;
7   import japa.parser.ast.body.BodyDeclaration;
8   import japa.parser.ast.body.FieldDeclaration;
9   import japa.parser.ast.body.TypeDeclaration;
10  import japa.parser.ast.body.VariableDeclarator;
11  import japa.parser.ast.expr.AnnotationExpr;
12  import japa.parser.ast.expr.Expression;
13  import japa.parser.ast.expr.NameExpr;
14  import japa.parser.ast.type.ClassOrInterfaceType;
15  import japa.parser.ast.type.Type;
16  
17  import java.io.ByteArrayInputStream;
18  import java.lang.reflect.Modifier;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.Set;
23  
24  import org.springframework.roo.classpath.details.FieldMetadata;
25  import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
26  import org.springframework.roo.classpath.javaparser.CompilationUnitServices;
27  import org.springframework.roo.classpath.javaparser.JavaParserUtils;
28  import org.springframework.roo.model.JavaSymbolName;
29  import org.springframework.roo.model.JavaType;
30  import org.springframework.roo.support.style.ToStringCreator;
31  import org.springframework.roo.support.util.Assert;
32  
33  /**
34   * Java Parser implementation of {@link FieldMetadata}.
35   * 
36   * @author Ben Alex
37   * @since 1.0
38   *
39   */
40  public class JavaParserFieldMetadata implements FieldMetadata {
41  	private JavaType fieldType;
42  	private String fieldInitializer;
43  	private JavaSymbolName fieldName;
44  	private List<AnnotationMetadata> annotations = new ArrayList<AnnotationMetadata>();
45  	private String declaredByMetadataId;
46  	private int modifier;
47  
48  	public JavaParserFieldMetadata(String declaredByMetadataId, FieldDeclaration fieldDeclaration, VariableDeclarator var, CompilationUnitServices compilationUnitServices, Set<JavaSymbolName> typeParameters) {
49  		Assert.notNull(declaredByMetadataId, "Declared by metadata ID required");
50  		Assert.notNull(fieldDeclaration, "Field declaration is mandatory");
51  		Assert.notNull(var, "Variable declarator required");
52  		Assert.isTrue(fieldDeclaration.getVariables().contains(var), "Cannot request a variable not already in the field declaration");
53  		Assert.notNull(compilationUnitServices, "Compilation unit services are required");
54  		
55  		// Convert Java Parser modifier into JDK modifier
56  		this.modifier = JavaParserUtils.getJdkModifier(fieldDeclaration.getModifiers());
57  		
58  		this.declaredByMetadataId = declaredByMetadataId;
59  		
60  		Type type = fieldDeclaration.getType();
61  		this.fieldType = JavaParserUtils.getJavaType(compilationUnitServices, type, typeParameters);
62  		
63  		// Convert into an array if this variable ID uses array notation
64  		if (var.getId().getArrayCount() > 0) {
65  			this.fieldType = new JavaType(fieldType.getFullyQualifiedTypeName(), var.getId().getArrayCount() + fieldType.getArray(), fieldType.getDataType(), fieldType.getArgName(), fieldType.getParameters());
66  		}
67  		
68  		this.fieldName = new JavaSymbolName(var.getId().getName());
69  		
70  		// Lookup initializer, if one was requested and easily determinable
71  		Expression e = var.getInit();
72  		if (e != null) {
73  			this.fieldInitializer = e.toString();
74  		}
75  		
76  		List<AnnotationExpr> annotations = fieldDeclaration.getAnnotations();
77  		if (annotations != null) {
78  			for (AnnotationExpr annotation : annotations) {
79  				this.annotations.add(new JavaParserAnnotationMetadata(annotation, compilationUnitServices));
80  			}
81  		}
82  	}
83  
84  	public int getModifier() {
85  		return modifier;
86  	}
87  
88  	public String getDeclaredByMetadataId() {
89  		return declaredByMetadataId;
90  	}
91  
92  	public List<AnnotationMetadata> getAnnotations() {
93  		return Collections.unmodifiableList(annotations);
94  	}
95  
96  	public JavaSymbolName getFieldName() {
97  		return fieldName;
98  	}
99  
100 	public JavaType getFieldType() {
101 		return fieldType;
102 	}
103 	
104 	public static void addField(CompilationUnitServices compilationUnitServices, List<BodyDeclaration> members, FieldMetadata field, boolean permitFlush) {
105 		Assert.notNull(compilationUnitServices, "Compilation unit services required");
106 		Assert.notNull(members, "Members required");
107 		Assert.notNull(field, "Field required");
108 		
109 		// Import the field type into the compilation unit
110 		NameExpr importedType = JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), field.getFieldType());
111 		ClassOrInterfaceType fieldType = JavaParserUtils.getClassOrInterfaceType(importedType);
112 		
113 		FieldDeclaration newField = ASTHelper.createFieldDeclaration(JavaParserUtils.getJavaParserModifier(field.getModifier()), fieldType, field.getFieldName().getSymbolName());
114 		
115 		// Add parameterized types for the field type (not initializer)
116 		if (field.getFieldType().getParameters().size() > 0) {
117 			List<Type> fieldTypeArgs = new ArrayList<Type>();
118 			fieldType.setTypeArgs(fieldTypeArgs);
119 			for (JavaType parameter : field.getFieldType().getParameters()) {
120 //				NameExpr importedParameterType = JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), parameter);
121 //				fieldTypeArgs.add(JavaParserUtils.getReferenceType(importedParameterType));
122 				fieldTypeArgs.add(JavaParserUtils.importParametersForType(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), parameter));
123 			}
124 		}
125 		
126 		List<VariableDeclarator> vars = newField.getVariables();
127 		Assert.notEmpty(vars, "Expected ASTHelper to have provided a single VariableDeclarator");
128 		Assert.isTrue(vars.size() == 1, "Expected ASTHelper to have provided a single VariableDeclarator");
129 		VariableDeclarator vd = vars.iterator().next();
130 
131 		if (field.getFieldInitializer() != null && field.getFieldInitializer().length() > 0) {
132 			// There is an initializer.
133 			// We need to make a fake field that we can have JavaParser parse.
134 			// Easiest way to do that is to build a simple source class containing the required field and re-parse it.
135 			StringBuilder sb = new StringBuilder();
136 			sb.append("class TemporaryClass {\n");
137 			sb.append("  private " + field.getFieldType()  + " " + field.getFieldName() + " = " + field.getFieldInitializer() + ";\n");
138 			sb.append("}\n");
139 			ByteArrayInputStream bais = new ByteArrayInputStream(sb.toString().getBytes());
140 			CompilationUnit ci;
141 			try {
142 				ci = JavaParser.parse(bais);
143 			} catch (ParseException pe) {
144 				throw new IllegalStateException("Illegal state: JavaParser did not parse correctly", pe);
145 			}
146 			List<TypeDeclaration> types = ci.getTypes();
147 			if (types == null || types.size() != 1) {
148 				throw new IllegalArgumentException("Field member invalid");
149 			}
150 			TypeDeclaration td = types.get(0);
151 			List<BodyDeclaration> bodyDeclarations = td.getMembers();
152 			if (bodyDeclarations == null || bodyDeclarations.size() != 1) {
153 				throw new IllegalStateException("Illegal state: JavaParser did not return body declarations correctly");
154 			}
155 			BodyDeclaration bd = bodyDeclarations.get(0);
156 			if (!(bd instanceof FieldDeclaration)) {
157 				throw new IllegalStateException("Illegal state: JavaParser did not return a field declaration correctly");
158 			}
159 			FieldDeclaration fd = (FieldDeclaration) bd;
160 			if (fd.getVariables() == null || fd.getVariables().size() != 1) {
161 				throw new IllegalStateException("Illegal state: JavaParser did not return a field declaration correctly");
162 			}
163 			
164 			Expression init = fd.getVariables().get(0).getInit();
165 
166 			// TODO: resolve imports (?)
167 
168 			vd.setInit(init);
169 		}
170 		
171 		// Add annotations
172 		List<AnnotationExpr> annotations = new ArrayList<AnnotationExpr>();
173 		newField.setAnnotations(annotations);
174 		for (AnnotationMetadata annotation : field.getAnnotations()) {
175 			JavaParserAnnotationMetadata.addAnnotationToList(compilationUnitServices, annotations, annotation, false);
176 		}
177 		
178 
179 		// Locate where to add this field; also verify if this field already exists
180 		int nextFieldIndex = 0;
181 		int i = -1;
182 		for (BodyDeclaration bd : members) {
183 			i++;
184 			if (bd instanceof FieldDeclaration) {
185 				// Next field should appear after this current field
186 				nextFieldIndex = i + 1;
187 				FieldDeclaration bdf = (FieldDeclaration) bd;
188 				for (VariableDeclarator v : bdf.getVariables()) {
189 					Assert.isTrue(!field.getFieldName().getSymbolName().equals(v.getId().getName()), "A field with name '" + field.getFieldName().getSymbolName() + "' already exists");
190 				}
191 			}
192 		}
193 
194 		// Add the field to the compilation unit
195 		members.add(nextFieldIndex, newField);
196 		
197 		if (permitFlush) {
198 			compilationUnitServices.flush();
199 		}
200 	}
201 	
202 	public static void removeField(CompilationUnitServices compilationUnitServices, List<BodyDeclaration> members, JavaSymbolName fieldName) {
203 		Assert.notNull(compilationUnitServices, "Compilation unit services required");
204 		Assert.notNull(members, "Members required");
205 		Assert.notNull(fieldName, "Field name to remove is required");
206 		
207 		// Locate the field
208 		int i = -1;
209 		int toDelete = -1;
210 		for (BodyDeclaration bd : members) {
211 			i++;
212 			if (bd instanceof FieldDeclaration) {
213 				FieldDeclaration fieldDeclaration = (FieldDeclaration) bd;
214 				for (VariableDeclarator var : fieldDeclaration.getVariables()) {
215 					if (var.getId().getName().equals(fieldName.getSymbolName())) {
216 						toDelete = i;
217 						break;
218 					}
219 				}
220 			}
221 		}
222 		
223 		Assert.isTrue(toDelete > -1, "Could not locate field '" + fieldName + "' to delete");
224 		
225 		// Do removal outside iteration of body declaration members, to avoid concurrent modification exceptions
226 		members.remove(toDelete);
227 
228 		compilationUnitServices.flush();
229 	}
230 
231 	public String getFieldInitializer() {
232 		return this.fieldInitializer;
233 	}
234 
235 	public String toString() {
236 		ToStringCreator tsc = new ToStringCreator(this);
237 		tsc.append("declaredByMetadataId", declaredByMetadataId);
238 		tsc.append("modifier", Modifier.toString(modifier));
239 		tsc.append("fieldType", fieldType);
240 		tsc.append("fieldName", fieldName);
241 		tsc.append("fieldInitializer", fieldInitializer);
242 		tsc.append("annotations", annotations);
243 		return tsc.toString();
244 	}
245 
246 }