001/*
002 * Copyright (c) 2004-2012, Willem Cazander
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
006 * that the following conditions are met:
007 * 
008 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the
009 *   following disclaimer.
010 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
011 *   the following disclaimer in the documentation and/or other materials provided with the distribution.
012 * 
013 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
014 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
015 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
016 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
018 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
019 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
020 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
021 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
022 */
023
024package org.x4o.xml.eld.xsd;
025
026import java.lang.reflect.Method;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036import org.x4o.xml.element.ElementAttributeHandler;
037import org.x4o.xml.element.ElementBindingHandler;
038import org.x4o.xml.element.ElementClass;
039import org.x4o.xml.element.ElementClassAttribute;
040import org.x4o.xml.element.ElementLanguage;
041import org.x4o.xml.element.ElementLanguageModule;
042import org.x4o.xml.element.ElementNamespaceContext;
043import org.xml.sax.SAXException;
044import org.xml.sax.ext.DefaultHandler2;
045import org.xml.sax.helpers.AttributesImpl;
046
047/**
048 * EldXsdXmlWriter Creates the schema from an eld resource.
049 * 
050 * Note: this is still writing a bit quick and hacky.
051 * 
052 * @author Willem Cazander
053 * @version 1.0 Aug 8, 2012
054 */
055public class EldXsdXmlWriter {
056        
057        
058        static public final String SCHEMA_URI = "http://www.w3.org/2001/XMLSchema";
059
060        protected ElementLanguage context = null;
061        protected DefaultHandler2 xmlWriter = null;
062        protected String writeNamespace = null;
063        protected Map<String, String> namespaces = null;
064        
065        public EldXsdXmlWriter(DefaultHandler2 xmlWriter,ElementLanguage context) {
066                this.xmlWriter=xmlWriter;
067                this.context=context;
068                this.namespaces=new HashMap<String,String>(10);
069        }
070        
071        private void startNamespace(String uri,String prefixNamespace) {
072                String prefix = namespaces.get(uri);
073                if (prefix!=null) {
074                        return;
075                }
076                if (uri.equals(writeNamespace)) {
077                        namespaces.put(uri, "this");
078                        return;
079                }
080                if (prefixNamespace!=null) {
081                        namespaces.put(uri, prefixNamespace); // let user define it
082                        return;
083                }
084                StringBuilder buf = new StringBuilder(20);
085                for (char c:uri.toLowerCase().toCharArray()) {
086                        if (Character.isLetter(c)) {
087                                buf.append(c);
088                        }
089                        if (Character.isDigit(c)) {
090                                buf.append(c);
091                        }
092                }
093                prefix = buf.toString();
094                if (prefix.startsWith("http")) {
095                        prefix = prefix.substring(4);
096                }
097                if (prefix.startsWith("uri")) {
098                        prefix = prefix.substring(3);
099                }
100                if (prefix.startsWith("url")) {
101                        prefix = prefix.substring(3);
102                }
103                namespaces.put(uri, prefix);
104        }
105        
106        public void startNamespaces(String namespaceUri) {
107                
108                this.writeNamespace=namespaceUri;
109                this.namespaces.clear();
110                
111                // redo this mess, add nice find for binding handlers
112                for (ElementLanguageModule modContext:context.getElementLanguageModules()) {
113                        for (ElementNamespaceContext nsContext:modContext.getElementNamespaceContexts()) {
114                                for (ElementClass ec:nsContext.getElementClasses()) {
115                                        Class<?> objectClass = null;
116                                        if (ec.getObjectClass()!=null) {
117                                                objectClass = ec.getObjectClass();
118                                                for (ElementLanguageModule mod:context.getElementLanguageModules()) {
119                                                        for (ElementNamespaceContext ns:mod.getElementNamespaceContexts()) {
120                                                                for (ElementClass checkClass:ns.getElementClasses()) {
121                                                                        if (checkClass.getObjectClass()==null) {
122                                                                                continue;
123                                                                        }
124                                                                        Class<?> checkObjectClass = checkClass.getObjectClass();
125                                                                        List<ElementBindingHandler> b = context.findElementBindingHandlers(objectClass,checkObjectClass);
126                                                                        if (b.isEmpty()==false) {
127                                                                                startNamespace(ns.getUri(),ns.getSchemaPrefix());
128                                                                        }
129                                                                }
130                                                        }
131                                                }
132                                        }
133                                }
134                        }
135                }
136        }
137
138        
139        private static final String COMMENT_SEPERATOR = " ==================================================================== ";
140        private static final String COMMENT_TEXT = "=====";
141        
142        public void startSchema(ElementNamespaceContext ns,ElementLanguage elementLanguage) throws SAXException {
143                
144                xmlWriter.startDocument();
145                
146                // this is a mess;
147                char[] msg;
148                msg = "\n".toCharArray();
149                xmlWriter.ignorableWhitespace(msg,0,msg.length);
150                msg = COMMENT_SEPERATOR.toCharArray();
151                xmlWriter.comment(msg,0,msg.length);
152                String desc = "Automatic generated schema for language: "+elementLanguage.getLanguageConfiguration().getLanguage();
153                int space = COMMENT_SEPERATOR.length()-desc.length()-(2*COMMENT_TEXT.length())-4;
154                StringBuffer b = new StringBuffer(COMMENT_SEPERATOR.length());
155                b.append(" ");
156                b.append(COMMENT_TEXT);
157                b.append("  ");
158                b.append(desc);
159                for (int i=0;i<space;i++) {
160                        b.append(' ');
161                }
162                b.append(COMMENT_TEXT);
163                b.append(" ");
164                
165                msg = "\n".toCharArray();
166                xmlWriter.ignorableWhitespace(msg,0,msg.length);
167                msg = b.toString().toCharArray();
168                xmlWriter.comment(msg,0,msg.length);
169                msg = "\n".toCharArray();
170                xmlWriter.ignorableWhitespace(msg,0,msg.length);
171                msg = COMMENT_SEPERATOR.toCharArray();
172                xmlWriter.comment(msg,0,msg.length);
173                msg = "\n".toCharArray();
174                xmlWriter.ignorableWhitespace(msg,0,msg.length);
175                
176                ElementLanguageModule module = null;
177                for (ElementLanguageModule elm:elementLanguage.getElementLanguageModules()) {
178                        ElementNamespaceContext s = elm.getElementNamespaceContext(ns.getUri());
179                        if (s!=null) {
180                                module = elm;
181                                break;
182                        }
183                }
184                
185                b = new StringBuffer(COMMENT_SEPERATOR.length());
186                b.append("\n\tProviderName:\t");
187                b.append(module.getProviderName());
188                b.append("\n\tModuleName:\t\t");
189                b.append(module.getName());
190                b.append("\n\tNamespaces:\t\t");
191                b.append(module.getElementNamespaceContexts().size());
192                b.append("\n\tNamespace:\t\t");
193                b.append(ns.getUri());
194                b.append("\n\tCreated on:\t\t");
195                b.append(new Date());
196                b.append("\n");
197                msg = b.toString().toCharArray();
198                xmlWriter.comment(msg,0,msg.length);
199                
200                
201                xmlWriter.startPrefixMapping("", SCHEMA_URI);
202                
203                for (String uri:namespaces.keySet()) {
204                        String prefix = namespaces.get(uri);
205                        xmlWriter.startPrefixMapping(prefix, uri);
206                }
207                
208                AttributesImpl atts = new AttributesImpl();
209                atts.addAttribute ("", "version", "", "", "1.0");
210                atts.addAttribute ("", "elementFormDefault", "", "", "qualified");
211                atts.addAttribute ("", "attributeFormDefault", "", "", "unqualified");
212                atts.addAttribute ("", "targetNamespace", "", "", ns.getUri());
213                xmlWriter.startElement (SCHEMA_URI, "schema", "", atts);
214                
215                for (String uri:namespaces.keySet()) {
216                        if (ns.getUri().equals(uri)) {
217                                continue;
218                        }
219                        ElementNamespaceContext nsContext = context.findElementNamespaceContext(uri);
220                        atts = new AttributesImpl();
221                        atts.addAttribute ("", "namespace", "", "", nsContext.getUri());
222                        atts.addAttribute ("", "schemaLocation", "", "", nsContext.getSchemaResource());
223                        xmlWriter.startElement (SCHEMA_URI, "import", "", atts);
224                        xmlWriter.endElement (SCHEMA_URI, "import", "");
225                }
226        }
227        
228        public void endSchema() throws SAXException {
229                xmlWriter.endElement (SCHEMA_URI, "schema" , "");
230                xmlWriter.endDocument();
231        }
232        
233        public void writeElementClass(ElementClass ec,ElementNamespaceContext nsWrite) throws SAXException {
234                
235                AttributesImpl atts = new AttributesImpl();
236                if (nsWrite.getLanguageRoot()!=null && nsWrite.getLanguageRoot()) {
237                        atts.addAttribute ("", "name", "", "", ec.getTag());
238                        xmlWriter.startElement (SCHEMA_URI, "element", "", atts);       // Only in the language root xsd there is an element.
239                        
240                        atts = new AttributesImpl();
241                        xmlWriter.startElement (SCHEMA_URI, "complexType", "", atts);
242                } else {
243                        atts.addAttribute ("", "name", "", "", ec.getTag()+"Type");
244                        xmlWriter.startElement (SCHEMA_URI, "complexType", "", atts);
245                }
246                
247                if (ec.getSchemaContentBase()!=null) {
248                        atts = new AttributesImpl();
249                        if (ec.getSchemaContentComplex()!=null && ec.getSchemaContentComplex()) {
250                                if (ec.getSchemaContentMixed()!=null && ec.getSchemaContentMixed()) {
251                                        atts.addAttribute ("", "mixed", "", "", "true");
252                                }
253                                xmlWriter.startElement (SCHEMA_URI, "complexContent", "", atts);
254                        } else {
255                                xmlWriter.startElement (SCHEMA_URI, "simpleContent", "", atts);
256                        }
257                        atts = new AttributesImpl();
258                        atts.addAttribute ("", "base", "", "", ec.getSchemaContentBase());
259                        xmlWriter.startElement (SCHEMA_URI, "extension", "", atts);
260                }
261                
262                if (ec.getSchemaContentBase()==null) {
263                        atts = new AttributesImpl();
264                        atts.addAttribute ("", "minOccurs", "", "", "0"); // make unordered elements
265                        atts.addAttribute ("", "maxOccurs", "", "", "unbounded");
266                        xmlWriter.startElement (SCHEMA_URI, "choice", "", atts);
267                        for (ElementLanguageModule mod:context.getElementLanguageModules()) {
268                                for (ElementNamespaceContext ns:mod.getElementNamespaceContexts()) {
269                                        writeElementClassNamespaces(ec,nsWrite,ns);
270                                }
271                        }
272                        xmlWriter.endElement(SCHEMA_URI, "choice", "");
273                }
274                
275                List<String> attrNames = new ArrayList<String>(30);
276                for (ElementClassAttribute eca:ec.getElementClassAttributes()) {
277                        attrNames.add(eca.getName());
278                        atts = new AttributesImpl();
279                        atts.addAttribute ("", "name", "", "", eca.getName());
280                        atts.addAttribute ("", "type", "", "", "string");
281                        if (eca.getRequired()!=null && eca.getRequired()) {
282                                atts.addAttribute ("", "use", "", "", "required");      
283                        }
284                        xmlWriter.startElement (SCHEMA_URI, "attribute", "", atts);
285                        xmlWriter.endElement(SCHEMA_URI, "attribute", "");      
286                }
287                
288                for (ElementLanguageModule mod:context.getElementLanguageModules()) {
289                        for (ElementAttributeHandler eah:mod.getElementAttributeHandlers()) {
290                                attrNames.add(eah.getAttributeName());
291                                atts = new AttributesImpl();
292                                atts.addAttribute ("", "name", "", "", eah.getAttributeName());
293                                atts.addAttribute ("", "type", "", "", "string");
294                                xmlWriter.startElement (SCHEMA_URI, "attribute", "", atts);
295                                xmlWriter.endElement(SCHEMA_URI, "attribute", "");      
296                        }
297                }
298                
299                if (ec.getAutoAttributes()!=null && ec.getAutoAttributes()==false) {
300                        // oke, reverse this if and rm whitespace.
301                        char[] msg;
302                        msg = " ".toCharArray();
303                        xmlWriter.ignorableWhitespace(msg,0,msg.length);
304                        
305                } else {
306                        
307                        if (ec.getObjectClass()!=null) {
308                                for (Method m:ec.getObjectClass().getMethods()) {
309                                        if (m.getName().startsWith("set")) {
310                                                String n = m.getName().substring(3);
311                                                if (m.getParameterTypes().length==0) {
312                                                        continue; // set without parameters
313                                                }
314                                                if (n.length()<2) {
315                                                        continue;
316                                                }
317                                                n = n.substring(0,1).toLowerCase()+n.substring(1,n.length());
318                                                if (attrNames.contains(n)) {
319                                                        continue;
320                                                }
321                                                atts = new AttributesImpl();
322                                                atts.addAttribute ("", "name", "", "", n);
323                                                
324                                                Class<?> type = m.getParameterTypes()[0]; // TODO make full list for auto attribute type resolving.
325                                                if (type.equals(Object.class)) {
326                                                        atts.addAttribute ("", "type", "", "", "string");// object is always string because is always assignable
327                                                } else if (type.isAssignableFrom(Boolean.class) | type.isAssignableFrom(Boolean.TYPE)) {
328                                                        atts.addAttribute ("", "type", "", "", "boolean");
329                                                } else if (type.isAssignableFrom(Integer.class) | type.isAssignableFrom(Integer.TYPE)) {
330                                                        atts.addAttribute ("", "type", "", "", "integer");
331                                                } else if (type.isAssignableFrom(Long.class) | type.isAssignableFrom(Long.TYPE)) {
332                                                        atts.addAttribute ("", "type", "", "", "long");
333                                                } else if (type.isAssignableFrom(Float.class) | type.isAssignableFrom(Float.TYPE)) {
334                                                        atts.addAttribute ("", "type", "", "", "float");
335                                                } else if (type.isAssignableFrom(Double.class) | type.isAssignableFrom(Double.TYPE)) {
336                                                        atts.addAttribute ("", "type", "", "", "double");
337                                                } else {
338                                                        atts.addAttribute ("", "type", "", "", "string");
339                                                }
340                                                xmlWriter.startElement (SCHEMA_URI, "attribute", "", atts);
341                                                xmlWriter.endElement(SCHEMA_URI, "attribute", "");      
342                                        }
343                                }
344                        } else {
345                                atts = new AttributesImpl();
346                                xmlWriter.startElement (SCHEMA_URI, "anyAttribute", "", atts);
347                                xmlWriter.endElement(SCHEMA_URI, "anyAttribute", "");
348                        }
349                }
350                if (ec.getSchemaContentBase()!=null) {
351                        xmlWriter.endElement(SCHEMA_URI, "extension", "");
352                        if (ec.getSchemaContentComplex()!=null && ec.getSchemaContentComplex()) {
353                                xmlWriter.endElement(SCHEMA_URI, "complexContent", "");
354                        } else {
355                                xmlWriter.endElement(SCHEMA_URI, "simpleContent", "");
356                        }
357                }
358                xmlWriter.endElement(SCHEMA_URI, "complexType", "");
359                if (nsWrite.getLanguageRoot()!=null && nsWrite.getLanguageRoot()) {
360                        xmlWriter.endElement(SCHEMA_URI, "element", "");
361                }
362        }
363        
364        private void writeElementClassNamespaces(ElementClass ec,ElementNamespaceContext nsWrite,ElementNamespaceContext ns) throws SAXException {
365                AttributesImpl atts = new AttributesImpl();
366                List<String> refElements = new ArrayList<String>(20);
367                for (ElementClass checkClass:ns.getElementClasses()) {
368                        List<String> parents = checkClass.getElementParents(nsWrite.getUri());
369                        if (parents!=null && parents.contains(ec.getTag())) {
370                                refElements.add(checkClass.getTag());
371                        }
372                        if (ec.getObjectClass()==null) {
373                                continue;
374                        }
375                        if (checkClass.getObjectClass()==null) {
376                                continue;
377                        }
378                        Class<?> objectClass = ec.getObjectClass();
379                        Class<?> checkObjectClass = checkClass.getObjectClass();
380                        List<ElementBindingHandler> b = context.findElementBindingHandlers(objectClass,checkObjectClass);
381                        if (b.isEmpty()==false) {
382                                refElements.add(checkClass.getTag());
383                        }
384                }
385                if (refElements.isEmpty()==false) {
386                        Set<String> s = new HashSet<String>(refElements.size());
387                        s.addAll(refElements);
388                        List<String> r = new ArrayList<String>(s.size());
389                        r.addAll(s);
390                        Collections.sort(r);
391                        
392                        String prefix = namespaces.get(ns.getUri());
393                        for (String refElement:r) {
394                                atts = new AttributesImpl();
395                                if (nsWrite.getLanguageRoot()!=null && nsWrite.getLanguageRoot()) {
396                                        atts.addAttribute ("", "ref", "", "", prefix+":"+refElement);
397                                } else if (nsWrite.getUri().equals(ns.getUri())==false) {
398                                        atts.addAttribute ("", "ref", "", "", prefix+":"+refElement);
399                                } else {
400                                        atts.addAttribute ("", "name", "", "", refElement);
401                                        atts.addAttribute ("", "type", "", "", prefix+":"+refElement+"Type");
402                                }
403                                xmlWriter.startElement (SCHEMA_URI, "element", "", atts);
404                                xmlWriter.endElement (SCHEMA_URI, "element", "");
405                        }
406                }
407        }
408        
409        
410        public void writeElement(ElementClass ec,ElementNamespaceContext nsWrite) throws SAXException {
411                if (nsWrite.getLanguageRoot()!=null && nsWrite.getLanguageRoot()) {
412                        return; // is done in writeElementClass
413                }
414                AttributesImpl atts = new AttributesImpl();
415                atts.addAttribute ("", "name", "", "", ec.getTag());
416                atts.addAttribute ("", "type", "", "", "this:"+ec.getTag()+"Type");
417                xmlWriter.startElement(SCHEMA_URI, "element", "", atts);        // Only in the language root xsd there is an element.
418                
419                if (ec.getDescription()!=null) {
420                        atts = new AttributesImpl();
421                        xmlWriter.startElement(SCHEMA_URI, "annotation", "", atts);
422                        atts = new AttributesImpl();
423                        atts.addAttribute ("", "xml:lang", "", "", "en");
424                        xmlWriter.startElement(SCHEMA_URI, "documentation", "", atts);
425                        char[] msg = ec.getDescription().toCharArray();
426                        xmlWriter.characters(msg,0,msg.length);
427                        xmlWriter.endElement(SCHEMA_URI, "documentation", "");
428                        xmlWriter.endElement(SCHEMA_URI, "annotation", "");
429                }
430                
431                
432                xmlWriter.endElement(SCHEMA_URI, "element", "");
433        }
434}