001 /*
002 * Copyright 2011 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
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 package org.kuali.rice.devtools.generators.mo;
017
018 import com.sun.codemodel.ClassType;
019 import com.sun.codemodel.JAnnotationArrayMember;
020 import com.sun.codemodel.JAnnotationUse;
021 import com.sun.codemodel.JBlock;
022 import com.sun.codemodel.JClass;
023 import com.sun.codemodel.JCodeModel;
024 import com.sun.codemodel.JConditional;
025 import com.sun.codemodel.JDefinedClass;
026 import com.sun.codemodel.JDocComment;
027 import com.sun.codemodel.JExpr;
028 import com.sun.codemodel.JFieldRef;
029 import com.sun.codemodel.JFieldVar;
030 import com.sun.codemodel.JInvocation;
031 import com.sun.codemodel.JMethod;
032 import com.sun.codemodel.JMod;
033 import com.sun.codemodel.JType;
034 import com.sun.codemodel.JVar;
035 import com.sun.codemodel.writer.SingleStreamCodeWriter;
036 import org.apache.commons.lang.builder.EqualsBuilder;
037 import org.apache.commons.lang.builder.HashCodeBuilder;
038 import org.apache.commons.lang.builder.ToStringBuilder;
039 import org.kuali.rice.core.api.CoreConstants;
040 import org.kuali.rice.core.api.mo.AbstractDataTransferObject;
041 import org.kuali.rice.core.api.mo.ModelBuilder;
042
043 import javax.xml.bind.annotation.XmlAccessType;
044 import javax.xml.bind.annotation.XmlAccessorType;
045 import javax.xml.bind.annotation.XmlAnyElement;
046 import javax.xml.bind.annotation.XmlElement;
047 import javax.xml.bind.annotation.XmlRootElement;
048 import javax.xml.bind.annotation.XmlType;
049 import java.io.ByteArrayOutputStream;
050 import java.io.Serializable;
051 import java.lang.reflect.Method;
052 import java.util.ArrayList;
053 import java.util.List;
054
055 /**
056 * This is a simple utility class which generates an "immutable" object complete with a builder based on a supplied
057 * contract interface definition.
058 *
059 * @author Kuali Rice Team (rice.collab@kuali.org)
060 *
061 */
062 public class ImmutableJaxbGenerator {
063
064 public static void main(String[] args) throws Exception {
065
066 if (args.length > 2 || args.length < 1) {
067 System.err.println("There should be two arguments defined as follows:\n" +
068 " 1. Fully qualified class name of a 'contract' interface\n" +
069 " 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");
070 System.exit(1);
071 }
072
073 // argument one should be a fully qualified class name of a "contract" interface
074 String contractInterfaceName = args[0];
075
076 String className = null;
077 // argument two should be the fully qualified class name of the class to generate
078 if (args.length == 2) {
079 className = args[1];
080 } else {
081 if (!contractInterfaceName.endsWith("Contract")) {
082 throw new IllegalArgumentException("If not explicitly specifying target classname, then contract class name must end with 'Contract'");
083 }
084 className = contractInterfaceName.substring(0, contractInterfaceName.lastIndexOf("Contract"));
085 }
086
087 Generator generator = new Generator(contractInterfaceName, className);
088 generator.generate();
089 }
090
091 public static class Generator {
092
093 private final String contractInterfaceName;
094 private final String className;
095 private final JCodeModel codeModel;
096
097 public Generator(String contractInterfaceName, String className) {
098 this.contractInterfaceName = contractInterfaceName;
099 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 }