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