View Javadoc
1   package org.exolab.castor.builder.factory;
2   
3   import org.apache.commons.lang3.StringUtils;
4   import org.castor.xml.JavaNaming;
5   import org.exolab.castor.builder.AnnotationBuilder;
6   import org.exolab.castor.builder.info.FieldInfo;
7   import org.exolab.castor.builder.info.nature.SolrjFieldInfoNature;
8   import org.exolab.castor.builder.info.nature.XMLInfoNature;
9   import org.exolab.castor.builder.types.XSType;
10  import org.exolab.javasource.JAnnotation;
11  import org.exolab.javasource.JAnnotationType;
12  import org.exolab.javasource.JClass;
13  import org.exolab.javasource.JDocComment;
14  import org.exolab.javasource.JDocDescriptor;
15  import org.exolab.javasource.JField;
16  import org.exolab.javasource.JMethod;
17  import org.exolab.javasource.JModifiers;
18  import org.exolab.javasource.JParameter;
19  import org.exolab.javasource.JPrimitiveType;
20  import org.exolab.javasource.JSourceCode;
21  import org.exolab.javasource.JType;
22  
23  /**
24   * This factory takes a FieldInfo and generates the suitable JFields (and optional the getter and
25   * setter methods) into the JClass.
26   */
27  public class FieldMemberAndAccessorFactory {
28  
29    /**
30     * The {@link JavaNaming} to use.
31     */
32    private JavaNaming javaNaming;
33  
34  
35    /**
36     * Whether to use old field naming convention with '_' prefix.
37     */
38    private boolean useOldFieldNaming;
39  
40    /**
41     * Creates a factory that offers public methods to create the field initialization code as well as
42     * the getter/setter methods.
43     * 
44     * @param javaNaming JavaNaming to use
45     */
46    public FieldMemberAndAccessorFactory(final JavaNaming javaNaming) {
47      this(javaNaming, false);
48    }
49  
50    /**
51     * Creates an instance of this class.
52     * 
53     * @param javaNaming JavaNaming to use
54     */
55    public FieldMemberAndAccessorFactory(final JavaNaming javaNaming, boolean useOldFieldNaming) {
56      this.javaNaming = javaNaming;
57      this.useOldFieldNaming = useOldFieldNaming;
58    }
59  
60    /**
61     * Creates the field initialization code in a constructor.
62     * 
63     * @param fieldInfo the fieldInfo to translate
64     * @param jsc the JSourceCode in which to add the source to
65     */
66    public void generateInitializerCode(final FieldInfo fieldInfo, final JSourceCode jsc) {
67      // set the default value
68      XMLInfoNature xmlNature = new XMLInfoNature(fieldInfo);
69  
70      if (!xmlNature.getSchemaType().isPrimitive()) {
71        String value = fieldInfo.getDefaultValue();
72        boolean dateTime = xmlNature.getSchemaType().isDateTime();
73        if (value == null) {
74          value = fieldInfo.getFixedValue();
75        }
76        if (value != null) {
77          StringBuilder buffer = new StringBuilder(50);
78          // date/time constructors throw ParseException that
79          // needs to be catched in the constructor--> not the prettiest solution
80          // when mulitple date/time in a class.
81          if (dateTime) {
82            jsc.add("try {");
83            jsc.indent();
84          }
85          /*
86           * fieldInfo.getWriteMethodName() will either prefix the method with 'add' (for multivalued
87           * fields) or 'set'!
88           * 
89           * @see FieldInfo#getWriteMethodeName()
90           */
91          buffer.append(fieldInfo.getWriteMethodName())
92              // buffer.append(FieldInfo.METHOD_PREFIX_SET);
93              // buffer.append(fieldInfo.getMethodSuffix());
94              .append('(').append(value).append(");");
95          jsc.add(buffer.toString());
96          if (dateTime) {
97            jsc.unindent();
98            jsc.add("} catch (java.text.ParseException pe) {");
99            jsc.indent();
100           jsc.add("throw new IllegalStateException(pe.getMessage());");
101           jsc.unindent();
102           jsc.add("}");
103         }
104       }
105     }
106   } // -- generateInitializerCode
107 
108   /**
109    * Adds the suitable JField to the JClass.
110    * 
111    * @param fieldInfo the fieldInfo to translate
112    * @param jClass the jclass the jField will be added to
113    */
114   public final void createJavaField(final FieldInfo fieldInfo, final JClass jClass) {
115     XMLInfoNature xmlNature = new XMLInfoNature(fieldInfo);
116     XSType type = xmlNature.getSchemaType();
117     JType jType = type.getJType();
118 
119     JField field = null;
120     if (useOldFieldNaming()) {
121       field = new JField(type.getJType(), fieldInfo.getName());
122     } else {
123       field = new JField(type.getJType(), fieldInfo.getName(), null);
124     }
125 
126     if (xmlNature.getSchemaType().isDateTime()) {
127       field.setDateTime(true);
128     }
129 
130     if (fieldInfo.isStatic() || fieldInfo.isFinal()) {
131       JModifiers modifiers = field.getModifiers();
132       modifiers.setFinal(fieldInfo.isFinal());
133       modifiers.setStatic(fieldInfo.isStatic());
134     }
135 
136     if (!(fieldInfo.getVisibility().equals("private"))) {
137       JModifiers modifiers = field.getModifiers();
138       if (fieldInfo.getVisibility().equals("protected")) {
139         modifiers.makeProtected();
140       } else if (fieldInfo.getVisibility().equals("public")) {
141         modifiers.makePublic();
142       }
143     }
144 
145     // -- set init String
146     if (fieldInfo.getDefaultValue() != null) {
147       if (!xmlNature.isMultivalued()) {
148         field.setInitString(fieldInfo.getDefaultValue());
149       }
150     }
151 
152     if (fieldInfo.getFixedValue() != null && !xmlNature.getSchemaType().isDateTime()) {
153       field.setInitString(fieldInfo.getFixedValue());
154     }
155 
156     // -- set Javadoc comment
157     if (fieldInfo.getComment() != null) {
158       field.setComment(fieldInfo.getComment());
159     }
160 
161     // deal with SOLRJ annotations
162     if (fieldInfo.hasNature(SolrjFieldInfoNature.class.getName())) {
163       SolrjFieldInfoNature solrjNature = new SolrjFieldInfoNature(fieldInfo);
164 
165       JAnnotationType annotationType = null;
166       if (solrjNature.isIdDefinition()) {
167         annotationType = new JAnnotationType("org.apache.solr.client.solrj.beans.Id");
168         jClass.addImport("org.apache.solr.client.solrj.beans.Id");
169       } else {
170         annotationType = new JAnnotationType("org.apache.solr.client.solrj.beans.Field");
171         jClass.addImport("org.apache.solr.client.solrj.beans.Field");
172       }
173       JAnnotation annotation = new JAnnotation(annotationType);
174       field.addAnnotation(annotation);
175       String fieldName = solrjNature.getFieldName();
176       if (StringUtils.isNotBlank(fieldName)) {
177         annotation.setValue("\"" + fieldName + "\"");
178       }
179     }
180 
181     jClass.addField(field);
182 
183     // -- special supporting fields
184 
185     // -- has_field
186     if ((!type.isEnumerated()) && (jType.isPrimitive())) {
187       if (useOldFieldNaming()) {
188         field = new JField(JType.BOOLEAN, "_has" + fieldInfo.getName());
189       } else {
190         field = new JField(JType.BOOLEAN, "has" + fieldInfo.getName());
191       }
192       field.setComment("Keeps track of whether primitive field " + fieldInfo.getName()
193           + " has been set already.");
194       jClass.addField(field);
195     }
196 
197     // -- save default value for primitives
198     // -- not yet finished
199     /*
200      * if (type.isPrimitive()) { field = new JField(jType, "_DEFAULT" + name.toUpperCase());
201      * JModifiers modifiers = field.getModifiers(); modifiers.setFinal(true);
202      * modifiers.setStatic(true);
203      * 
204      * if (_default != null) field.setInitString(_default);
205      * 
206      * jClass.addField(field); }
207      */
208   }
209 
210   /**
211    * Adds the getter/setter for this field to the jClass.
212    * 
213    * @param fieldInfo the fieldInfo to translate
214    * @param jClass the jclass the jField will be added to
215    * @param useJava50 java version flag
216    */
217   public void createAccessMethods(final FieldInfo fieldInfo, final JClass jClass,
218       final boolean useJava50, final AnnotationBuilder[] annotationBuilders) {
219     if ((fieldInfo.getMethods() & FieldInfo.READ_METHOD) > 0) {
220       createGetterMethod(fieldInfo, jClass, useJava50, annotationBuilders);
221     }
222     if ((fieldInfo.getMethods() & FieldInfo.WRITE_METHOD) > 0) {
223       createSetterMethod(fieldInfo, jClass, useJava50);
224     }
225     if (fieldInfo.requiresHasAndDeleteMethods()) {
226       createHasAndDeleteMethods(fieldInfo, jClass);
227     }
228   }
229 
230   /**
231    * Creates the Javadoc comments for the getter method associated with this FieldInfo.
232    * 
233    * @param fieldInfo the fieldInfo to translate
234    * @param jDocComment the JDocComment to add the Javadoc comments to.
235    */
236   private void createGetterComment(final FieldInfo fieldInfo, final JDocComment jDocComment) {
237     String fieldName = fieldInfo.getName();
238     // -- remove '_' if necessary
239     if (fieldName.indexOf('_') == 0) {
240       fieldName = fieldName.substring(1);
241     }
242 
243     String mComment = "Returns the value of field '" + fieldName + "'.";
244     if ((fieldInfo.getComment() != null) && (fieldInfo.getComment().length() > 0)) {
245       mComment += " The field '" + fieldName + "' has the following description: ";
246 
247       // XDoclet support - Add a couple newlines if it's a doclet tag
248       if (fieldInfo.getComment().startsWith("@")) {
249         mComment += "\n\n";
250       }
251 
252       mComment += fieldInfo.getComment();
253     }
254     jDocComment.setComment(mComment);
255   } // -- createGetterComment
256 
257   /**
258    * Creates the getter methods for this FieldInfo.
259    * 
260    * @param fieldInfo the fieldInfo to translate
261    * @param jClass the JClass to add the methods to
262    * @param useJava50 true if source code is supposed to be generated for Java 5
263    */
264   private void createGetterMethod(final FieldInfo fieldInfo, final JClass jClass,
265       final boolean useJava50, AnnotationBuilder[] annotationBuilders) {
266     JMethod method = null;
267     JSourceCode jsc = null;
268 
269     String mname = fieldInfo.getMethodSuffix();
270 
271     XSType xsType = new XMLInfoNature(fieldInfo).getSchemaType();
272     JType jType = xsType.getJType();
273 
274     // -- create get method
275     method =
276         new JMethod(fieldInfo.getReadMethodName(), jType, "the value of field '" + mname + "'.");
277     // if (useJava50) {
278     // Java5HacksHelper.addOverrideAnnotations(method.getSignature());
279     // }
280 
281     for (AnnotationBuilder annotationBuilder : annotationBuilders) {
282       annotationBuilder.addFieldGetterAnnotations(fieldInfo, method);
283     }
284 
285     jClass.addMethod(method);
286     createGetterComment(fieldInfo, method.getJDocComment());
287     jsc = method.getSourceCode();
288     jsc.add("return this.");
289     jsc.append(fieldInfo.getName());
290     jsc.append(";");
291 
292     if (xsType.getType() == XSType.BOOLEAN_TYPE) {
293 
294       // -- create is<Property>t method
295       method =
296           new JMethod(fieldInfo.getIsMethodName(), jType, "the value of field '" + mname + "'.");
297       // if (useJava50) {
298       // Java5HacksHelper.addOverrideAnnotations(method.getSignature());
299       // }
300       jClass.addMethod(method);
301       createGetterComment(fieldInfo, method.getJDocComment());
302       jsc = method.getSourceCode();
303       jsc.add("return this.");
304       jsc.append(fieldInfo.getName());
305       jsc.append(";");
306 
307     }
308 
309   } // -- createGetterMethod
310 
311   /**
312    * Creates the "has" and "delete" methods for this field associated with this FieldInfo. These
313    * methods are typically only needed for primitive types which cannot be assigned a null value.
314    *
315    * @param fieldInfo the fieldInfo to translate
316    * @param jClass the JClass to add the methods to
317    */
318   private void createHasAndDeleteMethods(final FieldInfo fieldInfo, final JClass jClass) {
319     JMethod method = null;
320     JSourceCode jsc = null;
321 
322     String mname = fieldInfo.getMethodSuffix();
323 
324     XSType xsType = new XMLInfoNature(fieldInfo).getSchemaType();
325     xsType.getJType();
326 
327     // -- create hasMethod
328     method = new JMethod(fieldInfo.getHasMethodName(), JType.BOOLEAN,
329         "true if at least one " + mname + " has been added");
330     jClass.addMethod(method);
331     jsc = method.getSourceCode();
332     jsc.add("return this.");
333     if (useOldFieldNaming()) {
334       jsc.append("_");
335     }
336     jsc.append("has");
337     String fieldName = fieldInfo.getName();
338     jsc.append(fieldName);
339     jsc.append(";");
340 
341     // -- create delete method
342     method = new JMethod(fieldInfo.getDeleteMethodName());
343     jClass.addMethod(method);
344     jsc = method.getSourceCode();
345     jsc.add("this.");
346     if (useOldFieldNaming()) {
347       jsc.append("_");
348     }
349     jsc.append("has");
350     jsc.append(fieldName);
351     jsc.append("= false;");
352     // -- bound properties
353     if (fieldInfo.isBound()) {
354       // notify listeners
355       jsc.add("notifyPropertyChangeListeners(\"");
356       if (fieldName.startsWith("_")) {
357         jsc.append(fieldName.substring(1));
358       } else {
359         jsc.append(fieldName);
360       }
361       jsc.append("\", ");
362       // -- 'this.' ensures this refers to the class member not the parameter
363       jsc.append(xsType.createToJavaObjectCode("this." + fieldName));
364       jsc.append(", null");
365       jsc.append(");");
366     }
367   } // -- createHasAndDeleteMethods
368 
369   /**
370    * Creates the Javadoc comments for the setter method associated with this FieldInfo.
371    * 
372    * @param fieldInfo the fieldInfo to translate
373    * @param jDocComment the JDocComment to add the Javadoc comments to.
374    */
375   private void createSetterComment(final FieldInfo fieldInfo, final JDocComment jDocComment) {
376     String fieldName = fieldInfo.getName();
377     // -- remove '_' if necessary
378     if (fieldName.indexOf('_') == 0) {
379       fieldName = fieldName.substring(1);
380     }
381 
382     String atParam = "the value of field '" + fieldName + "'.";
383 
384     String mComment = "Sets " + atParam;
385     if ((fieldInfo.getComment() != null) && (fieldInfo.getComment().length() > 0)) {
386       mComment += " The field '" + fieldName + "' has the following description: ";
387 
388       // XDoclet support - Add a couple newlines if it's a doclet tag
389       if (fieldInfo.getComment().startsWith("@")) {
390         mComment += "\n\n";
391       }
392 
393       mComment += fieldInfo.getComment();
394     }
395 
396     jDocComment.setComment(mComment);
397 
398     JDocDescriptor paramDesc = jDocComment.getParamDescriptor(fieldName);
399     if (paramDesc == null) {
400       paramDesc = JDocDescriptor.createParamDesc(fieldName, null);
401       jDocComment.addDescriptor(paramDesc);
402     }
403     paramDesc.setDescription(atParam);
404   } // -- createSetterComment
405 
406   /**
407    * Creates the setter (mutator) method(s) for this FieldInfo.
408    *
409    * @param fieldInfo the fieldInfo to translate
410    * @param jClass the JClass to add the methods to
411    * @param useJava50 true if source code is supposed to be generated for Java 5
412    */
413   private void createSetterMethod(final FieldInfo fieldInfo, final JClass jClass,
414       final boolean useJava50) {
415     JMethod method = null;
416     JSourceCode jsc = null;
417 
418     XMLInfoNature xmlNature = new XMLInfoNature(fieldInfo);
419 
420     String mname = fieldInfo.getMethodSuffix();
421     XSType xsType = xmlNature.getSchemaType();
422     JType jType = xsType.getJType();
423 
424     // -- create set method
425     /*
426      * fieldInfo.getWriteMethodName() will either prefix the method with 'add' (for multivalued
427      * fields) or 'set'!
428      * 
429      * @see FieldInfo#getWriteMethodeName()
430      */
431     method = new JMethod(fieldInfo.getWriteMethodName());
432     jClass.addMethod(method);
433 
434     String paramName = fieldInfo.getName();
435 
436     // -- make parameter name pretty,
437     // -- simply for aesthetic beauty
438     if (paramName.indexOf('_') == 0) {
439       String tempName = paramName.substring(1);
440       if (javaNaming.isValidJavaIdentifier(tempName)) {
441         paramName = tempName;
442       }
443     }
444 
445     method.addParameter(new JParameter(jType, paramName));
446     // if (useJava50) {
447     // Java5HacksHelper.addOverrideAnnotations(method.getSignature()); // DAB Java 5.0 hack
448     // }
449     createSetterComment(fieldInfo, method.getJDocComment());
450     jsc = method.getSourceCode();
451 
452     String fieldName = fieldInfo.getName();
453     // -- bound properties
454     if (fieldInfo.isBound()) {
455       // save old value
456       jsc.add("java.lang.Object old");
457       jsc.append(mname);
458       jsc.append(" = ");
459       // -- 'this.' ensures this refers to the class member not the parameter
460       jsc.append(xsType.createToJavaObjectCode("this." + fieldName));
461       jsc.append(";");
462     }
463 
464     // -- set new value
465     jsc.add("this.");
466     jsc.append(fieldName);
467     jsc.append(" = ");
468     jsc.append(paramName);
469     jsc.append(";");
470 
471     if (fieldInfo.getFieldInfoReference() != null) {
472       jsc.add("this.");
473       jsc.append(fieldInfo.getFieldInfoReference().getName());
474       jsc.append(" = ");
475 
476       JType referencedJType =
477           new XMLInfoNature(fieldInfo.getFieldInfoReference()).getSchemaType().getJType();
478       if (referencedJType.isPrimitive()) {
479         jsc.append(paramName);
480       } else if (jType.isPrimitive()) {
481         JPrimitiveType primitive = (JPrimitiveType) jType;
482         jsc.append("new ");
483         jsc.append(primitive.getWrapperName());
484         jsc.append("(");
485         jsc.append(paramName);
486         jsc.append(")");
487       } else {
488         jsc.append(paramName);
489       }
490 
491       jsc.append(";");
492     }
493 
494     // -- hasProperty
495     if (fieldInfo.requiresHasAndDeleteMethods()) {
496       jsc.add("this.");
497       if (useOldFieldNaming()) {
498         jsc.append("_");
499       }
500       jsc.append("has");
501       jsc.append(fieldName);
502       jsc.append(" = true;");
503     }
504 
505     // -- bound properties
506     if (fieldInfo.isBound()) {
507       // notify listeners
508       jsc.add("notifyPropertyChangeListeners(\"");
509       if (fieldName.startsWith("_")) {
510         jsc.append(fieldName.substring(1));
511       } else {
512         jsc.append(fieldName);
513       }
514       jsc.append("\", old");
515       jsc.append(mname);
516       jsc.append(", ");
517       // -- 'this.' ensures this refers to the class member not the parameter
518       jsc.append(xsType.createToJavaObjectCode("this." + fieldName));
519       jsc.append(");");
520     }
521   } // -- createSetterMethod
522 
523   /**
524    * Returns the javaNaming.
525    * 
526    * @return the javaNaming instance
527    */
528   public JavaNaming getJavaNaming() {
529     return javaNaming;
530   }
531 
532   public void setUseOldFieldNaming(boolean useOldFieldNaming) {
533     this.useOldFieldNaming = useOldFieldNaming;
534   }
535 
536   private boolean useOldFieldNaming() {
537     return this.useOldFieldNaming;
538   }
539 }