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.Field;
14  import java.lang.reflect.Method;
15  import java.util.List;
16  
17  import javax.xml.bind.annotation.XmlType;
18  
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  
22  import com.legstar.coxb.CobolBindingException;
23  import com.legstar.coxb.CobolComplexType;
24  import com.legstar.coxb.CobolElement;
25  import com.legstar.coxb.ICobolArrayComplexBinding;
26  import com.legstar.coxb.ICobolBinaryBinding;
27  import com.legstar.coxb.ICobolBinding;
28  import com.legstar.coxb.ICobolChoiceBinding;
29  import com.legstar.coxb.ICobolComplexBinding;
30  import com.legstar.coxb.common.CComplexBinding;
31  import com.legstar.coxb.host.HostException;
32  import com.legstar.coxb.impl.CBinaryBinding;
33  import com.legstar.coxb.impl.RedefinesMap;
34  import com.legstar.coxb.util.BindingUtil;
35  import com.legstar.coxb.util.ClassUtil;
36  
37  /**
38   * This class implements a bi-directional binding between a cobol structure and
39   * a java object. Visitors can use this class to visit each element of the
40   * structure in turn. Reflexion is used on the Java object to infer a list of
41   * children.
42   */
43  
44  public class CComplexReflectBinding extends CComplexBinding {
45  
46      /**
47       * Reference to a JAXB object factory. This is needed because this class
48       * might need to create JAXB objects.
49       */
50      private Object _jaxbObjectFactory;
51  
52      /** Java object to which this cobol complex element is bound. */
53      private Object _jaxbObject;
54  
55      /**
56       * Indicates that the associated Jaxb object just came from the constructor
57       * and doesn't need to be recreated.
58       */
59      private boolean _unusedJaxbObject = false;
60  
61      /**
62       * Dynamic counters are named after the array or list they belong to plus
63       * this additional suffix.
64       */
65      private static final String COUNTER_SUFFIX = "Counter";
66  
67      /**
68       * Dynamic counters also need a cobol name which is built from the
69       * corresponding list or array cobol name plus this suffix.
70       */
71      private static final String COUNTER_COBOL_SUFFIX = "--C";
72  
73      /** Logger. */
74      private final Log _log = LogFactory.getLog(getClass());
75  
76      /**
77       * Constructor for a root Complex element with a bound JAXB object.
78       * 
79       * @param jaxbObjectFactory the JAXB object factory
80       * @param jaxbObject the concrete JAXB object instance bound to this complex
81       *            element
82       * @throws ReflectBindingException if construction fails
83       */
84      public CComplexReflectBinding(final Object jaxbObjectFactory,
85              final Object jaxbObject) throws ReflectBindingException {
86  
87          this(jaxbObject.getClass().getSimpleName(), jaxbObject.getClass()
88                  .getSimpleName(), jaxbObject.getClass(), null, null,
89                  jaxbObjectFactory);
90          _jaxbObject = jaxbObject;
91          _unusedJaxbObject = true;
92      }
93  
94      /**
95       * Constructor for a root Complex element knowing the bound JAXB class.
96       * 
97       * @param jaxbObjectFactory the JAXB object factory
98       * @param jaxbType JAXB type of complex field
99       * @throws ReflectBindingException if construction fails
100      */
101     public CComplexReflectBinding(final Object jaxbObjectFactory,
102             final Class < ? > jaxbType) throws ReflectBindingException {
103 
104         this(jaxbType.getSimpleName(), jaxbType.getSimpleName(), jaxbType,
105                 null, null, jaxbObjectFactory);
106     }
107 
108     /**
109      * Constructor for a child Complex element knowing the bound JAXB class.
110      * 
111      * @param bindingName the identifier for this binding
112      * @param jaxbName field name in parent JAXB object
113      * @param jaxbType JAXB type of complex field
114      * @param cobolAnnotations the cobol annotations for this element
115      * @param parentBinding a reference to the parent binding
116      * @param jaxbObjectFactory the JAXB object factory
117      * @throws ReflectBindingException if construction fails
118      */
119     public CComplexReflectBinding(final String bindingName,
120             final String jaxbName, final Class < ? > jaxbType,
121             final CobolElement cobolAnnotations,
122             final ICobolComplexBinding parentBinding,
123             final Object jaxbObjectFactory) throws ReflectBindingException {
124 
125         super(bindingName, jaxbName, jaxbType, cobolAnnotations, parentBinding);
126         _jaxbObjectFactory = jaxbObjectFactory;
127         initComplexElement(jaxbType, jaxbObjectFactory);
128     }
129 
130     /**
131      * Helper method. JAXB Types are annotated with an XmlType which gives an
132      * ordered list of properties
133      * 
134      * @param jaxbType the JAXB Class with annotations
135      * @param jaxbObjectFactory the JAXB object factory
136      * @throws ReflectBindingException if initialization fails
137      */
138     private void initComplexElement(final Class < ? > jaxbType,
139             final Object jaxbObjectFactory) throws ReflectBindingException {
140 
141         if (_log.isDebugEnabled()) {
142             _log.debug("Initializing Complex binding for " + jaxbType);
143         }
144         XmlType xmlType = (XmlType) jaxbType.getAnnotation(XmlType.class);
145         if (xmlType == null) {
146             throw new ReflectBindingException("No jaxb annotations found in "
147                     + jaxbType);
148         }
149         if (_log.isDebugEnabled()) {
150             _log.debug("Found JAXB annotations: " + xmlType.toString());
151         }
152 
153         /* Assume we are bound to a JAXB object */
154         setValueObjectClassName(jaxbType.getName());
155         setValueObjectsFactoryClassName(jaxbObjectFactory.getClass().getName());
156 
157         /*
158          * Jaxb class might hold an annotation which gives more details on how
159          * to bind
160          */
161         CobolComplexType cobolComplexType = (CobolComplexType) jaxbType
162                 .getAnnotation(CobolComplexType.class);
163         if (cobolComplexType != null
164                 && cobolComplexType.javaClassName() != null
165                 && cobolComplexType.javaClassName().length() > 0) {
166             setValueObjectClassName(cobolComplexType.javaClassName());
167             /*
168              * TODO allow more options, such as factory name, to be passed as
169              * annotations
170              */
171             setValueObjectsFactoryClassName(null);
172         }
173 
174         initChildren(jaxbType, xmlType);
175 
176         if (_log.isDebugEnabled()) {
177             _log.debug("Complex binding sucessfully initialized for: "
178                     + jaxbType);
179         }
180     }
181 
182     /**
183      * Creates a binding property for each child.
184      * 
185      * @param parentJaxbType the parent JAXB Class with annotations
186      * @param xmlType the JAXB annotations
187      * @throws ReflectBindingException if children bindings fail
188      * */
189     public void initChildren(final Class < ? > parentJaxbType,
190             final XmlType xmlType) throws ReflectBindingException {
191 
192         if (_log.isDebugEnabled()) {
193             _log.debug("Initializing children of: "
194                     + parentJaxbType.getSimpleName());
195         }
196         /* Map of choice elements for redefined elements */
197         RedefinesMap redefinesMap = new RedefinesMap();
198 
199         /* Process each property of this complex type in the predefined order */
200         for (String prop : xmlType.propOrder()) {
201 
202             /* Get a reference to this property field and type */
203             Field field;
204             Class < ? > jaxbType;
205             try {
206                 field = parentJaxbType.getDeclaredField(prop);
207                 jaxbType = BindingUtil.getJavaClass(field);
208 
209             } catch (SecurityException e) {
210                 throw new ReflectBindingException(e);
211             } catch (NoSuchFieldException e) {
212                 throw new ReflectBindingException(e);
213             } catch (CobolBindingException e) {
214                 throw new ReflectBindingException(e);
215             }
216 
217             ICobolBinding cobolBinding = ReflectBindingFactory.createBinding(
218                     jaxbType, field, this, _jaxbObjectFactory);
219 
220             if (_log.isDebugEnabled()) {
221                 _log.debug("Java field " + jaxbType.getSimpleName()
222                         + " bound to " + cobolBinding);
223             }
224 
225             /*
226              * If this element is a variable size array or list without an
227              * explicit depending on clause, dynamically generate a counter.
228              */
229             if (cobolBinding.getMaxOccurs() > 1
230                     && cobolBinding.getMinOccurs() < cobolBinding
231                             .getMaxOccurs()
232                     && (cobolBinding.getDependingOn() == null || cobolBinding
233                             .getDependingOn().length() == 0)) {
234                 createDynamicCounter(cobolBinding);
235             }
236 
237             /*
238              * If this element is redefined, create a choice which will be
239              * populated as we discover alternatives. The choice becomes the
240              * parent for the redefined element and all alternatives.
241              */
242             String redefines = cobolBinding.getRedefines();
243             if (cobolBinding.isRedefined()) {
244                 if (_log.isDebugEnabled()) {
245                     _log.debug("Creating Choice binding for redefined Cobol element "
246                             + cobolBinding.getCobolName());
247                 }
248                 ICobolChoiceBinding choice = ReflectBindingFactory
249                         .createChoiceBinding(this, cobolBinding, redefinesMap);
250                 getChildrenList().add(choice);
251 
252             } else if (redefines != null && redefines.length() > 0) {
253                 if (_log.isDebugEnabled()) {
254                     _log.debug("Adding " + cobolBinding.getCobolName()
255                             + " to Choice binding for Cobol element "
256                             + redefines);
257                 }
258                 ICobolChoiceBinding choice = redefinesMap
259                         .getChoiceElement(redefines);
260                 if (choice == null) {
261                     throw new ReflectBindingException("Cobol element "
262                             + cobolBinding.getCobolName()
263                             + " redefining unbound element " + redefines);
264                 }
265                 /*
266                  * Add the redefining item to the alternative list in the choice
267                  * element
268                  */
269                 choice.addAlternative(cobolBinding);
270 
271             } else {
272                 getChildrenList().add(cobolBinding);
273             }
274 
275         }
276         if (_log.isDebugEnabled()) {
277             _log.debug("Children sucessfully initialized for: "
278                     + parentJaxbType.getSimpleName());
279         }
280     }
281 
282     /**
283      * Generates a transient binding to hold the item counter for a variable
284      * size array when no explicit depending on clause exist. This sitution
285      * arises when the jaxb object was generated from an XSD instead of an
286      * existing cobol copybook.
287      * 
288      * @param cobolElement the variable size array or list
289      * @throws ReflectBindingException if counter cannot be created
290      */
291     private void createDynamicCounter(final ICobolBinding cobolElement)
292             throws ReflectBindingException {
293 
294         ICobolBinaryBinding counter = createDynamicCounterBinding(cobolElement);
295         storeCounter(counter);
296 
297         /*
298          * Now inform the variable size array that it has a depending on object
299          */
300         cobolElement.setDependingOn(counter.getCobolName());
301 
302         /*
303          * Arrays of complex items have a reference to a single binding used for
304          * all items. That binding holds the same cobol annotations as the
305          * wrapper array. Since we updated the wrapper depending on clause, we
306          * need to do the same at the item level.
307          */
308         if (cobolElement instanceof ICobolArrayComplexBinding) {
309             ((ICobolArrayComplexBinding) cobolElement).getComplexItemBinding()
310                     .setDependingOn(counter.getCobolName());
311         }
312 
313         if (_log.isDebugEnabled()) {
314             _log.debug("Created depending on relationship for "
315                     + cobolElement.getBindingName() + " with "
316                     + counter.getCobolName());
317         }
318     }
319 
320     /**
321      * Variable size arrays and lists need an extra numeric element to count
322      * items. Such element is a special "transient" binding which has no
323      * associated jaxb property.
324      * 
325      * @param listBinding the cobol binding for the array or list
326      * @return the counter binding
327      * @throws ReflectBindingException if cobol description cannot be created
328      */
329     private ICobolBinaryBinding createDynamicCounterBinding(
330             final ICobolBinding listBinding) throws ReflectBindingException {
331         if (_log.isDebugEnabled()) {
332             _log.debug("Creating a dynamic counter for "
333                     + listBinding.getBindingName());
334         }
335         CBinaryBinding counter = new CBinaryBinding(
336                 listBinding.getBindingName() + COUNTER_SUFFIX, null, null,
337                 null, this);
338         counter.setCobolName(getCounterCobolName(listBinding.getCobolName()));
339         counter.setLevelNumber(listBinding.getLevelNumber());
340         counter.setUsage("BINARY");
341         counter.setPicture("9(9)");
342         counter.setByteLength(4);
343         counter.setTotalDigits(9);
344         counter.setIsODOObject(true);
345         return counter;
346     }
347 
348     /**
349      * Dynamic counters need a unique Cobol name. This method determines such a
350      * name based on the related array or list cobol name. This method does not
351      * guarantee unicity. TODO reuse logic in CobolGen for unique Cobol name
352      * generation
353      * 
354      * @param cobolName cobol name of corresponding list or array
355      * @return the proposed counter cobol name
356      */
357     private String getCounterCobolName(final String cobolName) {
358         if (cobolName.length() < 31 - COUNTER_COBOL_SUFFIX.length()) {
359             return cobolName + COUNTER_COBOL_SUFFIX;
360         } else {
361             return cobolName.substring(0, 30 - COUNTER_COBOL_SUFFIX.length())
362                     + COUNTER_COBOL_SUFFIX;
363         }
364     }
365 
366     /** {@inheritDoc} */
367     public void createJaxbObject() throws HostException {
368         createValueObject();
369     }
370 
371     /** {@inheritDoc} */
372     public void createValueObject() throws HostException {
373         /*
374          * Since this complex binding has a constructor that takes a value
375          * object, we might already have a value object that was not used yet.
376          */
377         if (_unusedJaxbObject && _jaxbObject != null) {
378             _unusedJaxbObject = false;
379             return;
380         }
381         _jaxbObject = BindingUtil.newJaxbObject(_jaxbObjectFactory,
382                 getJaxbType().getName());
383     }
384 
385     /** {@inheritDoc} */
386     public void setChildrenValues() throws HostException {
387 
388         /* Make sure there is an associated JAXB object */
389         if (_jaxbObject == null) {
390             createJaxbObject();
391         }
392 
393         /* Set this binding properties from java object property values */
394         for (ICobolBinding child : getChildrenList()) {
395             /*
396              * Children that are not bound to a jaxb property are ignored. This
397              * includes Choices and dynamically generated counbters for
398              * instance.
399              */
400             if (!child.isBound()) {
401                 continue;
402             } else {
403                 Object value = ClassUtil.invokeGetProperty(_jaxbObject,
404                         child.getJaxbName(), child.getJaxbType(),
405                         child.getMaxOccurs());
406                 if (_log.isDebugEnabled()) {
407                     _log.debug("Getting value from JAXB property "
408                             + child.getJaxbName() + " value=" + value);
409                 }
410                 child.setObjectValue(value);
411 
412                 /*
413                  * If this is a variable size array or list, make sure any
414                  * associated counter is updated
415                  */
416                 if (child.getMaxOccurs() > 1
417                         && child.getMinOccurs() < child.getMaxOccurs()) {
418                     setCounterValue(child.getDependingOn(),
419                             ((List < ? >) value).size());
420                 }
421             }
422         }
423     }
424 
425     /** {@inheritDoc} */
426     public void setJaxbPropertyValue(final int index) throws HostException {
427         setPropertyValue(index);
428     }
429 
430     /** {@inheritDoc} */
431     public void setPropertyValue(final int index) throws HostException {
432 
433         ICobolBinding child = getChildrenList().get(index);
434 
435         /*
436          * Children that are not bound to a value object are ignored. This
437          * includes Choices and dynamically generated counters for instance.
438          */
439         if (!child.isBound()) {
440             return;
441         }
442 
443         // Lookup the setter parameter type on the actual object
444         // We are making the assumption that the setter method name is unique
445         Class < ? > param = child.getJaxbType();
446         String setterName = ClassUtil.getSetterMethodName(child.getJaxbName());
447         for (Method method : _jaxbObject.getClass().getMethods()) {
448             if (method.getName().equals(setterName)) {
449                 param = method.getParameterTypes()[0];
450                 break;
451             }
452         }
453 
454         Object value = child.getObjectValue(param);
455         if (_log.isDebugEnabled()) {
456             _log.debug("Setting value of JAXB property " + child.getJaxbName()
457                     + " value=" + value);
458         }
459 
460         ClassUtil.invokeSetProperty(_jaxbObject, child.getJaxbName(), value,
461                 param);
462     }
463 
464     /** {@inheritDoc} */
465     public Object getObjectValue(final Class < ? > type) throws HostException {
466         if (type.equals(getJaxbType())) {
467             return _jaxbObject;
468         } else if (type.getName().equals(getValueObjectClassName())) {
469             return _jaxbObject;
470         } else {
471             throw new HostException("Attempt to get binding " + getJaxbName()
472                     + " as an incompatible type " + type);
473         }
474     }
475 
476     /** {@inheritDoc} */
477     public void setObjectValue(final Object value) throws HostException {
478         if (value == null) {
479             _jaxbObject = null;
480             return;
481         }
482         if (value.getClass().equals(getJaxbType())) {
483             _jaxbObject = value;
484         } else if (value.getClass().getName().equals(getValueObjectClassName())) {
485             _jaxbObject = value;
486         } else {
487             throw new HostException("Attempt to set binding " + getJaxbName()
488                     + " from an incompatible value " + value);
489         }
490     }
491 
492     /**
493      * @return the java object factory for value objects creation
494      */
495     public Object getObjectFactory() {
496         return _jaxbObjectFactory;
497     }
498 
499     /**
500      * @param objectFactory the java object factory for value objects creation
501      */
502     public void setObjectFactory(final Object objectFactory) {
503         _jaxbObjectFactory = objectFactory;
504     }
505 
506     /** {@inheritDoc} */
507     public boolean isSet() {
508         return (_jaxbObject != null);
509     }
510 
511 }