View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2015 LegSem.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the GNU Lesser Public License v2.1
5    * which accompanies this distribution, and is available at
6    * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
7    * 
8    * Contributors:
9    *     LegSem - initial API and implementation
10   ******************************************************************************/
11  package com.legstar.coxb.impl.reflect;
12  
13  import java.lang.reflect.Constructor;
14  import java.lang.reflect.Field;
15  import java.lang.reflect.InvocationTargetException;
16  import java.lang.reflect.Method;
17  
18  import javax.xml.bind.annotation.XmlElement;
19  
20  import com.legstar.coxb.CobolElement;
21  import com.legstar.coxb.CobolJavaTypeAdapter;
22  import com.legstar.coxb.ICobolBinding;
23  import com.legstar.coxb.ICobolChoiceBinding;
24  import com.legstar.coxb.ICobolComplexBinding;
25  import com.legstar.coxb.impl.CArrayBinaryBinding;
26  import com.legstar.coxb.impl.CArrayDbcsBinding;
27  import com.legstar.coxb.impl.CArrayDoubleBinding;
28  import com.legstar.coxb.impl.CArrayFloatBinding;
29  import com.legstar.coxb.impl.CArrayNationalBinding;
30  import com.legstar.coxb.impl.CArrayOctetStreamBinding;
31  import com.legstar.coxb.impl.CArrayPackedDecimalBinding;
32  import com.legstar.coxb.impl.CArrayStringBinding;
33  import com.legstar.coxb.impl.CArrayZonedDecimalBinding;
34  import com.legstar.coxb.impl.CBinaryBinding;
35  import com.legstar.coxb.impl.CDbcsBinding;
36  import com.legstar.coxb.impl.CDoubleBinding;
37  import com.legstar.coxb.impl.CFloatBinding;
38  import com.legstar.coxb.impl.CNationalBinding;
39  import com.legstar.coxb.impl.COctetStreamBinding;
40  import com.legstar.coxb.impl.CPackedDecimalBinding;
41  import com.legstar.coxb.impl.CStringBinding;
42  import com.legstar.coxb.impl.CZonedDecimalBinding;
43  import com.legstar.coxb.impl.RedefinesMap;
44  import com.legstar.coxb.util.ClassUtil;
45  import com.legstar.coxb.util.NameUtil;
46  
47  /**
48   * Creates specialized COBOL/Java bindings based upon properties discovered
49   * using reflection on JAXB classes.
50   * 
51   */
52  public final class ReflectBindingFactory {
53  
54      /**
55       * Choice bindings derive their name from the redefined alternative.
56       */
57      private static final String CHOICE_NAME_SUFFIX = "Choice";
58  
59      /**
60       * Complex array bindings derive their name from the occuring complex item.
61       */
62      private static final String COMPLEX_ARRAY_NAME_SUFFIX = "Wrapper";
63  
64      /**
65       * Utility class.
66       */
67      private ReflectBindingFactory() {
68  
69      }
70  
71      /**
72       * Create a specialized binding by analyzing a JAXB class field.
73       * <p/>
74       * The strategy is as follows:
75       * <ul>
76       * <li>If there is an explicit adapter, then it becomes the binding</li>
77       * <li>Otherwise binding is inferred from COBOL annotations</li>
78       * </ul>
79       * 
80       * @param jaxbType the java property type
81       * @param field the JAXB class field
82       * @param parentBinding the parent binding (null if none)
83       * @param jaxbObjectFactory the JAXB object factory
84       * @return a specialized binding
85       */
86      public static ICobolBinding createBinding(final Class < ? > jaxbType,
87              final Field field, final ICobolComplexBinding parentBinding,
88              final Object jaxbObjectFactory) throws ReflectBindingException {
89  
90          CobolElement cobolAnnotations = field.getAnnotation(CobolElement.class);
91          if (cobolAnnotations == null) {
92              throw new ReflectBindingException(
93                      "No cobol annotations found for field " + field.getName());
94          }
95          String jaxbName = getJaxbName(parentBinding.getJaxbType(), field,
96                  cobolAnnotations.maxOccurs());
97  
98          CobolJavaTypeAdapter adapter = field
99                  .getAnnotation(CobolJavaTypeAdapter.class);
100         if (adapter != null) {
101             return getCustomAdapter(adapter, jaxbName, jaxbType,
102                     cobolAnnotations, parentBinding);
103         }
104 
105         return createBinding(jaxbName, jaxbType, cobolAnnotations,
106                 parentBinding, jaxbObjectFactory);
107 
108     }
109 
110     /**
111      * Dispatch creation methods based on the COBOL type.
112      * 
113      * @param jaxbName the java property name
114      * @param jaxbType the java property type
115      * @param cobolAnnotations the COBOL annotations
116      * @param parentBinding the parent binding (null if none)
117      * @param jaxbObjectFactory the JAXB object factory
118      * @return a specialized binding
119      * @throws ReflectBindingException
120      */
121     public static ICobolBinding createBinding(final String jaxbName,
122             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
123             final ICobolComplexBinding parentBinding,
124             final Object jaxbObjectFactory) throws ReflectBindingException {
125 
126         switch (cobolAnnotations.type()) {
127         case GROUP_ITEM:
128             return createComplexBinding(jaxbName, jaxbType, cobolAnnotations,
129                     parentBinding, jaxbObjectFactory);
130         case ALPHABETIC_ITEM:
131         case ALPHANUMERIC_EDITED_ITEM:
132         case ALPHANUMERIC_ITEM:
133         case NUMERIC_EDITED_ITEM:
134         case EXTERNAL_FLOATING_ITEM:
135             return createStringBinding(jaxbName, jaxbType, cobolAnnotations,
136                     parentBinding);
137         case NATIONAL_ITEM:
138             return createNationalBinding(jaxbName, jaxbType, cobolAnnotations,
139                     parentBinding);
140         case PACKED_DECIMAL_ITEM:
141             return createPackedDecimalBinding(jaxbName, jaxbType,
142                     cobolAnnotations, parentBinding);
143         case ZONED_DECIMAL_ITEM:
144             return createZonedDecimalBinding(jaxbName, jaxbType,
145                     cobolAnnotations, parentBinding);
146         case DBCS_ITEM:
147             return createDbcsBinding(jaxbName, jaxbType, cobolAnnotations,
148                     parentBinding);
149         case OCTET_STREAM_ITEM:
150         case INDEX_ITEM:
151         case POINTER_ITEM:
152         case PROC_POINTER_ITEM:
153         case FUNC_POINTER_ITEM:
154             return createOctetStreamBinding(jaxbName, jaxbType,
155                     cobolAnnotations, parentBinding);
156         case BINARY_ITEM:
157         case NATIVE_BINARY_ITEM:
158             return createBinaryBinding(jaxbName, jaxbType, cobolAnnotations,
159                     parentBinding);
160         case SINGLE_FLOAT_ITEM:
161             return createFloatBinding(jaxbName, jaxbType, cobolAnnotations,
162                     parentBinding);
163         case DOUBLE_FLOAT_ITEM:
164             return createDoubleBinding(jaxbName, jaxbType, cobolAnnotations,
165                     parentBinding);
166         default:
167             throw (new ReflectBindingException(
168                     "Unrecognized cobol type for field "
169                             + cobolAnnotations.cobolName()));
170         }
171 
172     }
173 
174     /**
175      * Create a group element type.
176      * 
177      * @param jaxbName the java property name
178      * @param jaxbType the java property type
179      * @param cobolAnnotations the cobol annotations for this element
180      * @param parentBinding the parent binding (null if none)
181      * @param jaxbObjectFactory the JAXB object factory
182      * @return the new cobol element description
183      * @throws ReflectBindingException if cobol description cannot be created
184      */
185     public static ICobolBinding createComplexBinding(final String jaxbName,
186             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
187             final ICobolComplexBinding parentBinding,
188             final Object jaxbObjectFactory) throws ReflectBindingException {
189 
190         /* JAXB considers a member to be an array only if maxOccurs > 1 */
191         if (cobolAnnotations.maxOccurs() > 1) {
192             /* A single complex binding is used for all items */
193             ICobolComplexBinding item = new CComplexReflectBinding(jaxbName,
194                     jaxbName, jaxbType, cobolAnnotations, parentBinding,
195                     jaxbObjectFactory);
196             return new CArrayComplexReflectBinding(jaxbName
197                     + COMPLEX_ARRAY_NAME_SUFFIX, jaxbName, jaxbType,
198                     cobolAnnotations, parentBinding, item, jaxbObjectFactory);
199         } else {
200             return new CComplexReflectBinding(jaxbName, jaxbName, jaxbType,
201                     cobolAnnotations, parentBinding, jaxbObjectFactory);
202         }
203     }
204 
205     /**
206      * Create a choice element type which will group all alternatives.
207      * <p/>
208      * The identifier of this choice element is built from the first alternative
209      * java name.
210      * <p/>
211      * The choice element replaces the redefined element in the parent
212      * hierarchy. All alternative (redefining elements) are then added to the
213      * choice.
214      * 
215      * @param cobolAnnotations the cobol annotations for this element
216      * @param parentBinding the parent binding (null if none)
217      * @param cobolBinding the cobol descriptor for this element
218      * @param redefinesMap the current list of redefined items
219      * @return the new cobol element description
220      * @throws ReflectBindingException if cobol binding cannot be created
221      */
222     public static ICobolChoiceBinding createChoiceBinding(
223             final ICobolComplexBinding parentBinding,
224             final ICobolBinding cobolBinding, final RedefinesMap redefinesMap)
225             throws ReflectBindingException {
226 
227         String choiceJaxbName = cobolBinding.getJaxbName() + CHOICE_NAME_SUFFIX;
228 
229         CChoiceReflectBinding choice = new CChoiceReflectBinding(
230                 choiceJaxbName, parentBinding);
231 
232         /*
233          * Propagate unmarshaling/marshaling strategies from first alternative
234          * to the new choice element.
235          */
236         choice.setMarshalChoiceStrategyClassName(cobolBinding
237                 .getMarshalChoiceStrategyClassName());
238         choice.setUnmarshalChoiceStrategyClassName(cobolBinding
239                 .getUnmarshalChoiceStrategyClassName());
240         choice.setCobolName(cobolBinding.getCobolName());
241 
242         /*
243          * Add the redefined item as the first alternative in the choice element
244          */
245         choice.addAlternative(cobolBinding);
246 
247         /* Add this choice element in the redefines map */
248         redefinesMap.updateChoiceElement(cobolBinding.getCobolName(), choice);
249 
250         /* Return the choice as the current element for caller */
251         return choice;
252 
253     }
254 
255     /**
256      * Create a String binding type.
257      * 
258      * @param jaxbName the java property name
259      * @param jaxbType the java property type
260      * @param cobolAnnotations the cobol annotations for this element
261      * @param parentBinding the parent binding
262      * @return the new cobol element description
263      * @throws ReflectBindingException if cobol description cannot be created
264      */
265     public static ICobolBinding createStringBinding(final String jaxbName,
266             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
267             final ICobolComplexBinding parentBinding)
268             throws ReflectBindingException {
269 
270         if (cobolAnnotations.maxOccurs() > 0) {
271             return new CArrayStringBinding(jaxbName, jaxbName, jaxbType,
272                     cobolAnnotations, parentBinding);
273         } else {
274             return new CStringBinding(jaxbName, jaxbName, jaxbType,
275                     cobolAnnotations, parentBinding);
276         }
277     }
278 
279     /**
280      * Create a OctetStream binding type.
281      * 
282      * @param jaxbName the java property name
283      * @param jaxbType the java property type
284      * @param cobolAnnotations the cobol annotations for this element
285      * @param parentBinding the parent binding
286      * @return the new cobol element description
287      * @throws ReflectBindingException if cobol description cannot be created
288      */
289     public static ICobolBinding createOctetStreamBinding(final String jaxbName,
290             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
291             final ICobolComplexBinding parentBinding)
292             throws ReflectBindingException {
293 
294         if (cobolAnnotations.maxOccurs() > 0) {
295             return new CArrayOctetStreamBinding(jaxbName, jaxbName, jaxbType,
296                     cobolAnnotations, parentBinding);
297         } else {
298             return new COctetStreamBinding(jaxbName, jaxbName, jaxbType,
299                     cobolAnnotations, parentBinding);
300         }
301     }
302 
303     /**
304      * Create a National binding type.
305      * 
306      * @param jaxbName the java property name
307      * @param jaxbType the java property type
308      * @param cobolAnnotations the cobol annotations for this element
309      * @param parentBinding the parent binding
310      * @return the new cobol element description
311      * @throws ReflectBindingException if cobol description cannot be created
312      */
313     public static ICobolBinding createNationalBinding(final String jaxbName,
314             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
315             final ICobolComplexBinding parentBinding)
316             throws ReflectBindingException {
317 
318         if (cobolAnnotations.maxOccurs() > 0) {
319             return new CArrayNationalBinding(jaxbName, jaxbName, jaxbType,
320                     cobolAnnotations, parentBinding);
321         } else {
322             return new CNationalBinding(jaxbName, jaxbName, jaxbType,
323                     cobolAnnotations, parentBinding);
324         }
325     }
326 
327     /**
328      * Create a Dbcs binding type.
329      * 
330      * @param jaxbName the java property name
331      * @param jaxbType the java property type
332      * @param cobolAnnotations the cobol annotations for this element
333      * @param parentBinding the parent binding
334      * @return the new cobol element description
335      * @throws ReflectBindingException if cobol description cannot be created
336      */
337     public static ICobolBinding createDbcsBinding(final String jaxbName,
338             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
339             final ICobolComplexBinding parentBinding)
340             throws ReflectBindingException {
341 
342         if (cobolAnnotations.maxOccurs() > 0) {
343             return new CArrayDbcsBinding(jaxbName, jaxbName, jaxbType,
344                     cobolAnnotations, parentBinding);
345         } else {
346             return new CDbcsBinding(jaxbName, jaxbName, jaxbType,
347                     cobolAnnotations, parentBinding);
348         }
349     }
350 
351     /**
352      * Create a PackedDecimal binding type.
353      * 
354      * @param jaxbName the java property name
355      * @param jaxbType the java property type
356      * @param cobolAnnotations the cobol annotations for this element
357      * @param parentBinding the parent binding
358      * @return the new cobol element description
359      * @throws ReflectBindingException if cobol description cannot be created
360      */
361     public static ICobolBinding createPackedDecimalBinding(
362             final String jaxbName, final Class < ? > jaxbType,
363             final CobolElement cobolAnnotations,
364             final ICobolComplexBinding parentBinding)
365             throws ReflectBindingException {
366 
367         if (cobolAnnotations.maxOccurs() > 0) {
368             return new CArrayPackedDecimalBinding(jaxbName, jaxbName, jaxbType,
369                     cobolAnnotations, parentBinding);
370         } else {
371             return new CPackedDecimalBinding(jaxbName, jaxbName, jaxbType,
372                     cobolAnnotations, parentBinding);
373         }
374     }
375 
376     /**
377      * Create a ZonedDecimal binding type.
378      * 
379      * @param jaxbName the java property name
380      * @param jaxbType the java property type
381      * @param cobolAnnotations the cobol annotations for this element
382      * @param parentBinding the parent binding
383      * @return the new cobol element description
384      * @throws ReflectBindingException if cobol description cannot be created
385      */
386     public static ICobolBinding createZonedDecimalBinding(
387             final String jaxbName, final Class < ? > jaxbType,
388             final CobolElement cobolAnnotations,
389             final ICobolComplexBinding parentBinding)
390             throws ReflectBindingException {
391 
392         if (cobolAnnotations.maxOccurs() > 0) {
393             return new CArrayZonedDecimalBinding(jaxbName, jaxbName, jaxbType,
394                     cobolAnnotations, parentBinding);
395         } else {
396             return new CZonedDecimalBinding(jaxbName, jaxbName, jaxbType,
397                     cobolAnnotations, parentBinding);
398         }
399     }
400 
401     /**
402      * Create a Binary binding type.
403      * 
404      * @param jaxbName the java property name
405      * @param jaxbType the java property type
406      * @param cobolAnnotations the cobol annotations for this element
407      * @param parentBinding the parent binding
408      * @return the new cobol element description
409      * @throws ReflectBindingException if cobol description cannot be created
410      */
411     public static ICobolBinding createBinaryBinding(final String jaxbName,
412             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
413             final ICobolComplexBinding parentBinding)
414             throws ReflectBindingException {
415 
416         if (cobolAnnotations.maxOccurs() > 0) {
417             return new CArrayBinaryBinding(jaxbName, jaxbName, jaxbType,
418                     cobolAnnotations, parentBinding);
419         } else {
420             return new CBinaryBinding(jaxbName, jaxbName, jaxbType,
421                     cobolAnnotations, parentBinding);
422         }
423     }
424 
425     /**
426      * Create a Float binding type.
427      * 
428      * @param jaxbName the java property name
429      * @param jaxbType the java property type
430      * @param cobolAnnotations the cobol annotations for this element
431      * @param parentBinding the parent binding
432      * @return the new cobol element description
433      * @throws ReflectBindingException if cobol description cannot be created
434      */
435     public static ICobolBinding createFloatBinding(final String jaxbName,
436             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
437             final ICobolComplexBinding parentBinding)
438             throws ReflectBindingException {
439 
440         if (cobolAnnotations.maxOccurs() > 0) {
441             return new CArrayFloatBinding(jaxbName, jaxbName, jaxbType,
442                     cobolAnnotations, parentBinding);
443         } else {
444             return new CFloatBinding(jaxbName, jaxbName, jaxbType,
445                     cobolAnnotations, parentBinding);
446         }
447     }
448 
449     /**
450      * Create a Double binding type.
451      * 
452      * @param jaxbName the java property name
453      * @param jaxbType the java property type
454      * @param cobolAnnotations the cobol annotations for this element
455      * @param parentBinding the parent binding
456      * @return the new cobol element description
457      * @throws ReflectBindingException if cobol description cannot be created
458      */
459     public static ICobolBinding createDoubleBinding(final String jaxbName,
460             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
461             final ICobolComplexBinding parentBinding)
462             throws ReflectBindingException {
463 
464         if (cobolAnnotations.maxOccurs() > 0) {
465             return new CArrayDoubleBinding(jaxbName, jaxbName, jaxbType,
466                     cobolAnnotations, parentBinding);
467         } else {
468             return new CDoubleBinding(jaxbName, jaxbName, jaxbType,
469                     cobolAnnotations, parentBinding);
470         }
471     }
472 
473     /**
474      * Get the JAXB property name usable by appending "get".
475      * <p/>
476      * We first try the host field name processed with the JAXB name converter
477      * which strips off underscores, etc... This is the most likely situation.
478      * <p/>
479      * If first try does not work, we try the host field name directly. This
480      * case corresponds to ECI compatibility mode which bypasses the JAXB name
481      * converter.
482      * <p/>
483      * If this still does not work, we try the XmlElement annotation. This
484      * corresponds to cases (@see numzoned) where XmlEelement annotation is used
485      * to override the proposed JAXB name.
486      * 
487      * @param parentJaxbType the parent JAXB object type
488      * @param hostField the JAXB field
489      * @param maxOccurs the number of occurrences (needed to identify lists)
490      * @return a property name usable to create a getter a method on the JAXB
491      *         object
492      * @throws ReflectBindingException
493      */
494     public static String getJaxbName(Class < ? > parentJaxbType,
495             final Field hostField, final int maxOccurs)
496             throws ReflectBindingException {
497 
498         Method getter = null;
499         String getterPrefix = ClassUtil.getGetterPrefix(hostField.getType(),
500                 maxOccurs);
501 
502         try {
503             getter = parentJaxbType.getMethod(ClassUtil.getGetterName(
504                     getterPrefix, NameUtil.toClassName(hostField.getName())));
505         } catch (SecurityException e) {
506             throw new ReflectBindingException(e);
507         } catch (NoSuchMethodException e) {
508             try {
509                 getter = parentJaxbType.getMethod(ClassUtil.getGetterName(
510                         getterPrefix, hostField.getName()));
511             } catch (SecurityException e1) {
512                 throw new ReflectBindingException(e1);
513             } catch (NoSuchMethodException e1) {
514                 XmlElement xmlAnnotation = hostField
515                         .getAnnotation(XmlElement.class);
516                 if (xmlAnnotation != null && xmlAnnotation.name() != null
517                         && !xmlAnnotation.name().equals("##default")) {
518                     try {
519                         getter = parentJaxbType.getMethod(ClassUtil
520                                 .getGetterName(getterPrefix, NameUtil
521                                         .toClassName(xmlAnnotation.name())));
522                     } catch (SecurityException e2) {
523                         throw new ReflectBindingException(e);
524                     } catch (NoSuchMethodException e2) {
525                         throw new ReflectBindingException(e);
526                     }
527                 } else {
528                     throw new ReflectBindingException(
529                             "Unable to find JAXB getter method for "
530                                     + hostField.getName());
531                 }
532             }
533         }
534         return getter.getName().substring(getterPrefix.length());
535 
536     }
537 
538     /**
539      * Elements might be associated with a custom adapter.
540      * <p/>
541      * Custom adapters will usually inherit from
542      * {@link com.legstar.coxb.common.CBinding} or one of its subclasses.
543      * <p/>
544      * The adapter must implement a constructor that takes the same parameters
545      * as CBinding.
546      * 
547      * @param adapter the adapter annotations
548      * @param jaxbName the JAXB name of the bound property
549      * @param jaxbType the JAXB type of the bound property
550      * @param cobolAnnotations the COBOL annotations
551      * @param parentBinding the parent binding
552      * @return an instance of the adapter
553      * @throws ReflectBindingException if instantiation fails
554      */
555     protected static ICobolBinding getCustomAdapter(
556             CobolJavaTypeAdapter adapter, final String jaxbName,
557             final Class < ? > jaxbType, final CobolElement cobolAnnotations,
558             final ICobolComplexBinding parentBinding)
559             throws ReflectBindingException {
560         try {
561             Class < ? extends ICobolBinding > adapterClass = adapter.value();
562             Constructor < ? extends ICobolBinding > ctor = adapterClass
563                     .getConstructor(String.class, String.class, Class.class,
564                             CobolElement.class, ICobolComplexBinding.class);
565             return ctor.newInstance(jaxbName, jaxbName, jaxbType,
566                     cobolAnnotations, parentBinding);
567         } catch (SecurityException e) {
568             throw new ReflectBindingException(e);
569         } catch (NoSuchMethodException e) {
570             throw new ReflectBindingException(e);
571         } catch (IllegalArgumentException e) {
572             throw new ReflectBindingException(e);
573         } catch (InstantiationException e) {
574             throw new ReflectBindingException(e);
575         } catch (IllegalAccessException e) {
576             throw new ReflectBindingException(e);
577         } catch (InvocationTargetException e) {
578             throw new ReflectBindingException(e);
579         }
580 
581     }
582 
583 }