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}