View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2010 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.xsdc.gen;
12  
13  import java.io.File;
14  import java.io.FileNotFoundException;
15  import java.io.FileOutputStream;
16  import java.io.IOException;
17  import java.io.OutputStream;
18  import java.io.StringWriter;
19  import java.net.URI;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.Map;
23  import java.util.Properties;
24  
25  import javax.xml.namespace.QName;
26  import javax.xml.parsers.DocumentBuilder;
27  import javax.xml.parsers.DocumentBuilderFactory;
28  import javax.xml.parsers.ParserConfigurationException;
29  import javax.xml.transform.OutputKeys;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.tools.ant.BuildException;
34  import org.apache.ws.commons.schema.XmlSchema;
35  import org.apache.ws.commons.schema.XmlSchemaAll;
36  import org.apache.ws.commons.schema.XmlSchemaAnnotation;
37  import org.apache.ws.commons.schema.XmlSchemaAppInfo;
38  import org.apache.ws.commons.schema.XmlSchemaCollection;
39  import org.apache.ws.commons.schema.XmlSchemaComplexType;
40  import org.apache.ws.commons.schema.XmlSchemaElement;
41  import org.apache.ws.commons.schema.XmlSchemaFractionDigitsFacet;
42  import org.apache.ws.commons.schema.XmlSchemaImport;
43  import org.apache.ws.commons.schema.XmlSchemaLengthFacet;
44  import org.apache.ws.commons.schema.XmlSchemaObject;
45  import org.apache.ws.commons.schema.XmlSchemaObjectCollection;
46  import org.apache.ws.commons.schema.XmlSchemaPatternFacet;
47  import org.apache.ws.commons.schema.XmlSchemaSequence;
48  import org.apache.ws.commons.schema.XmlSchemaSimpleType;
49  import org.apache.ws.commons.schema.XmlSchemaSimpleTypeList;
50  import org.apache.ws.commons.schema.XmlSchemaSimpleTypeRestriction;
51  import org.apache.ws.commons.schema.XmlSchemaSimpleTypeUnion;
52  import org.apache.ws.commons.schema.XmlSchemaTotalDigitsFacet;
53  import org.apache.ws.commons.schema.XmlSchemaType;
54  import org.apache.ws.commons.schema.utils.NamespaceMap;
55  import org.apache.ws.commons.schema.utils.NamespacePrefixList;
56  import org.w3c.dom.Attr;
57  import org.w3c.dom.Comment;
58  import org.w3c.dom.Document;
59  import org.w3c.dom.Element;
60  import org.w3c.dom.Node;
61  import org.w3c.dom.NodeList;
62  import org.xml.sax.SAXException;
63  
64  import com.legstar.codegen.tasks.SourceToXsdCobolTask;
65  import com.legstar.coxb.CobolMarkup;
66  import com.legstar.coxb.CobolType;
67  
68  /**
69   * This Ant Task maps XML schema elements with Cobol data types. The result is
70   * a new XML Schema with special annotations that can be used to map XML to
71   * legacy data.
72   *<p/>
73   * TODO Examine processing of Default and Fixed attributes which are not handled
74   * at the moment.
75   */
76  public class XsdCobolAnnotator extends SourceToXsdCobolTask {
77  
78      /** Logger. */
79      private final Log _log = LogFactory.getLog(getClass());
80  
81      /* ====================================================================== */
82      /* = Properties section = */
83      /* ====================================================================== */
84  
85      /** This builder is used for annotation markup elements. */
86      private final DocumentBuilder mDb;
87  
88      /** Associates a cobol type to an XSD primitive type. */
89      private XsdCobolTypeMap mTypeMap = new XsdCobolTypeMap();
90  
91      /** Used to build valid cobol names from java names. */
92      private CobolNameResolver mNameResolver;
93  
94      /** Parameters that can be externally modified. */
95      private Properties mOptions;
96  
97      /**
98       * Must be a map of Type names and Element names where Type names
99       * must be registered.
100      */
101     private Map < QName, QName > mRootElements;
102 
103     /** Maps a complexType to a Java qualified class name. */
104     private Map < String, String > mComplexTypeToJavaClassMap;
105 
106     /* ====================================================================== */
107     /* = Constants section = */
108     /* ====================================================================== */
109     /** XML Schema namespace. */
110     private static final String XSD_NS = "http://www.w3.org/2001/XMLSchema";
111 
112     /** SOAP namespace. */
113     private static final String SOAP_NS =
114             "http://schemas.xmlsoap.org/wsdl/soap/";
115 
116     /** WSDL namespace. */
117     private static final String WSDL_NS = "http://schemas.xmlsoap.org/wsdl/";
118 
119     /** WS-Addressing namespace. */
120     private static final String ADDRESSING_NS =
121             "http://schemas.xmlsoap.org/ws/2004/08/addressing";
122 
123     /** Namespaces namespace. */
124     private static final String NS_NS = "http://www.w3.org/2000/xmlns/";
125 
126     /** Cobol annotations namespace. */
127     private static final String COBOL_NS = "http://www.legsem.com/legstar/xml/cobol-binding-1.0.1.xsd";
128 
129     /** Cobol annotations default prefix. */
130     private static final String COBOL_PFX = "cb";
131 
132     /** Cobol annotation parent element name. */
133     private static final String COBOL_PARENT_ELN = "cb:cobolElements";
134 
135     /** Cobol annotation element name. */
136     private static final String COBOL_ELN = "cb:cobolElement";
137 
138     /** Cobol annotation parent complex type name. */
139     private static final String COBOL_PARENT_CTN = "cb:cobolComplexTypes";
140 
141     /** Cobol annotation complex type name. */
142     private static final String COBOL_CTN = "cb:cobolComplexType";
143 
144     /** Maximum size of a Cobol item name. */
145     public static final int MAX_COBOLNAME_LEN = 30;
146 
147     /** The cobol reserved words substitution file name. */
148     private static final String OPTIONS_FILE_NAME = "xsdcoptions.properties";
149 
150     /** This is the target namespace from the source Xsd. */
151     private String mOriginalTargetNamespace;
152 
153     /** Indicates if namespace switching is requested. */
154     private boolean mNeedNamespaceSwitch = false;
155 
156     /**
157      * At construction time, a DOM factory is created. The DOM factory is later
158      * used to create DOM documents which serve as home for all the annotation
159      * markup nodes that will be created during the annotation process.
160      * We also load cobol name substitution lists and options.
161      */
162     public XsdCobolAnnotator() {
163         setModel(new XsdToXsdCobolModel());
164         try {
165             DocumentBuilderFactory docFac =
166                     DocumentBuilderFactory.newInstance();
167             docFac.setNamespaceAware(true);
168             mDb = docFac.newDocumentBuilder();
169             mNameResolver = new CobolNameResolver();
170             mOptions = XsdcUtil.loadFromPropFile(this.getClass(),
171                     OPTIONS_FILE_NAME);
172         } catch (ParserConfigurationException e) {
173             throw (new BuildException(e));
174         } catch (CobolNameResolverException e) {
175             throw (new BuildException(e));
176         } catch (IOException e) {
177             throw (new BuildException(e));
178         }
179     }
180 
181     /**
182      * The ant execute method. Generates a new annotated schema.
183      */
184     public void execute() {
185         if (_log.isDebugEnabled()) {
186             _log.debug("XML Schema Cobol annotation started");
187         }
188         checkInputXsd();
189         XmlSchema schema = getSchema();
190 
191         checkAllParameters(schema);
192 
193         annotateSchema(schema);
194         addRootElements(schema);
195 
196         OutputStream out = getOutputStream();
197 
198         XmlSchemaObjectCollection items = schema.getItems();
199 
200         /*
201          * Process each element in the input Schema.
202          * Only elements are processed because we need to reconstruct
203          * a hierarchy of complex types. Elements give us the roots
204          * to start from.
205          */
206         try {
207             for (int i = 0; i < items.getCount(); i++) {
208                 XmlSchemaObject obj = items.getItem(i);
209                 if (obj instanceof XmlSchemaElement) {
210                     annotateElement(schema, (XmlSchemaElement) obj, 1);
211                 }
212             }
213         } catch (XsdCobolAnnotatorException e) {
214             throw (new BuildException(e));
215         }
216 
217         /* Make sure the generated schema has an XML declaration */
218         schema.setInputEncoding("UTF-8");
219         Map < String, String > options = new HashMap < String, String >();
220         options.put(OutputKeys.INDENT, "yes");
221         options.put(OutputKeys.OMIT_XML_DECLARATION, "no");
222         schema.write(out, options);
223         if (_log.isDebugEnabled()) {
224             _log.debug("XML Schema Cobol annotation ended. Result:");
225             logSchema(schema);
226         }
227     }
228 
229     /**
230      * Checks that the xsd file provided is valid.
231      */
232     protected void checkInputXsd() {
233 
234         if (_log.isDebugEnabled()) {
235             _log.debug("   Source Xsd URI      = "
236                     + ((getInputXsdUri() == null) ? null
237                             : getInputXsdUri().toString()));
238         }
239         /* Check that we have a valid input XML schema. */
240         if (getInputXsdUri() == null) {
241             throw (new BuildException(
242                     "Invalid input XML schema"));
243         }
244 
245         /* Set a valid default target annotated XSD file name */
246         if (getTargetXsdFileName() == null
247                 || getTargetXsdFileName().length() == 0) {
248             String targetXsdFileName = getLastSegment(getInputXsdUri());
249             /*
250              * If there is no extension or extension is not xsd, add xsd as
251              * the extension.
252              */
253             int p = targetXsdFileName.lastIndexOf('.');
254             if (p > 0) {
255                 String ext = targetXsdFileName.substring(
256                         p, targetXsdFileName.length());
257                 if (ext.compareToIgnoreCase(".xsd") != 0) {
258                     targetXsdFileName += ".xsd";
259                 }
260             } else {
261                 targetXsdFileName += ".xsd";
262             }
263             setTargetXsdFileName(targetXsdFileName);
264         }
265     }
266 
267     /**
268      * Retrieves the last segment from a URI.
269      * 
270      * @param uri the uri to process
271      * @return the last segment of the path
272      */
273     private String getLastSegment(final URI uri) {
274         String path = uri.getPath();
275         if (path == null || path.length() < 2) {
276             return null;
277         }
278         if ((path.charAt(path.length() - 1)) == '/') {
279             path = path.substring(0, path.length() - 1);
280         }
281         int pos = path.lastIndexOf('/');
282         if (pos < 0) {
283             return null;
284         }
285         return path.substring(++pos, path.length());
286     }
287 
288     /**
289      * Loads the input XML Schema either from an XSD file or a WSDL file.
290      * 
291      * @return a ws-commons XmlSchema instance.
292      */
293     @SuppressWarnings("unchecked")
294     private XmlSchema getSchema() {
295 
296         if (_log.isDebugEnabled()) {
297             _log.debug("getSchema started");
298         }
299         /* Load the input schema */
300         XmlSchemaCollection schemaCol = new XmlSchemaCollection();
301         XmlSchema schema;
302         try {
303             Document doc = mDb.parse(getInputXsdUri().toString());
304 
305             /* Get the root element (skipping any comments) */
306             Node root = doc.getFirstChild();
307             while (root != null && root instanceof Comment) {
308                 root = root.getNextSibling();
309             }
310             if (root == null) {
311                 throw new BuildException("File " + getInputXsdUri().toString()
312                         + " does not contain an XML schema");
313             }
314 
315             /* Look for an XML schema node */
316             NodeList nodes = doc.getElementsByTagNameNS(XSD_NS, "schema");
317             if (nodes == null || nodes.getLength() == 0) {
318                 throw new BuildException("File " + getInputXsdUri().toString()
319                         + " does not contain an XML schema");
320             }
321             if (nodes.getLength() > 1) {
322                 _log.warn("Only the first XML schema in "
323                         + getInputXsdUri().toString()
324                         + " will be processed");
325             }
326 
327             /* Translate that DOM schema node into an XmlSchema */
328             schema = schemaCol.read((Element) nodes.item(0));
329 
330             /*
331              * If this schema contains an include/import, then these includes
332              * are what we need.
333              * The previous schema element was a mere container.
334              * Potentially, there might be more than one include/import but we
335              * restrict ourselves
336              * to the first one here.
337              * TODO add support for multiple imports
338              */
339             XmlSchemaObjectCollection includes = schema.getIncludes();
340             for (Iterator includedItems = includes.getIterator(); includedItems
341                     .hasNext();) {
342                 Object include = includedItems.next();
343                 if (include instanceof XmlSchemaImport) {
344                     schema = ((XmlSchemaImport) include).getSchema();
345                     break;
346                 }
347             }
348 
349             /*
350              * In case this is a wsdl file, we store certain namespace
351              * attributes of the root at the schema node level so that
352              * the schema node is complete from an XmlSchema standpoint.
353              * We filter out all WSDL related namespaces.
354              * 
355              * We also retrieve message parts which are bound to complex types.
356              * We will later add them as elements in the schema as they play the
357              * role of root elements.
358              */
359             if (root.getLocalName().compareTo("definitions") == 0
360                     && root.getNamespaceURI().compareTo(WSDL_NS) == 0) {
361 
362                 NamespaceMap prefixmap = new NamespaceMap();
363                 NamespacePrefixList npl = schema.getNamespaceContext();
364                 for (int i = 0; i < npl.getDeclaredPrefixes().length; i++) {
365                     prefixmap.add(npl.getDeclaredPrefixes()[i], npl
366                             .getNamespaceURI(
367                             npl.getDeclaredPrefixes()[i]));
368                 }
369                 for (int i = 0; i < root.getAttributes().getLength(); i++) {
370                     Attr attribute = (Attr) root.getAttributes().item(i);
371                     String namespaceURI = attribute.getNamespaceURI();
372                     String value = attribute.getValue();
373                     String name = attribute.getName();
374                     if (name.equals("targetNamespace")
375                             && schema.getTargetNamespace() == null) {
376                         schema.setTargetNamespace(value);
377                     } else if (namespaceURI != null
378                             && namespaceURI.compareTo(NS_NS) == 0
379                             && value.compareTo(SOAP_NS) != 0
380                             && value.compareTo(WSDL_NS) != 0
381                             && value.compareTo(ADDRESSING_NS) != 0) {
382                         prefixmap.add(attribute.getLocalName(), value);
383                     }
384                 }
385                 schema.setNamespaceContext(prefixmap);
386 
387                 addWsdlPartsAsRootElements(doc, schema.getTargetNamespace());
388 
389             }
390 
391         } catch (FileNotFoundException e) {
392             throw (new BuildException(e));
393         } catch (SAXException e) {
394             throw (new BuildException(e));
395         } catch (IOException e) {
396             throw (new BuildException(e));
397         }
398 
399         if (_log.isDebugEnabled()) {
400             _log.debug("getSchema ended. Target namespace = "
401                     + schema.getTargetNamespace());
402             _log.debug("XML Schema before annotations:");
403             logSchema(schema);
404         }
405         return schema;
406     }
407 
408     /**
409      * WSDL part elements which are bound to schema types (rather than
410      * elements),
411      * do not have an element counterpart in the wsdl schema.
412      * <p/>
413      * What we do here, is that we store them as additional root elements which
414      * will be later added to the schema.
415      * 
416      * @param doc the XML document containing the WSDL
417      * @param targetNamespace the target namespace for the schema
418      */
419     protected void addWsdlPartsAsRootElements(final Document doc,
420             final String targetNamespace) {
421         NodeList nodes = doc.getElementsByTagNameNS(WSDL_NS, "part");
422         if (nodes != null) {
423             for (int i = 0; i < nodes.getLength(); i++) {
424                 Element partElement = (Element) nodes.item(i);
425                 String name = partElement.getAttribute("name");
426                 String type = partElement.getAttribute("type");
427                 if (type != null && type.length() > 0) {
428                     if (type.indexOf(':') > 0) {
429                         type = type.substring(type.indexOf(':') + 1);
430                     }
431                     if (mRootElements == null) {
432                         mRootElements = new HashMap < QName, QName >();
433                     }
434                     mRootElements.put(
435                             new QName(targetNamespace, type),
436                             new QName(targetNamespace, name));
437                 }
438             }
439         }
440     }
441 
442     /**
443      * Checks that parameters set are valid for the specified schema.
444      * 
445      * @param schema the input schema
446      */
447     private void checkAllParameters(final XmlSchema schema) {
448 
449         /* If the user did not specify a namespace get the schema one. */
450         if (getNamespace() == null || getNamespace().length() == 0) {
451             setNamespace(schema.getTargetNamespace());
452         } else {
453             /* Might be a case where we need to switch namespaces */
454             switchTargetNamespace(schema, getNamespace());
455         }
456 
457         /*
458          * Xsd file name is not mandatory because we can generate a
459          * sensible default value.
460          */
461         super.checkInput(false, false);
462 
463     }
464 
465     /**
466      * When the user requests a new target namespace, we need to switch
467      * from the original one keeping the same prefix.
468      * A namespace prefix list is immutable so we need to create a
469      * complete namespace context.
470      * 
471      * @param schema the schema being built
472      * @param newTargetNamespace the new target namespace
473      */
474     private void switchTargetNamespace(
475             final XmlSchema schema, final String newTargetNamespace) {
476         mOriginalTargetNamespace = schema.getTargetNamespace();
477         if (mOriginalTargetNamespace.equals(newTargetNamespace)) {
478             return;
479         }
480         mNeedNamespaceSwitch = true;
481         schema.setTargetNamespace(newTargetNamespace);
482         NamespaceMap prefixmap = new NamespaceMap();
483         NamespacePrefixList npl = schema.getNamespaceContext();
484         for (int i = 0; i < npl.getDeclaredPrefixes().length; i++) {
485             String ns = npl.getNamespaceURI(npl.getDeclaredPrefixes()[i]);
486             if (ns.equals(mOriginalTargetNamespace)) {
487                 prefixmap.add(npl.getDeclaredPrefixes()[i], newTargetNamespace);
488             } else {
489                 prefixmap.add(npl.getDeclaredPrefixes()[i], ns);
490             }
491         }
492         schema.setNamespaceContext(prefixmap);
493     }
494 
495     /**
496      * Switches namespace for a schema element.
497      * 
498      * @param schema the schema
499      * @param obj the schema element
500      */
501     private void switchNamespace(
502             final XmlSchema schema,
503             final XmlSchemaElement obj) {
504         if (obj.getQName().getNamespaceURI().equals(mOriginalTargetNamespace)) {
505             QName newQName = new QName(
506                     schema.getTargetNamespace(),
507                     obj.getQName().getLocalPart(),
508                     obj.getQName().getPrefix());
509             obj.setQName(newQName);
510         }
511         QName typeQName = obj.getSchemaTypeName();
512         if (typeQName.getNamespaceURI().equals(mOriginalTargetNamespace)) {
513             QName newTypeQName = new QName(
514                     schema.getTargetNamespace(),
515                     typeQName.getLocalPart(),
516                     typeQName.getPrefix());
517             obj.setSchemaTypeName(newTypeQName);
518         }
519     }
520 
521     /**
522      * Adds COXB annotations at the schema level.
523      * 
524      * @param schema the current schema being generated
525      */
526     private void annotateSchema(final XmlSchema schema) {
527         if (_log.isDebugEnabled()) {
528             _log.debug("AnnotateSchema started");
529         }
530         /* Add the JAXB and COXB namespaces to the target schema */
531         NamespaceMap prefixmap = new NamespaceMap();
532         NamespacePrefixList npl = schema.getNamespaceContext();
533         for (int i = 0; i < npl.getDeclaredPrefixes().length; i++) {
534             prefixmap.add(npl.getDeclaredPrefixes()[i], npl.getNamespaceURI(
535                     npl.getDeclaredPrefixes()[i]));
536         }
537         /*
538          * TODO check that prefix does not conflict with an existing one.
539          * 3 situations might occur:
540          * 1: an existing prefix with same value exists and points to a
541          * different namespace
542          * 2: namespace is already listed with a different prefix
543          * 3: namespace is already listed with the same prefix
544          */
545         prefixmap.add(COBOL_PFX, COBOL_NS);
546         schema.setNamespaceContext(prefixmap);
547 
548         if (_log.isDebugEnabled()) {
549             _log.debug("AnnotateSchema ended");
550         }
551 
552     }
553 
554     /**
555      * As an option, caller might want to add root elements to the
556      * schema before it is processed. To be valid these elements
557      * must map to an existing type and should not conflict with
558      * existing elements.
559      * 
560      * @param schema the schema to be annotated
561      */
562     private void addRootElements(final XmlSchema schema) {
563         if (mRootElements == null) {
564             return;
565         }
566         for (QName typeName : mRootElements.keySet()) {
567             XmlSchemaType schemaType = schema.getTypeByName(typeName);
568             QName eln = mRootElements.get(typeName);
569             if (schema.getTypeByName(typeName) == null) {
570                 throw (new BuildException("Root element " + eln
571                         + " has unknown type " + typeName));
572             }
573 
574             QName elementName = mRootElements.get(typeName);
575             XmlSchemaElement el = schema.getElementByName(elementName);
576             if (el == null) {
577                 el = new XmlSchemaElement();
578                 el.setQName(elementName);
579                 el.setName(elementName.getLocalPart());
580                 el.setSchemaTypeName(typeName);
581                 el.setSchemaType(schemaType);
582                 schema.getElements().add(eln, el);
583                 schema.getItems().add(el);
584             }
585         }
586     }
587 
588     /**
589      * Creates an output stream from the input XML schema file name.
590      * 
591      * @return an output stream
592      */
593     private OutputStream getOutputStream() {
594         OutputStream out;
595         String outPath = getTargetDir().getPath() + File.separator
596                 + getTargetXsdFileName();
597         try {
598             out = new FileOutputStream(new File(outPath));
599             return out;
600         } catch (FileNotFoundException e) {
601             throw (new BuildException(e));
602         }
603     }
604 
605     /**
606      * Main annotation process applied to an XML schema element.
607      * 
608      * @param schema the XML Schema being annotated
609      * @param obj the XML Schema element to annotate
610      * @param level the current level in the elements hierarchy. This is used
611      *            to create Cobol levels with the same depth as the input XML
612      *            schema.
613      * @throws XsdCobolAnnotatorException if annotation fails
614      */
615     public void annotateElement(
616             final XmlSchema schema,
617             final XmlSchemaElement obj,
618             final int level) throws XsdCobolAnnotatorException {
619         /*
620          * If this element is referencing another, it might not be useful to
621          * annotate it.
622          */
623         if (obj.getRefName() != null) {
624             return;
625         }
626         if (_log.isDebugEnabled()) {
627             _log.debug("annotate started for element = " + obj.getName());
628         }
629 
630         /* Create a DOM document to hold annotation notes */
631         Document doc = mDb.newDocument();
632         Element el = doc.createElementNS(COBOL_NS, COBOL_PARENT_ELN);
633         Element elc = doc.createElementNS(COBOL_NS, COBOL_ELN);
634         setAttributes(schema, obj, elc, level);
635         el.appendChild(elc);
636 
637         /* Add an annotation */
638         obj.setAnnotation(createAnnotation(el));
639 
640         /*
641          * If this element is of a complex type, it is time to
642          * process the complex type.
643          */
644         if (obj.getSchemaType() instanceof XmlSchemaComplexType) {
645             XmlSchemaComplexType type = (XmlSchemaComplexType) obj
646                     .getSchemaType();
647             annotateComplexType(schema, type, level);
648         }
649 
650         if (mNeedNamespaceSwitch) {
651             switchNamespace(schema, obj);
652         }
653 
654         if (_log.isDebugEnabled()) {
655             _log.debug("annotate ended for element = " + obj.getName());
656         }
657     }
658 
659     /**
660      * Main annotation process applied to an XML schema complex type.
661      * <p/>
662      * For now the only attribute that needs to go at the complex Type level is
663      * the java class name used when the schema is derived from a POJO rather
664      * than an XSD or WSDL.
665      * 
666      * @param schema the XML Schema being annotated
667      * @param type the XML Schema type to annotate
668      * @param level the current level in the elements hierarchy. This is used
669      *            to create Cobol levels with the same depth as the input XML
670      *            schema.
671      * @throws XsdCobolAnnotatorException if annotation fails
672      */
673     public void annotateComplexType(
674             final XmlSchema schema,
675             final XmlSchemaComplexType type,
676             final int level) throws XsdCobolAnnotatorException {
677         if (_log.isDebugEnabled()) {
678             _log.debug("annotate started for complex type = " + type.getName());
679         }
680 
681         /*
682          * If this complex type maps to a java class name, add this
683          * attribute to the annotation
684          */
685         if (mComplexTypeToJavaClassMap != null) {
686             String javaClassName =
687                     mComplexTypeToJavaClassMap.get(type.getName());
688             if (javaClassName != null) {
689                 if (_log.isDebugEnabled()) {
690                     _log.debug("   java class name = " + javaClassName);
691                 }
692                 /* Create a DOM document to hold annotation notes */
693                 Document doc = mDb.newDocument();
694                 Element el = doc.createElementNS(COBOL_NS, COBOL_PARENT_CTN);
695                 Element elc = doc.createElementNS(COBOL_NS, COBOL_CTN);
696                 elc.setAttribute(CobolMarkup.JAVA_CLASS_NAME, javaClassName);
697                 el.appendChild(elc);
698 
699                 /* Add an annotation */
700                 type.setAnnotation(createAnnotation(el));
701             }
702         }
703 
704         annotateComplexTypeElements(schema, type, level);
705 
706         if (_log.isDebugEnabled()) {
707             _log.debug("annotate ended for complex type = " + type.getName());
708         }
709     }
710 
711     /**
712      * Annotate a complex type elements.
713      * 
714      * @param schema the parent schema
715      * @param type the complex type
716      * @param level the current level in the elements hierarchy. This is used
717      *            to create Cobol levels with the same depth as the input XML
718      *            schema.
719      * @throws XsdCobolAnnotatorException if annotating fails
720      */
721     protected void annotateComplexTypeElements(
722             final XmlSchema schema,
723             final XmlSchemaComplexType type,
724             final int level) throws XsdCobolAnnotatorException {
725 
726         /* Complex types might contain xsd:attribute and no particles at all */
727         if (type.getParticle() != null) {
728 
729             if (type.getParticle().getMaxOccurs() > 1) {
730                 /* TODO find a way to handle occuring sequences and alls */
731                 _log
732                         .warn("Complex type "
733                                 + type.getName()
734                                 + " contains a multi-occurence particle that is ignored");
735             }
736 
737             if (type.getParticle() instanceof XmlSchemaSequence) {
738                 XmlSchemaSequence sequence = (XmlSchemaSequence) type
739                         .getParticle();
740                 annotateCollectionElements(schema, sequence.getItems(), level);
741                 return;
742 
743             } else if (type.getParticle() instanceof XmlSchemaAll) {
744                 XmlSchemaAll all = (XmlSchemaAll) type.getParticle();
745                 annotateCollectionElements(schema, all.getItems(), level);
746                 return;
747             }
748         }
749 
750         /* TODO process other particle types of interest */
751         /* TODO find a way to handle xsd:attribute */
752         _log.warn("Complex type " + type.getName()
753                 + " does not contain a sequence or all element");
754     }
755 
756     /**
757      * Take all elements from a collection and annotate them.
758      * 
759      * @param schema the parent schema
760      * @param items the parent collection
761      * @param level the current level in the elements hierarchy. This is used
762      *            to create Cobol levels with the same depth as the input XML
763      *            schema.
764      * @throws XsdCobolAnnotatorException if annotating fails
765      */
766     protected void annotateCollectionElements(
767             final XmlSchema schema,
768             final XmlSchemaObjectCollection items,
769             final int level) throws XsdCobolAnnotatorException {
770 
771         /* Process each element in the collection */
772         for (int i = 0; i < items.getCount(); i++) {
773             XmlSchemaObject element = items.getItem(i);
774             if (element instanceof XmlSchemaElement) {
775                 annotateElement(schema, (XmlSchemaElement) element,
776                         level + 2);
777             }
778         }
779     }
780 
781     /**
782      * Create an Xml schema annotation ready to be added on some schema
783      * object.
784      * 
785      * @param el a DOM element holding the annotations as child elements
786      * @return an Xml schema annotation
787      */
788     private XmlSchemaAnnotation createAnnotation(final Element el) {
789         XmlSchemaAnnotation annotation = new XmlSchemaAnnotation();
790         XmlSchemaAppInfo appInfo = new XmlSchemaAppInfo();
791         NodeList markup = el.getChildNodes();
792         appInfo.setMarkup(markup);
793         annotation.getItems().add(appInfo);
794         return annotation;
795     }
796 
797     /**
798      * Create a set of attributes for the Cobol annotation element depending
799      * on corresponding XML schema type.
800      * 
801      * @param schema the XML Schema being annotated
802      * @param obj the XML Schema element to annotate
803      * @param elc the DOM Element representing the Cobol annotation
804      * @param level the current level in the elements hierarchy. This is used
805      *            to create Cobol levels with the same depth as the input XML
806      *            schema.
807      * @throws XsdCobolAnnotatorException if annotation fails
808      */
809     private void setAttributes(
810             final XmlSchema schema,
811             final XmlSchemaElement obj,
812             final Element elc,
813             final int level) throws XsdCobolAnnotatorException {
814 
815         if (_log.isDebugEnabled()) {
816             _log.debug("setAttributes started for element  = "
817                     + obj.getName());
818             _log.debug("   XmlSchemaElement QName          = "
819                     + obj.getQName());
820             _log.debug("   XmlSchemaElement SchemaType     = "
821                     + obj.getSchemaType());
822             _log.debug("   XmlSchemaElement SchemaTypeName = "
823                     + obj.getSchemaTypeName());
824             _log.debug("   XmlSchemaElement MaxOccurs      = "
825                     + obj.getMaxOccurs());
826             _log.debug("   XmlSchemaElement MinOccurs      = "
827                     + obj.getMinOccurs());
828             _log.debug("   XmlSchemaElement RefName        = "
829                     + obj.getRefName());
830             _log.debug("   XmlSchemaElement DefaultValue   = "
831                     + obj.getDefaultValue());
832             _log.debug("   XmlSchemaElement FixedValue     = "
833                     + obj.getFixedValue());
834         }
835 
836         /* Add cobol attributes valid for all types */
837         elc.setAttribute(CobolMarkup.LEVEL_NUMBER, Integer.toString(level));
838         elc.setAttribute(CobolMarkup.COBOL_NAME, getCobolName(obj.getName()));
839 
840         if (_log.isDebugEnabled()) {
841             _log.debug("   Cobol level          = "
842                     + level);
843             _log.debug("   Cobol name           = "
844                     + elc.getAttribute(CobolMarkup.COBOL_NAME));
845         }
846         /*
847          * The semantic for maxOccurs is different for Cobol annotations than
848          * for XML Schema. a maxOccurs of 1 is a one item array for Cobol which
849          * is different from a simple item. If schema maxOccurs=1 we do not
850          * insert a Cobol maxOccurs annotation at all.
851          */
852         /*
853          * There is no natural mapping from XML schema arrays to Cobol arrays
854          * with depending on clause. This means that all XML Schema arrays are
855          * mapped to fixed size Cobol arrays. Since this would result in very
856          * inefficient Cobol structures, we impose a limit on arrays sizes.
857          */
858         if (obj.getMaxOccurs() > 1) {
859             if (obj.getMaxOccurs() > Short.MAX_VALUE) {
860                 String defaultMaxOccurs = XsdcUtil.getStringOption(mOptions,
861                         "default.max.occurs");
862                 elc.setAttribute(CobolMarkup.MAX_OCCURS, defaultMaxOccurs);
863                 _log.warn("Max occurs for element " + obj.getName()
864                         + " has been set to default value " + defaultMaxOccurs);
865             } else {
866                 elc.setAttribute(CobolMarkup.MAX_OCCURS,
867                         Long.toString(obj.getMaxOccurs()));
868             }
869 
870             elc.setAttribute(CobolMarkup.MIN_OCCURS,
871                     Long.toString(obj.getMinOccurs()));
872 
873             if (_log.isDebugEnabled()) {
874                 _log.debug("   Cobol minOccurs      = "
875                         + elc.getAttribute(CobolMarkup.MIN_OCCURS));
876                 _log.debug("   Cobol maxOccurs      = "
877                         + elc.getAttribute(CobolMarkup.MAX_OCCURS));
878             }
879         }
880 
881         /* Examine inner simple and complex types */
882         if (obj.getSchemaType() instanceof XmlSchemaSimpleType) {
883 
884             setSimpleTypeAttributes(schema,
885                     (XmlSchemaSimpleType) obj.getSchemaType(), elc);
886 
887         } else if (obj.getSchemaType() instanceof XmlSchemaComplexType) {
888 
889             setComplexTypeAttributes(
890                     schema, (XmlSchemaComplexType) obj.getSchemaType(), elc,
891                     level);
892         }
893         if (_log.isDebugEnabled()) {
894             _log.debug("setAttributes ended for element = " + obj.getName());
895         }
896     }
897 
898     /**
899      * Create a set of cobol attributes for a simple XML schema type.
900      * 
901      * @param schema the XML Schema being annotated
902      * @param type the XML schema type
903      * @param elc the DOM Element representing the Cobol annotation
904      * @throws XsdCobolAnnotatorException if annotation fails
905      */
906     private void setSimpleTypeAttributes(
907             final XmlSchema schema,
908             final XmlSchemaSimpleType type,
909             final Element elc) throws XsdCobolAnnotatorException {
910 
911         if (_log.isDebugEnabled()) {
912             _log.debug("setSimpleTypeAttributes started for type = "
913                     + type.getName());
914             _log.debug("   XmlSchemaType QName                   = "
915                     + type.getQName());
916             _log.debug("   XmlSchemaType BaseSchemaType          = "
917                     + type.getBaseSchemaType());
918             _log.debug("   XmlSchemaType DataType                = "
919                     + type.getDataType());
920             _log.debug("   XmlSchemaType DeriveBy                = "
921                     + type.getDeriveBy());
922         }
923         /*
924          * Somewhere in this simple type hierarchy there must be a primitive
925          * type from which it is derived.
926          */
927         QName primitiveType = getPrimitiveType(schema, type);
928 
929         /* From the primitive XML schema type we infer a candidate Cobol type */
930         CobolType cobolType = mTypeMap.get(primitiveType);
931         if (cobolType == null) {
932             throw new XsdCobolAnnotatorException(
933                     "Unsupported XML Schema type " + type.getQName());
934         }
935         elc.setAttribute(CobolMarkup.TYPE, cobolType.name());
936         if (_log.isDebugEnabled()) {
937             _log.debug("   Cobol type           = "
938                     + elc.getAttribute(CobolMarkup.TYPE));
939         }
940 
941         /*
942          * Simple types can derive from xsd:list, in which case we need to
943          * map them to arrays. Lists being unbounded, we need to artificially
944          * set a maximum bound to the corresponding Cobol array.
945          */
946         if (type.getContent() instanceof XmlSchemaSimpleTypeList) {
947             elc.setAttribute(CobolMarkup.MIN_OCCURS, "1");
948             elc.setAttribute(CobolMarkup.MAX_OCCURS,
949                     XsdcUtil.getStringOption(mOptions, "default.max.occurs"));
950             if (_log.isDebugEnabled()) {
951                 _log.debug("   Cobol minOccurs      = "
952                         + elc.getAttribute(CobolMarkup.MIN_OCCURS));
953                 _log.debug("   Cobol maxOccurs      = "
954                         + elc.getAttribute(CobolMarkup.MAX_OCCURS));
955             }
956         }
957 
958         /* We collect all facets of interest from the type restriction */
959         XsdFacets facets = new XsdFacets();
960         getFacets(schema, type, facets);
961 
962         /* Based on the element type and facets we gather more attributes */
963         switch (cobolType) {
964         case ALPHANUMERIC_ITEM:
965             setAlphaNumericAttributes(primitiveType, facets, elc);
966             break;
967         case BINARY_ITEM:
968             setBinaryAttributes(primitiveType, facets, elc);
969             break;
970         case PACKED_DECIMAL_ITEM:
971             setDecimalAttributes(primitiveType, facets, elc);
972             break;
973         case SINGLE_FLOAT_ITEM:
974             setSingleFloatAttributes(primitiveType, elc);
975             break;
976         case DOUBLE_FLOAT_ITEM:
977             setDoubleFloatAttributes(primitiveType, elc);
978             break;
979         case OCTET_STREAM_ITEM:
980             setOctetStreamAttributes(primitiveType, facets, elc);
981             break;
982         default:
983             throw new XsdCobolAnnotatorException(
984                     "Cobol type inferred is invalid");
985         }
986         if (_log.isDebugEnabled()) {
987             _log.debug("setSimpleTypeAttributes ended for type = "
988                     + type.getName());
989         }
990     }
991 
992     /**
993      * Annotated complex types as group items.
994      * 
995      * @param schema the XML Schema being annotated
996      * @param type the XML schema type
997      * @param elc the DOM Element representing the Cobol annotation
998      * @param level the current level in the type hierarchy
999      * @throws XsdCobolAnnotatorException if annotation fails
1000      */
1001     private void setComplexTypeAttributes(
1002             final XmlSchema schema,
1003             final XmlSchemaComplexType type,
1004             final Element elc,
1005             final int level) throws XsdCobolAnnotatorException {
1006 
1007         if (_log.isDebugEnabled()) {
1008             _log.debug("setComplexTypeAttributes started for type = "
1009                     + type.getName());
1010             _log.debug("   XmlSchemaType QName                    = "
1011                     + type.getQName());
1012         }
1013 
1014         elc.setAttribute(CobolMarkup.TYPE, CobolType.GROUP_ITEM.name());
1015         if (_log.isDebugEnabled()) {
1016             _log.debug("   Cobol type           = "
1017                     + elc.getAttribute(CobolMarkup.TYPE));
1018         }
1019 
1020         if (_log.isDebugEnabled()) {
1021             _log.debug("setComplexTypeAttributes ended for type = "
1022                     + type.getQName());
1023         }
1024     }
1025 
1026     /**
1027      * COBOL Alphanumerics are bounded. They must have a fixed size. This method
1028      * tries to infer one.
1029      * 
1030      * @param primitiveType the XML Schema primitive type
1031      * @param facets the set of XML schema facets
1032      * @param elc the annotated element
1033      * @throws XsdCobolAnnotatorException if attributes cannot be set
1034      */
1035     private void setAlphaNumericAttributes(
1036             final QName primitiveType,
1037             final XsdFacets facets,
1038             final Element elc) throws XsdCobolAnnotatorException {
1039 
1040         if (_log.isDebugEnabled()) {
1041             _log.debug("setAlphaNumericAttributes started for type = "
1042                     + primitiveType.getLocalPart());
1043         }
1044 
1045         /*
1046          * If a byte length cannot be inferred from a facet in the XML schema*
1047          * type hierarchy, use the default.
1048          */
1049         int byteLength = facets.getLength();
1050         if (byteLength < 0) {
1051             byteLength = XsdcUtil.getIntOption(mOptions,
1052                     "default.alphanumeric.len");
1053             _log.warn("Byte length for element "
1054                     + elc.getAttribute(CobolMarkup.COBOL_NAME)
1055                     + " has been set to default value " + byteLength);
1056         }
1057 
1058         /*
1059          * TODO add analysis of pattern facet to refine type and picture
1060          * inference
1061          * TODO see if there is a way to set isJustifiedRight
1062          */
1063         elc.setAttribute(CobolMarkup.PICTURE, "X("
1064                 + Integer.toString(byteLength) + ")");
1065         elc.setAttribute(CobolMarkup.USAGE, "DISPLAY");
1066 
1067         if (_log.isDebugEnabled()) {
1068             _log.debug("setAlphaNumericAttributes ended for type = "
1069                     + primitiveType.getLocalPart());
1070             _log.debug("   Cobol picture        = "
1071                     + elc.getAttribute(CobolMarkup.PICTURE));
1072             _log.debug("   Cobol usage          = "
1073                     + elc.getAttribute(CobolMarkup.USAGE));
1074         }
1075     }
1076 
1077     /**
1078      * COBOL octet stream data items are similar to alphanumerics.
1079      * 
1080      * @param primitiveType the XML Schema primitive type
1081      * @param facets the set of XML schema facets
1082      * @param elc the annotated element
1083      * @throws XsdCobolAnnotatorException if attributes cannot be set
1084      */
1085     private void setOctetStreamAttributes(
1086             final QName primitiveType,
1087             final XsdFacets facets,
1088             final Element elc) throws XsdCobolAnnotatorException {
1089 
1090         if (_log.isDebugEnabled()) {
1091             _log.debug("setOctetStreamAttributes started for type = "
1092                     + primitiveType.getLocalPart());
1093         }
1094         /*
1095          * If a byte length cannot be inferred from a facet in the XML schema*
1096          * type hierarchy, use the default.
1097          */
1098         int byteLength = facets.getLength();
1099         if (byteLength < 0) {
1100             byteLength = XsdcUtil.getIntOption(mOptions,
1101                     "default.octet.stream.len");
1102         }
1103 
1104         elc.setAttribute(CobolMarkup.PICTURE, "X("
1105                 + Integer.toString(byteLength) + ")");
1106         elc.setAttribute(CobolMarkup.USAGE, "DISPLAY");
1107 
1108         if (_log.isDebugEnabled()) {
1109             _log.debug("setOctetStreamAttributes ended for type = "
1110                     + primitiveType.getLocalPart());
1111             _log.debug("   Cobol picture        = "
1112                     + elc.getAttribute(CobolMarkup.PICTURE));
1113             _log.debug("   Cobol usage          = "
1114                     + elc.getAttribute(CobolMarkup.USAGE));
1115         }
1116     }
1117 
1118     /**
1119      * COBOL Binary numerics are signed or unsigned and have a fixed number of
1120      * digits. This method infers a number of digits and a sign.
1121      * 
1122      * @param primitiveType the XML Schema primitive type
1123      * @param facets the set of XML schema facets
1124      * @param elc the annotated element
1125      * @throws XsdCobolAnnotatorException if attributes cannot be set
1126      */
1127     private void setBinaryAttributes(
1128             final QName primitiveType,
1129             final XsdFacets facets,
1130             final Element elc) throws XsdCobolAnnotatorException {
1131 
1132         if (_log.isDebugEnabled()) {
1133             _log.debug("setBinaryAttributes started for type = "
1134                     + primitiveType.getLocalPart());
1135         }
1136         /*
1137          * If total digits are not specified in the XML schema, infer a suitable
1138          * default from the XML schema primitive type.
1139          */
1140         int totalDigits = facets.getTotalDigits();
1141         if (totalDigits < 0) {
1142             totalDigits = XsdcUtil.getIntOption(mOptions,
1143                     "default.int.total.digits");
1144             if (primitiveType.getLocalPart().equals("boolean")) {
1145                 totalDigits = XsdcUtil.getIntOption(mOptions,
1146                         "default.bool.total.digits");
1147             } else if (primitiveType.getLocalPart().equals("unsignedShort")) {
1148                 totalDigits = XsdcUtil.getIntOption(mOptions,
1149                         "default.short.total.digits");
1150             } else if (primitiveType.getLocalPart().equals("unsignedLong")) {
1151                 totalDigits = XsdcUtil.getIntOption(mOptions,
1152                         "default.long.total.digits");
1153             } else if (primitiveType.getLocalPart().equals("long")) {
1154                 totalDigits = XsdcUtil.getIntOption(mOptions,
1155                         "default.long.total.digits");
1156             } else if (primitiveType.getLocalPart().equals("short")) {
1157                 totalDigits = XsdcUtil.getIntOption(mOptions,
1158                         "default.short.total.digits");
1159             }
1160             /*
1161              * If a restriction on the number of digits is not explicitly
1162              * requested, use the cobol unlimited binary data type
1163              */
1164             elc.setAttribute(CobolMarkup.USAGE, "COMP-5");
1165         } else {
1166             elc.setAttribute(CobolMarkup.USAGE, "BINARY");
1167         }
1168 
1169         /* Determine if this is an unsigned numeric */
1170         boolean signed = true;
1171         if (primitiveType.getLocalPart().equals("boolean")
1172                 || primitiveType.getLocalPart().equals("positiveInteger")
1173                 || primitiveType.getLocalPart().equals("nonNegativeInteger")
1174                 || primitiveType.getLocalPart().equals("unsignedShort")
1175                 || primitiveType.getLocalPart().equals("unsignedLong")
1176                 || primitiveType.getLocalPart().equals("unsignedInt")) {
1177             signed = false;
1178         }
1179 
1180         /*
1181          * TODO add analysis of pattern facet to refine type and picture
1182          * inference
1183          */
1184         elc.setAttribute(CobolMarkup.PICTURE, "9("
1185                 + Integer.toString(totalDigits) + ")");
1186         elc.setAttribute(CobolMarkup.TOTAL_DIGITS,
1187                 Integer.toString(totalDigits));
1188         elc.setAttribute(CobolMarkup.IS_SIGNED, Boolean.toString(signed));
1189 
1190         if (_log.isDebugEnabled()) {
1191             _log.debug("setBinaryAttributes ended for type = "
1192                     + primitiveType.getLocalPart());
1193             _log.debug("   Cobol picture        = "
1194                     + elc.getAttribute(CobolMarkup.PICTURE));
1195             _log.debug("   Cobol usage          = "
1196                     + elc.getAttribute(CobolMarkup.USAGE));
1197             _log.debug("   Cobol totalDigits    = "
1198                     + elc.getAttribute(CobolMarkup.TOTAL_DIGITS));
1199             _log.debug("   Cobol isSigned       = "
1200                     + elc.getAttribute(CobolMarkup.IS_SIGNED));
1201         }
1202     }
1203 
1204     /**
1205      * COBOL Decimal numerics are signed or unsigned and have a fixed number of
1206      * total digits and fraction digits. This method infers numbers of digits
1207      * and sign.
1208      * 
1209      * @param primitiveType the XML Schema primitive type
1210      * @param facets the set of XML schema facets
1211      * @param elc the annotated element
1212      * @throws XsdCobolAnnotatorException if attributes cannot be set
1213      */
1214     private void setDecimalAttributes(
1215             final QName primitiveType,
1216             final XsdFacets facets,
1217             final Element elc) throws XsdCobolAnnotatorException {
1218 
1219         if (_log.isDebugEnabled()) {
1220             _log.debug("setDecimalAttributes started for type = "
1221                     + primitiveType.getLocalPart());
1222         }
1223 
1224         /*
1225          * If digits numbers are not specified in the XML schema, infer a
1226          * suitable default from the XML schema primitive type.
1227          */
1228         int totalDigits = facets.getTotalDigits();
1229         if (totalDigits < 0) {
1230             totalDigits = XsdcUtil.getIntOption(mOptions,
1231                     "default.dec.total.digits");
1232         }
1233         int fractionDigits = facets.getFractionDigits();
1234         if (fractionDigits < 0) {
1235             fractionDigits = XsdcUtil.getIntOption(mOptions,
1236                     "default.dec.frac.digits");
1237         }
1238 
1239         /* Consider decimals as always signed */
1240         boolean signed = true;
1241 
1242         elc.setAttribute(CobolMarkup.PICTURE, "9("
1243                 + Integer.toString(totalDigits - fractionDigits)
1244                 + ((fractionDigits > 0)
1245                         ? ")V9(" + Integer.toString(fractionDigits) : "")
1246                         + ")");
1247         elc.setAttribute(CobolMarkup.TOTAL_DIGITS,
1248                 Integer.toString(totalDigits));
1249         elc.setAttribute(CobolMarkup.FRACTION_DIGITS,
1250                 Integer.toString(fractionDigits));
1251         elc.setAttribute(CobolMarkup.USAGE, "COMP-3");
1252         elc.setAttribute(CobolMarkup.IS_SIGNED, Boolean.toString(signed));
1253 
1254         if (_log.isDebugEnabled()) {
1255             _log.debug("setDecimalAttributes ended for type = "
1256                     + primitiveType.getLocalPart());
1257             _log.debug("   Cobol picture        = "
1258                     + elc.getAttribute(CobolMarkup.PICTURE));
1259             _log.debug("   Cobol usage          = "
1260                     + elc.getAttribute(CobolMarkup.USAGE));
1261             _log.debug("   Cobol totalDigits    = "
1262                     + elc.getAttribute(CobolMarkup.TOTAL_DIGITS));
1263             _log.debug("   Cobol fractionDigits = "
1264                     + elc.getAttribute(CobolMarkup.FRACTION_DIGITS));
1265             _log.debug("   Cobol isSigned       = "
1266                     + elc.getAttribute(CobolMarkup.IS_SIGNED));
1267         }
1268     }
1269 
1270     /**
1271      * COBOL single float numerics.
1272      * 
1273      * @param primitiveType the XML Schema primitive type
1274      * @param elc the annotated element
1275      * @throws XsdCobolAnnotatorException if attributes cannot be set
1276      */
1277     private void setSingleFloatAttributes(
1278             final QName primitiveType,
1279             final Element elc) throws XsdCobolAnnotatorException {
1280 
1281         if (_log.isDebugEnabled()) {
1282             _log.debug("setSingleFloatAttributes started for type = "
1283                     + primitiveType.getLocalPart());
1284         }
1285 
1286         elc.setAttribute(CobolMarkup.USAGE, "COMP-1");
1287 
1288         if (_log.isDebugEnabled()) {
1289             _log.debug("setSingleFloatAttributes ended for type = "
1290                     + primitiveType.getLocalPart());
1291             _log.debug("   Cobol usage          = "
1292                     + elc.getAttribute(CobolMarkup.USAGE));
1293         }
1294     }
1295 
1296     /**
1297      * COBOL double float numerics.
1298      * 
1299      * @param primitiveType the XML Schema primitive type
1300      * @param elc the annotated element
1301      * @throws XsdCobolAnnotatorException if attributes cannot be set
1302      */
1303     private void setDoubleFloatAttributes(
1304             final QName primitiveType,
1305             final Element elc) throws XsdCobolAnnotatorException {
1306 
1307         if (_log.isDebugEnabled()) {
1308             _log.debug("setDoubleFloatAttributes started for type = "
1309                     + primitiveType.getLocalPart());
1310         }
1311 
1312         elc.setAttribute(CobolMarkup.USAGE, "COMP-2");
1313 
1314         if (_log.isDebugEnabled()) {
1315             _log.debug("setDoubleFloatAttributes ended for type = "
1316                     + primitiveType.getLocalPart());
1317             _log.debug("   Cobol usage          = "
1318                     + elc.getAttribute(CobolMarkup.USAGE));
1319         }
1320     }
1321 
1322     /**
1323      * Inferring the XML schema primitive type involves a recursive search
1324      * because types can form a hierarchy and restrict each other.
1325      * 
1326      * @param schema the XML Schema being annotated
1327      * @param type the type from which a primitive type should be inferred
1328      * @return the primitive type
1329      * @throws XsdCobolAnnotatorException if primitive type cannot be inferred
1330      */
1331     private QName getPrimitiveType(
1332             final XmlSchema schema,
1333             final XmlSchemaSimpleType type) throws XsdCobolAnnotatorException {
1334 
1335         if (_log.isDebugEnabled()) {
1336             _log.debug("getPrimitiveType started for type = " + type.getName());
1337         }
1338 
1339         QName typeName = type.getQName();
1340         if (typeName != null && XSD_NS.equals(typeName.getNamespaceURI())) {
1341             if (_log.isDebugEnabled()) {
1342                 _log.debug("getPrimitiveType ended for type = "
1343                         + type.getName());
1344                 _log.debug("   PrimitiveType = " + typeName);
1345             }
1346             return typeName;
1347         }
1348         if (type.getContent() != null) {
1349             if (type.getContent() instanceof XmlSchemaSimpleTypeRestriction) {
1350                 XmlSchemaSimpleTypeRestriction restriction =
1351                         (XmlSchemaSimpleTypeRestriction) type.getContent();
1352                 /*
1353                  * For an unknown reason, getBaseType() sometimes returns null.
1354                  * In such a case we have to locate the type using
1355                  * getBaseTypeName()
1356                  */
1357                 if (restriction.getBaseType() == null) {
1358                     typeName = restriction.getBaseTypeName();
1359                     if (typeName != null) {
1360                         if (XSD_NS.equals(typeName.getNamespaceURI())) {
1361                             if (_log.isDebugEnabled()) {
1362                                 _log.debug("getPrimitiveType ended for type = "
1363                                         + type.getName());
1364                                 _log.debug("   PrimitiveType = " + typeName);
1365                             }
1366                             return typeName;
1367                         }
1368                         /*
1369                          * Since restriction base type is not an XML Schema
1370                          * standard type, it must be defined in this schema.
1371                          * We don't support restrictions that are not simple
1372                          * types.
1373                          */
1374                         XmlSchemaType restrictionBaseType =
1375                                 schema.getTypeByName(typeName);
1376                         if (restrictionBaseType != null
1377                                 && restrictionBaseType
1378                                 instanceof XmlSchemaSimpleType) {
1379                             return getPrimitiveType(schema,
1380                                     (XmlSchemaSimpleType) restrictionBaseType);
1381                         }
1382                     }
1383                 } else {
1384                     return getPrimitiveType(schema, restriction.getBaseType());
1385                 }
1386 
1387             } else if (type.getContent() instanceof XmlSchemaSimpleTypeList) {
1388                 /* If this is a list, look for items type. */
1389                 XmlSchemaSimpleTypeList listType =
1390                         (XmlSchemaSimpleTypeList) type.getContent();
1391                 return getPrimitiveType(schema, listType.getItemType());
1392 
1393             } else if (type.getContent() instanceof XmlSchemaSimpleTypeUnion) {
1394                 _log.warn(type.getName()
1395                         + " is a union. Processing first type in the union.");
1396                 XmlSchemaSimpleTypeUnion simpleUnion = (XmlSchemaSimpleTypeUnion) type
1397                         .getContent();
1398                 return getPrimitiveType(schema,
1399                         (XmlSchemaSimpleType) simpleUnion.getBaseTypes()
1400                                 .getItem(0));
1401             }
1402         }
1403         throw new XsdCobolAnnotatorException(
1404                 "Cannot infer primitive type for " + typeName);
1405     }
1406 
1407     /**
1408      * Search for the all facets found in an XML schema type hierarchy. Since
1409      * we start from the most detailed type, the first facets encountered take
1410      * precedence over the ones we encounter higher in the hierarchy.
1411      * 
1412      * @param schema the XML Schema being annotated
1413      * @param type the type from which facets should be extracted
1414      * @param facets the facets extracted so far
1415      * @throws XsdCobolAnnotatorException if facets cannot be located
1416      */
1417     @SuppressWarnings("unchecked")
1418     private void getFacets(
1419             final XmlSchema schema,
1420             final XmlSchemaSimpleType type,
1421             final XsdFacets facets) throws XsdCobolAnnotatorException {
1422 
1423         /* facets are found in types restrictions */
1424         if (type.getContent() == null) {
1425             return;
1426         }
1427         if (_log.isDebugEnabled()) {
1428             _log.debug("getFacets started for type = " + type.getName());
1429         }
1430 
1431         if (type.getContent() instanceof XmlSchemaSimpleTypeRestriction) {
1432             XmlSchemaSimpleTypeRestriction restriction =
1433                     (XmlSchemaSimpleTypeRestriction) type.getContent();
1434             if (restriction.getFacets() != null) {
1435                 XmlSchemaObjectCollection collection = restriction.getFacets();
1436                 for (Iterator < XmlSchemaObject > i =
1437                         collection.getIterator(); i.hasNext();) {
1438                     XmlSchemaObject facet = i.next();
1439                     /*
1440                      * When a facet value is found, we keep it only if
1441                      * no previous type did set the same facet value
1442                      */
1443                     if (facet instanceof XmlSchemaLengthFacet) {
1444                         XmlSchemaLengthFacet xsef =
1445                                 (XmlSchemaLengthFacet) facet;
1446                         if (facets.getLength() == -1) {
1447                             facets.setLength(
1448                                     new Integer((String) xsef.getValue()));
1449                         }
1450                     }
1451                     if (facet instanceof XmlSchemaPatternFacet) {
1452                         XmlSchemaPatternFacet xsef =
1453                                 (XmlSchemaPatternFacet) facet;
1454                         if (facets.getPattern() == null) {
1455                             facets.setPattern((String) xsef.getValue());
1456                         }
1457                     }
1458                     if (facet instanceof XmlSchemaTotalDigitsFacet) {
1459                         XmlSchemaTotalDigitsFacet xsef =
1460                                 (XmlSchemaTotalDigitsFacet) facet;
1461                         if (facets.getTotalDigits() == -1) {
1462                             facets.setTotalDigits(
1463                                     new Integer((String) xsef.getValue()));
1464                         }
1465                     }
1466                     if (facet instanceof XmlSchemaFractionDigitsFacet) {
1467                         XmlSchemaFractionDigitsFacet xsef =
1468                                 (XmlSchemaFractionDigitsFacet) facet;
1469                         if (facets.getFractionDigits() == -1) {
1470                             facets.setFractionDigits(
1471                                     new Integer((String) xsef.getValue()));
1472                         }
1473                     }
1474                 }
1475             }
1476 
1477             /*
1478              * If this type derives from another non-primitive one, continue the
1479              * search up the hierarchy chain.
1480              */
1481             if (restriction.getBaseType() == null) {
1482                 QName typeName = restriction.getBaseTypeName();
1483                 if (typeName != null) {
1484                     if (XSD_NS.equals(typeName.getNamespaceURI())) {
1485                         return;
1486                     }
1487                     getFacets(schema,
1488                             (XmlSchemaSimpleType) schema.getTypeByName(
1489                                     typeName), facets);
1490                 }
1491             } else {
1492                 getFacets(schema, restriction.getBaseType(), facets);
1493             }
1494         }
1495 
1496         if (_log.isDebugEnabled()) {
1497             _log.debug("getFacets ended for type = " + type.getName());
1498             _log.debug("   Length facet         = " + facets.getLength());
1499             _log.debug("   TotalDigits facet    = " + facets.getTotalDigits());
1500             _log.debug("   FractionDigits facet = "
1501                     + facets.getFractionDigits());
1502             _log.debug("   Pattern facet        = " + facets.getPattern());
1503         }
1504     }
1505 
1506     /**
1507      * Method to infer a Cobol name from an XML schema type name.
1508      * 
1509      * @param xsdName the XSD type name
1510      * @return the proposed cobol name
1511      * @throws XsdCobolAnnotatorException if cobol name cannot be created
1512      * */
1513     public String getCobolName(
1514             final String xsdName) throws XsdCobolAnnotatorException {
1515         try {
1516             return mNameResolver.getName(xsdName);
1517         } catch (CobolNameResolverException e) {
1518             throw new XsdCobolAnnotatorException(e);
1519         }
1520     }
1521 
1522     /**
1523      * @return the input XML schema uri
1524      */
1525     public URI getInputXsdUri() {
1526         return getModel().getInputXsdUri();
1527     }
1528 
1529     /**
1530      * @param xsdUri the input XML schema uri to set
1531      */
1532     public void setInputXsdUri(
1533             final URI xsdUri) {
1534         getModel().setInputXsdUri(xsdUri);
1535     }
1536 
1537     /**
1538      * @return the map of Type names / Element names for elements that need
1539      *         to be added to the annotated schema.
1540      */
1541     public Map < QName, QName > getRootElements() {
1542         return mRootElements;
1543     }
1544 
1545     /**
1546      * @param rootElements map of Type names / Element names for elements that
1547      *            need to be added to the annotated schema to set
1548      */
1549     public void setRootElements(final Map < QName, QName > rootElements) {
1550         mRootElements = rootElements;
1551     }
1552 
1553     /**
1554      * @return the complexType to Java qualified class name map
1555      */
1556     public Map < String, String > getComplexTypeToJavaClassMap() {
1557         return mComplexTypeToJavaClassMap;
1558     }
1559 
1560     /**
1561      * @param complexTypeToJavaClassMap the complexType to Java qualified class
1562      *            name map to set
1563      */
1564     public void setComplexTypeToJavaClassMap(
1565             final Map < String, String > complexTypeToJavaClassMap) {
1566         mComplexTypeToJavaClassMap = complexTypeToJavaClassMap;
1567     }
1568 
1569     /**
1570      * @return this model.
1571      */
1572     public XsdToXsdCobolModel getModel() {
1573         return (XsdToXsdCobolModel) super.getModel();
1574     }
1575 
1576     /**
1577      * Prints the schema content in the log.
1578      * 
1579      * @param schema the XML Schema
1580      */
1581     protected void logSchema(final XmlSchema schema) {
1582         StringWriter sw = new StringWriter();
1583         schema.write(sw);
1584         _log.debug(sw.toString());
1585 
1586     }
1587 
1588 }