View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.devtools.generators.mo;
17  
18  import com.sun.codemodel.ClassType;
19  import com.sun.codemodel.JAnnotationArrayMember;
20  import com.sun.codemodel.JAnnotationUse;
21  import com.sun.codemodel.JBlock;
22  import com.sun.codemodel.JClass;
23  import com.sun.codemodel.JCodeModel;
24  import com.sun.codemodel.JConditional;
25  import com.sun.codemodel.JDefinedClass;
26  import com.sun.codemodel.JDocComment;
27  import com.sun.codemodel.JExpr;
28  import com.sun.codemodel.JFieldRef;
29  import com.sun.codemodel.JFieldVar;
30  import com.sun.codemodel.JInvocation;
31  import com.sun.codemodel.JMethod;
32  import com.sun.codemodel.JMod;
33  import com.sun.codemodel.JType;
34  import com.sun.codemodel.JVar;
35  import com.sun.codemodel.writer.SingleStreamCodeWriter;
36  import org.apache.commons.lang.builder.EqualsBuilder;
37  import org.apache.commons.lang.builder.HashCodeBuilder;
38  import org.apache.commons.lang.builder.ToStringBuilder;
39  import org.kuali.rice.core.api.CoreConstants;
40  import org.kuali.rice.core.api.mo.AbstractDataTransferObject;
41  import org.kuali.rice.core.api.mo.ModelBuilder;
42  
43  import javax.xml.bind.annotation.XmlAccessType;
44  import javax.xml.bind.annotation.XmlAccessorType;
45  import javax.xml.bind.annotation.XmlAnyElement;
46  import javax.xml.bind.annotation.XmlElement;
47  import javax.xml.bind.annotation.XmlRootElement;
48  import javax.xml.bind.annotation.XmlType;
49  import java.io.ByteArrayOutputStream;
50  import java.io.Serializable;
51  import java.lang.reflect.Method;
52  import java.util.ArrayList;
53  import java.util.List;
54  
55  /**
56   * This is a simple utility class which generates an "immutable" object complete with a builder based on a supplied
57   * contract interface definition.
58   * 
59   * @author Kuali Rice Team (rice.collab@kuali.org)
60   *
61   */
62  public class ImmutableJaxbGenerator {
63  
64  	public static void main(String[] args) throws Exception {
65  		
66  		if (args.length > 2 || args.length < 1) {
67  			System.err.println("There should be two arguments defined as follows:\n" + 
68  					"     1. Fully qualified class name of a 'contract' interface\n" + 
69  					"     2. [Optional] Fully qualified class name of the class to generate.  If not specified, will use the name of the contract interface class and remove \"Contract\" from the end of it.\n");
70  			System.exit(1);
71  		}
72  		
73  		// argument one should be a fully qualified class name of a "contract" interface
74  		String contractInterfaceName = args[0];
75  		
76  		String className = null;
77  		// argument two should be the fully qualified class name of the class to generate
78  		if (args.length == 2) {
79  			className = args[1];
80  		} else {
81  			if (!contractInterfaceName.endsWith("Contract")) {
82  				throw new IllegalArgumentException("If not explicitly specifying target classname, then contract class name must end with 'Contract'");
83  			}
84  			className = contractInterfaceName.substring(0, contractInterfaceName.lastIndexOf("Contract"));
85  		}
86  		
87  		Generator generator = new Generator(contractInterfaceName, className);
88  		generator.generate();
89  	}
90  	
91  	public static class Generator {
92  		
93  		private final String contractInterfaceName;
94  		private final String className;
95  		private final JCodeModel codeModel;
96  		
97  		public Generator(String contractInterfaceName, String className) {
98  			this.contractInterfaceName = contractInterfaceName;
99  			this.className = className;
100 			this.codeModel = new JCodeModel();
101 		}
102 		
103 		public void generate() throws Exception {
104 			byte[] javaCode = generateJava();
105 			System.out.println(new String(javaCode));
106 		}
107 		
108 		private byte[] generateJava() throws Exception {
109 			
110 			JDefinedClass classModel = codeModel._class(JMod.PUBLIC | JMod.FINAL, className, ClassType.CLASS);
111 			Class<?> contractInterface = Class.forName(contractInterfaceName);
112 			classModel._implements(contractInterface);
113             classModel._extends(AbstractDataTransferObject.class);
114 			
115 			List<FieldModel> fields = determineFields(contractInterface);
116 			
117 			renderConstantsClass(classModel);
118 			renderElementsClass(classModel, fields);
119 			renderClassLevelAnnotations(classModel, fields);
120 			renderFields(classModel, fields);
121 			renderFutureElementsField(classModel);			
122 			renderPrivateJaxbConstructor(classModel, fields);
123 			renderBuilderConstructor(classModel, fields);
124 			renderGetters(classModel, fields);
125 			renderBuilderClass(classModel, fields, contractInterface);
126 
127 			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
128 			codeModel.build(new SingleStreamCodeWriter(outputStream));
129 			return outputStream.toByteArray();
130 			
131 		}
132 		
133 		private List<FieldModel> determineFields(Class<?> contractInterface) throws Exception {
134 			List<FieldModel> fieldModels = new ArrayList<FieldModel>();
135 			
136 			Method[] methods = contractInterface.getMethods();
137 			for (Method method : methods) {
138 				String methodName = method.getName();
139 				String fieldName = null;
140 				if (method.getReturnType() != Void.class && method.getParameterTypes().length == 0) {
141 					if (methodName.startsWith("get")) {
142 						fieldName = Util.toLowerCaseFirstLetter(methodName.substring(3));
143 					} else if (methodName.startsWith("is")) {
144 						fieldName = Util.toLowerCaseFirstLetter(methodName.substring(2));
145 					} else {
146 						continue;
147 					}
148 					fieldModels.add(new FieldModel(fieldName, method.getReturnType()));
149 				}
150 			}
151 			
152 			return fieldModels;
153 		}
154 		
155 		private void renderConstantsClass(JDefinedClass classModel) throws Exception {
156 			
157 			// define constants class
158 			JDefinedClass constantsClass = classModel._class(JMod.STATIC, Util.CONSTANTS_CLASS_NAME);
159 			
160 			// generate the javadoc on the top of the Constants class
161 			JDocComment javadoc = constantsClass.javadoc();
162 			javadoc.append(Util.CONSTANTS_CLASS_JAVADOC);
163 			
164 			// render root element name
165 			JFieldVar rootElementField = constantsClass.field(JMod.FINAL | JMod.STATIC, String.class, Util.ROOT_ELEMENT_NAME_FIELD);
166 			rootElementField.init(JExpr.lit(Util.toLowerCaseFirstLetter(classModel.name())));
167 			
168 			// render type name
169 			JFieldVar typeNameField = constantsClass.field(JMod.FINAL | JMod.STATIC, String.class, Util.TYPE_NAME_FIELD);
170 			typeNameField.init(JExpr.lit(classModel.name() + Util.TYPE_NAME_SUFFIX));
171             
172 		}
173 		
174 		private void renderElementsClass(JDefinedClass classModel, List<FieldModel> fields) throws Exception {
175 			
176 			// define constants class
177 			JDefinedClass elementsClass = classModel._class(JMod.STATIC, Util.ELEMENTS_CLASS_NAME);
178 			
179 			// generate the javadoc on the top of the Elements class
180 			JDocComment javadoc = elementsClass.javadoc();
181 			javadoc.append(Util.ELEMENTS_CLASS_JAVADOC);
182 			
183 			// go through each field and create a corresponding constant
184 			for (FieldModel fieldModel : fields) {
185 				if (Util.isCommonElement(fieldModel.fieldName)) {
186 					continue;
187 				}
188 				JFieldVar elementFieldVar = elementsClass.field(JMod.FINAL | JMod.STATIC, String.class, Util.toConstantsVariable(fieldModel.fieldName));
189 				elementFieldVar.init(JExpr.lit(fieldModel.fieldName));
190 			}
191 		}
192 		
193 		private void renderClassLevelAnnotations(JDefinedClass classModel, List<FieldModel> fields) throws Exception {
194 			JFieldRef constantsClass = classModel.staticRef(Util.CONSTANTS_CLASS_NAME);
195 			JFieldRef elementsClass = classModel.staticRef(Util.ELEMENTS_CLASS_NAME);
196 			JClass coreConstants = codeModel.ref(CoreConstants.class);
197 			JFieldRef commonElementsRef = coreConstants.staticRef("CommonElements");
198 			
199 			// XmlRootElement
200 			JAnnotationUse rootElementAnnotation = classModel.annotate(XmlRootElement.class);
201 			rootElementAnnotation.param("name", constantsClass.ref(Util.ROOT_ELEMENT_NAME_FIELD));
202 			
203 			// XmlAccessorType
204 			JAnnotationUse xmlAccessorTypeAnnotation = classModel.annotate(XmlAccessorType.class);
205 			xmlAccessorTypeAnnotation.param("value", XmlAccessType.NONE);
206 			
207 			// XmlType
208 			JAnnotationUse xmlTypeAnnotation = classModel.annotate(XmlType.class);
209 			xmlTypeAnnotation.param("name", constantsClass.ref(Util.TYPE_NAME_FIELD));
210 			JAnnotationArrayMember propOrderMember = xmlTypeAnnotation.paramArray("propOrder");
211 			for (FieldModel field : fields) {
212 				if (Util.isCommonElement(field.fieldName)) {
213 					propOrderMember.param(commonElementsRef.ref(Util.toConstantsVariable(field.fieldName)));
214 				} else {
215 					propOrderMember.param(elementsClass.ref(Util.toConstantsVariable(field.fieldName)));
216 				}
217 			}
218 			propOrderMember.param(commonElementsRef.ref("FUTURE_ELEMENTS"));
219 		}
220 		
221 		private void renderFields(JDefinedClass classModel, List<FieldModel> fields) {
222 			for (FieldModel fieldModel : fields) {
223 				renderField(classModel, fieldModel);
224 			}
225 		}
226 		
227 		private void renderField(JDefinedClass classModel, FieldModel fieldModel) {
228 			JFieldVar field = classModel.field(JMod.PRIVATE | JMod.FINAL, fieldModel.fieldType, fieldModel.fieldName);
229 			JAnnotationUse annotation = field.annotate(XmlElement.class);
230 			if (Util.isCommonElement(fieldModel.fieldName)) {
231 				JClass coreConstants = codeModel.ref(CoreConstants.class);
232 				JFieldRef commonElementsRef = coreConstants.staticRef("CommonElements");
233 				annotation.param("name", commonElementsRef.ref(Util.toConstantsVariable(fieldModel.fieldName)));
234 			} else {
235 				JClass elementsClass = codeModel.ref(Util.ELEMENTS_CLASS_NAME);
236 				JFieldRef fieldXmlNameRef = elementsClass.staticRef(Util.toConstantsVariable(fieldModel.fieldName));
237 				annotation.param("name", fieldXmlNameRef);
238 			}
239 			annotation.param("required", false);
240 		}
241 		
242 		private void renderFutureElementsField(JDefinedClass classModel) throws Exception {
243 			JType collectionType = codeModel.parseType("java.util.Collection<org.w3c.dom.Element>");
244 			JFieldVar field = classModel.field(JMod.PRIVATE | JMod.FINAL, collectionType, "_futureElements");
245 			field.init(JExpr._null());
246 			JAnnotationUse annotation = field.annotate(SuppressWarnings.class);
247 			annotation.param("value", "unused");
248 			field.annotate(XmlAnyElement.class);
249 		}
250 		
251 		private void renderPrivateJaxbConstructor(JDefinedClass classModel, List<FieldModel> fields) {
252 			JMethod method = classModel.constructor(JMod.PRIVATE);
253 			JBlock body = method.body();
254 			for (FieldModel fieldModel : fields) {
255 				body.directStatement("this." + fieldModel.fieldName + " = " + getInitString(fieldModel.fieldType) + ";");
256 			}
257 			method.javadoc().add("Private constructor used only by JAXB.");
258 		}
259 
260         private String getInitString(Class<?> clazz) {
261             if (clazz == Boolean.TYPE) {
262                 return "false";
263             } else if (clazz == Character.TYPE) {
264                 return "'\\\\u0000'";
265             } else if (clazz == Long.TYPE) {
266                 return "0L";
267             } else if (clazz == Float.TYPE) {
268                 return "0.0F";
269             } else if (clazz == Double.TYPE) {
270                 return "0.0D";
271             } else if (clazz == Byte.TYPE || clazz == Short.TYPE || clazz == Integer.TYPE) {
272                 return "0";
273             } else {
274                 return "null";
275             }
276         }
277 		
278 		private void renderBuilderConstructor(JDefinedClass classModel, List<FieldModel> fields) {
279 			JMethod method = classModel.constructor(JMod.PRIVATE);
280 			method.param(codeModel.ref("Builder"), "builder");
281 			JBlock body = method.body();
282 			for (FieldModel fieldModel : fields) {
283 				body.directStatement("this." + fieldModel.fieldName + " = builder." + Util.generateGetter(fieldModel.fieldName, isBoolean(fieldModel.fieldType)) + ";");
284 			}
285 		}
286 		
287 		private void renderGetters(JDefinedClass classModel, List<FieldModel> fields) {
288             for (FieldModel fieldModel : fields) {
289                 JMethod getterMethod = classModel.method(JMod.PUBLIC, fieldModel.fieldType, Util.generateGetterName(fieldModel.fieldName, isBoolean(fieldModel.fieldType)));
290 				JBlock methodBody = getterMethod.body();
291 				methodBody.directStatement("return this." + fieldModel.fieldName + ";");
292 				getterMethod.annotate(Override.class);
293 			}
294 		}
295 
296         private boolean isBoolean(Class<?> clazz) {
297             return clazz == Boolean.TYPE || clazz == Boolean.class;
298         }
299 		
300 		private void renderBuilderClass(JDefinedClass classModel, List<FieldModel> fields, Class<?> contractInterface) throws Exception {
301 			
302 			// define constants class
303 			JDefinedClass builderClass = classModel._class(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, Util.BUILDER_CLASS_NAME);
304 			
305 			// create a literal version of the Builder class so that the code generator won't pre-pend Builder class references with outermost class
306 			JClass literalBuilderClass = codeModel.ref("Builder");
307 			
308 			// generate the javadoc on the top of the Elements class
309 			JDocComment javadoc = builderClass.javadoc();
310 			javadoc.append(Util.generateBuilderJavadoc(classModel.name(), contractInterface.getSimpleName()));
311 			
312 			builderClass._implements(contractInterface);
313 			builderClass._implements(ModelBuilder.class);
314 			builderClass._implements(Serializable.class);
315 			
316 			// render the builder fields
317 			for (FieldModel fieldModel : fields) {
318 				builderClass.field(JMod.PRIVATE, fieldModel.fieldType, fieldModel.fieldName);
319 			}
320 			
321 			// render default empty constructor for builder
322 			JMethod constructor = builderClass.constructor(JMod.PRIVATE);
323 			constructor.body().directStatement("// TODO modify this constructor as needed to pass any required values and invoke the appropriate 'setter' methods");
324 		
325 			renderBuilderDefaultCreate(builderClass, literalBuilderClass);
326 			renderBuilderCreateContract(builderClass, literalBuilderClass, fields, contractInterface);
327 			renderBuild(builderClass);
328 			renderGetters(builderClass, fields);
329 			renderSetters(builderClass, fields);
330 		}
331 		
332 		private void renderBuilderDefaultCreate(JDefinedClass builderClass, JClass literalBuilderClass) {
333 			JMethod createMethod = builderClass.method(JMod.PUBLIC | JMod.STATIC, literalBuilderClass, "create");
334 			JBlock createMethodBody = createMethod.body();
335 			createMethodBody.directStatement("// TODO modify as needed to pass any required values and add them to the signature of the 'create' method");
336 			createMethodBody.directStatement("return new Builder();");
337 		}
338 		
339 		private void renderBuilderCreateContract(JDefinedClass builderClass, JClass literalBuilderClass, List<FieldModel> fields, Class<?> contractInterface) {
340 			JMethod createContractMethod = builderClass.method(JMod.PUBLIC | JMod.STATIC, literalBuilderClass, "create");
341 			JVar contractParam = createContractMethod.param(contractInterface, "contract");
342 			JBlock body = createContractMethod.body();
343 			JConditional nullContractCheck = body._if(contractParam.eq(JExpr._null()));
344 			nullContractCheck._then().directStatement("throw new IllegalArgumentException(\"contract was null\");");
345 			body.directStatement("// TODO if create() is modified to accept required parameters, this will need to be modified");
346 			body.directStatement("Builder builder = create();");
347 			for (FieldModel fieldModel : fields) {
348 				String fieldName = fieldModel.fieldName;
349 				body.directStatement("builder." + Util.generateSetter(fieldName, "contract." + Util.generateGetter(fieldName, isBoolean(fieldModel.fieldType))) + ";");
350 			}
351 			body.directStatement("return builder;");
352 		}
353 		
354 		private void renderBuild(JDefinedClass builderClass) {
355 			JMethod buildMethod = builderClass.method(JMod.PUBLIC, builderClass.outer(), "build");
356 			buildMethod.body().directStatement("return new " + builderClass.outer().name() + "(this);");
357 		}
358 		
359 		private void renderSetters(JDefinedClass builderClass, List<FieldModel> fields) {
360 			for (FieldModel fieldModel : fields) {
361 				String fieldName = fieldModel.fieldName;
362 				JMethod setterMethod = builderClass.method(JMod.PUBLIC, codeModel.VOID, Util.generateSetterName(fieldName));
363 				setterMethod.param(fieldModel.fieldType, fieldName);
364 				setterMethod.body().directStatement("// TODO add validation of input value if required and throw IllegalArgumentException if needed");
365 				setterMethod.body().directStatement("this." + fieldName + " = " + fieldName + ";");
366 			}
367 		}
368 
369 	}
370 	
371 	private static class FieldModel {
372 		
373 		private final String fieldName;
374 		private final Class<?> fieldType;
375 		
376 		private FieldModel(String fieldName, Class<?> fieldType) {
377 			this.fieldName = fieldName;
378 			this.fieldType = fieldType;
379 		}
380 				
381 	}
382 	
383 }