| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 2003 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Apache" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache |
| * XMLBeans", nor may "Apache" appear in their name, without prior |
| * written permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation and was |
| * originally based on software copyright (c) 2003 BEA Systems |
| * Inc., <http://www.bea.com/>. For more information on the Apache Software |
| * Foundation, please see <http://www.apache.org/>. |
| */ |
| |
| package org.apache.xmlbeans.impl.binding.compile; |
| |
| import org.apache.xmlbeans.impl.binding.bts.BindingFile; |
| import org.apache.xmlbeans.impl.binding.bts.BindingType; |
| import org.apache.xmlbeans.impl.binding.bts.BindingLoader; |
| import org.apache.xmlbeans.impl.binding.bts.XmlName; |
| import org.apache.xmlbeans.impl.binding.bts.JavaName; |
| import org.apache.xmlbeans.impl.binding.bts.ByNameBean; |
| import org.apache.xmlbeans.impl.binding.bts.QNameProperty; |
| import org.apache.xmlbeans.impl.binding.bts.SimpleBindingType; |
| import org.apache.xmlbeans.impl.binding.bts.BindingTypeName; |
| import org.apache.xmlbeans.impl.binding.compile.BindingFileGenerator; |
| import org.apache.xmlbeans.impl.binding.compile.JavaCodeGenerator; |
| import org.apache.xmlbeans.impl.binding.compile.JavaCodePrinter; |
| import org.apache.xmlbeans.impl.common.NameUtil; |
| import org.apache.xmlbeans.SchemaType; |
| import org.apache.xmlbeans.SchemaTypeSystem; |
| import org.apache.xmlbeans.SchemaProperty; |
| import org.apache.xmlbeans.SchemaLocalAttribute; |
| import org.apache.xmlbeans.XmlObject; |
| import org.apache.xmlbeans.XmlOptions; |
| import org.apache.xmlbeans.soap.SOAPArrayType; |
| import org.apache.xmlbeans.soap.SchemaWSDLArrayType; |
| |
| import javax.xml.namespace.QName; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.LinkedHashMap; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.HashMap; |
| import java.util.Collections; |
| import java.util.AbstractCollection; |
| import java.util.List; |
| import java.math.BigInteger; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Writer; |
| import java.io.IOException; |
| |
| /** |
| * This is the "JAXRPC-style" Schema-to-bts compiler. |
| */ |
| public class JAXRPCSchemaBinder implements JavaCodeGenerator, BindingFileGenerator, SchemaToJavaResult |
| { |
| private Set usedNames = new HashSet(); |
| private SchemaTypeSystem sts; |
| private Map scratchFromXmlName = new LinkedHashMap(); |
| private Map scratchFromSchemaType = new HashMap(); // for convenience |
| private Map scratchFromJavaNameString = new HashMap(); // for printing |
| private BindingLoader path; |
| private int structureCount; |
| private BindingFile bindingFile = new BindingFile(); |
| |
| private JAXRPCSchemaBinder(SchemaTypeSystem sts, BindingLoader path) |
| { |
| this.sts = sts; |
| this.path = path; |
| } |
| |
| /** |
| * Generates a binding for the given set of schema types. |
| * |
| * Defer to any previously defined bindings on the BindingLoader path |
| * supplied. |
| */ |
| public static SchemaToJavaResult bind(SchemaTypeSystem sts, BindingLoader path) |
| { |
| JAXRPCSchemaBinder binder = new JAXRPCSchemaBinder(sts, path); |
| binder.bind(); |
| return binder; |
| } |
| |
| /** |
| * Computes the binding. |
| */ |
| private void bind() |
| { |
| // Every type or global element or global attribute is dropped into |
| // one of a number of categories. (See Scratch constants.) |
| // |
| // Based on the category, a java class is either defined or found |
| // that maches with the schema type. |
| // |
| // The phases work as follows: |
| // |
| // 1. categorize and allocate Scratches |
| // 2. write or find java names for each xml type |
| // 3. allocate binding types for each scratch (done in the same pass as 2) |
| // 4. fill in the java getter/setter structure of any complex types |
| |
| |
| // 1. categorize and allocate Scratches |
| createScratchArea(); |
| |
| // 2. write or find java names for each xml type |
| // 3. allocate binding types for each scratch (done in the same pass as 2) |
| for (Iterator i = scratchIterator(); i.hasNext(); ) |
| { |
| Scratch scratch = (Scratch)i.next(); |
| resolveJavaName(scratch); |
| createBindingType(scratch); |
| } |
| |
| // 4. fill in the java getter/setter structure of any complex types |
| for (Iterator i = scratchIterator(); i.hasNext(); ) |
| { |
| resolveJavaStructure((Scratch)i.next()); |
| } |
| } |
| |
| /** |
| * This function goes through all relevant schema types, plus soap |
| * array types, and creates a scratch area for each. Each |
| * scratch area is also marked at this time with an XmlName, |
| * a schema type, and a category. |
| */ |
| private void createScratchArea() |
| { |
| for (Iterator i = allTypeIterator(); i.hasNext(); ) |
| { |
| SchemaType sType = (SchemaType)i.next(); |
| XmlName xmlName = XmlName.forSchemaType(sType); |
| Scratch scratch; |
| |
| if (sType.isSimpleType()) |
| { |
| // simple types are atomic |
| // todo: what about simple content, custom codecs, etc? |
| scratch = new Scratch(sType, xmlName, Scratch.ATOMIC_TYPE); |
| } |
| else if (sType.isDocumentType()) |
| { |
| scratch = new Scratch(sType, XmlName.forGlobalName(XmlName.ELEMENT, sType.getDocumentElementName()), Scratch.ELEMENT); |
| } |
| else if (sType.isAttributeType()) |
| { |
| scratch = new Scratch(sType, XmlName.forGlobalName(XmlName.ATTRIBUTE, sType.getDocumentElementName()), Scratch.ATTRIBUTE); |
| } |
| else if (isSoapArray(sType)) |
| { |
| scratch = new Scratch(sType, xmlName, Scratch.SOAPARRAY_REF); |
| xmlName = soapArrayTypeName(sType); |
| scratch.setAsIf(xmlName); |
| |
| // soap arrays unroll like this |
| while (xmlName.getComponentType() == XmlName.SOAP_ARRAY) |
| { |
| scratch = new Scratch(null, xmlName, Scratch.SOAPARRAY); |
| scratchFromXmlName.put(xmlName, scratch); |
| xmlName = xmlName.getOuterComponent(); |
| } |
| } |
| else if (isLiteralArray(sType)) |
| { |
| scratch = new Scratch(sType, xmlName, Scratch.LITERALARRAY_TYPE); |
| } |
| else |
| { |
| scratch = new Scratch(sType, xmlName, Scratch.STRUCT_TYPE); |
| } |
| |
| scratchFromXmlName.put(xmlName, scratch); |
| scratchFromSchemaType.put(sType, scratch); |
| |
| } |
| } |
| |
| /** |
| * Computes a JavaName for each scratch. Notice that structures and |
| * atoms can be computed directly, but arrays, elements, etc, need |
| * to defer to other scratch areas, so this is a resolution |
| * process that occurs in dependency order. |
| */ |
| private void resolveJavaName(Scratch scratch) |
| { |
| // already resolved (we recurse to do in dependency order) |
| if (scratch.getJavaName() != null) |
| return; |
| |
| switch (scratch.getCategory()) |
| { |
| case Scratch.ATOMIC_TYPE: |
| { |
| resolveSimpleScratch(scratch); |
| return; |
| } |
| |
| case Scratch.STRUCT_TYPE: |
| { |
| structureCount += 1; |
| JavaName javaName = pickUniqueJavaName(scratch.getSchemaType()); |
| scratch.setJavaName(javaName); |
| scratchFromJavaNameString.put(javaName.toString(), scratch); |
| return; |
| } |
| |
| case Scratch.LITERALARRAY_TYPE: |
| { |
| SchemaType itemType = getLiteralArrayItemType(scratch.getSchemaType()); |
| Scratch itemScratch = scratchForSchemaType(itemType); |
| resolveJavaName(itemScratch); |
| scratch.setJavaName(JavaName.forArray(itemScratch.getJavaName(), 1)); |
| return; |
| } |
| |
| case Scratch.SOAPARRAY_REF: |
| { |
| XmlName soapArrayName = scratch.getAsIf(); |
| Scratch arrayScratch = scratchForXmlName(soapArrayName); |
| resolveJavaName(arrayScratch); |
| scratch.setJavaName(arrayScratch.getJavaName()); |
| scratch.setAsIf(arrayScratch.getXmlName()); |
| return; |
| } |
| |
| case Scratch.SOAPARRAY: |
| { |
| XmlName arrayName = scratch.getXmlName(); |
| XmlName itemName = arrayName.getOuterComponent(); |
| Scratch itemScratch = scratchForXmlName(itemName); |
| resolveJavaName(itemScratch); |
| scratch.setJavaName(JavaName.forArray(itemScratch.getJavaName(), arrayName.getNumber())); |
| return; |
| } |
| |
| case Scratch.ELEMENT: |
| case Scratch.ATTRIBUTE: |
| { |
| SchemaType contentType = scratch.getSchemaType().getProperties()[0].getType(); |
| Scratch contentScratch = scratchForSchemaType(contentType); |
| resolveJavaName(contentScratch); |
| scratch.setJavaName(contentScratch.getJavaName()); |
| scratch.setAsIf(contentScratch.getXmlName()); |
| return; |
| } |
| |
| default: |
| throw new IllegalStateException("Unrecognized category"); |
| } |
| } |
| |
| /** |
| * Computes a BindingType for a scratch. |
| */ |
| private void createBindingType(Scratch scratch) |
| { |
| assert(scratch.getBindingType() == null); |
| |
| BindingTypeName btName = BindingTypeName.forPair(scratch.getJavaName(), scratch.getXmlName()); |
| |
| switch (scratch.getCategory()) |
| { |
| case Scratch.ATOMIC_TYPE: |
| case Scratch.ELEMENT: |
| case Scratch.SOAPARRAY_REF: |
| case Scratch.ATTRIBUTE: |
| SimpleBindingType simpleResult = new SimpleBindingType(btName); |
| simpleResult.setAsIfXmlType(scratch.getAsIf()); |
| scratch.setBindingType(simpleResult); |
| bindingFile.addBindingType(simpleResult, shouldBeFromJavaDefault(btName), true); |
| break; |
| |
| case Scratch.STRUCT_TYPE: |
| ByNameBean byNameResult = new ByNameBean(btName); |
| scratch.setBindingType(byNameResult); |
| bindingFile.addBindingType(byNameResult, true, true); |
| break; |
| |
| case Scratch.LITERALARRAY_TYPE: |
| throw new UnsupportedOperationException(); |
| |
| case Scratch.SOAPARRAY: |
| throw new UnsupportedOperationException(); |
| |
| default: |
| throw new IllegalStateException("Unrecognized category"); |
| } |
| } |
| |
| /** |
| * Returns true if the given btName would be the first (unique) |
| * default java->xml binding to be entered in the java-to-type or |
| * java-to-element tables. |
| */ |
| private boolean shouldBeFromJavaDefault(BindingTypeName btName) |
| { |
| JavaName jName = btName.getJavaName(); |
| XmlName xName = btName.getXmlName(); |
| if (xName.isSchemaType()) |
| { |
| return (bindingFile.lookupTypeFor(jName) == null && |
| path.lookupTypeFor(jName) == null); |
| } |
| if (xName.getComponentType() == XmlName.ELEMENT) |
| { |
| return (bindingFile.lookupElementFor(jName) == null && |
| path.lookupElementFor(jName) == null); |
| } |
| return false; |
| } |
| |
| /** |
| * Now we resolve the structural aspects (property names) for each |
| * scratch. |
| * |
| * todo: understand how we want inheritance to work |
| */ |
| private void resolveJavaStructure(Scratch scratch) |
| { |
| if (scratch.getCategory() != Scratch.STRUCT_TYPE) |
| return; |
| |
| if (scratch.isStructureResolved()) |
| return; |
| |
| scratch.setStructureResolved(true); |
| |
| SchemaType baseType = scratch.getSchemaType().getBaseType(); |
| Collection baseProperties = null; |
| if (baseType != null) |
| baseProperties = extractProperties(baseType); |
| if (baseProperties == null) |
| baseProperties = Collections.EMPTY_LIST; |
| |
| // sort properties based on QName attr/elt |
| Map seenAttrProps = new HashMap(); |
| Map seenEltProps = new HashMap(); |
| Set seenMethodNames = new HashSet(); |
| seenMethodNames.add("getClass"); |
| |
| for (Iterator i = baseProperties.iterator(); i.hasNext(); ) |
| { |
| QNameProperty prop = (QNameProperty)i.next(); |
| if (prop.isAttribute()) |
| seenAttrProps.put(prop.getQName(), prop); |
| else |
| seenEltProps.put(prop.getQName(), prop); |
| |
| // todo: probably this collision avoidance should be using Java introspection instead |
| if (prop.getGetterName() != null) |
| seenMethodNames.add(prop.getGetterName()); |
| if (prop.getSetterName() != null) |
| seenMethodNames.add(prop.getSetterName()); |
| } |
| |
| // now deal with remaining props |
| SchemaProperty[] props = scratch.getSchemaType().getProperties(); |
| for (int i = 0; i < props.length; i++) |
| { |
| QNameProperty prop = (QNameProperty)(props[i].isAttribute() ? seenAttrProps : seenEltProps).get(props[i].getName()); |
| if (prop != null) |
| { |
| // already seen property: verify multiplicity looks cool |
| if (prop.isMultiple() != isMultiple(props[i])) |
| { |
| // todo: signal nicer error |
| throw new IllegalStateException("Can't change multiplicity"); |
| } |
| |
| // todo: think about optionality and nillability too |
| } |
| else |
| { |
| SchemaType sType = props[i].getType(); |
| BindingType bType = bindingTypeForSchemaType(sType); |
| |
| String propName = pickUniquePropertyName(props[i].getName(), seenMethodNames); |
| String getter = "get" + propName; |
| String setter = "set" + propName; |
| boolean isMultiple = isMultiple(props[i]); |
| JavaName collection = null; |
| if (isMultiple) |
| collection = JavaName.forArray(bType.getName().getJavaName(), 1); |
| |
| prop = new QNameProperty(); |
| prop.setQName(props[i].getName()); |
| prop.setAttribute(props[i].isAttribute()); |
| prop.setSetterName(setter); |
| prop.setGetterName(getter); |
| prop.setCollectionClass(collection); |
| prop.setBindingType(bType); |
| prop.setNillable(props[i].hasNillable() != SchemaProperty.NEVER); |
| prop.setOptional(isOptional(props[i])); |
| prop.setMultiple(isMultiple); |
| } |
| scratch.addQNameProperty(prop); |
| } |
| } |
| |
| /** |
| * Picks a property name without colliding with names of |
| * previously picked getters and setters. |
| */ |
| private String pickUniquePropertyName(QName name, Set seenMethodNames) |
| { |
| String baseName = NameUtil.upperCamelCase(name.getLocalPart()); |
| String propName = baseName; |
| for (int i = 1; ; i += 1) |
| { |
| String getter = "get" + propName; |
| String setter = "set" + propName; |
| |
| if (!seenMethodNames.contains(getter) && |
| !seenMethodNames.contains(setter)) |
| { |
| seenMethodNames.add(getter); |
| seenMethodNames.add(setter); |
| return propName; |
| } |
| propName = baseName + i; |
| } |
| } |
| |
| /** |
| * True if the given SchemaProperty has maxOccurs > 1 |
| */ |
| private static boolean isMultiple(SchemaProperty prop) |
| { |
| return (prop.getMaxOccurs() == null || prop.getMaxOccurs().compareTo(BigInteger.ONE) > 0); |
| } |
| |
| /** |
| * True if the given SchemaProperty has minOccurs < 1 |
| */ |
| private static boolean isOptional(SchemaProperty prop) |
| { |
| return (prop.getMinOccurs().signum() == 0); |
| } |
| |
| /** |
| * Returns a collection of QNameProperties for a given schema type that |
| * may either be on the path or the current scratch area. |
| */ |
| private Collection extractProperties(SchemaType sType) |
| { |
| // case 1: it's in the current area |
| Scratch scratch = scratchForSchemaType(sType); |
| if (scratch != null) |
| { |
| resolveJavaStructure(scratch); |
| return scratch.getQNameProperties(); |
| } |
| |
| // case 2: it's in the path |
| BindingType bType = path.getBindingType(path.lookupPojoFor(XmlName.forSchemaType(sType))); |
| if (!(bType instanceof ByNameBean)) |
| { |
| return null; |
| } |
| Collection result = new ArrayList(); |
| ByNameBean bnb = (ByNameBean)bType; |
| for (Iterator i = bnb.getProperties().iterator(); i.hasNext(); ) |
| { |
| result.add(i.next()); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * True for a schema type that is a SOAP array. |
| */ |
| private static boolean isSoapArray(SchemaType sType) |
| { |
| // SOAP Array definition must be put on the compiletime classpath |
| while (sType != null) |
| { |
| String signature = XmlName.forSchemaType(sType).toString(); |
| |
| // captures both SOAP 1.1 and SOAP 1.2+ |
| if (signature.equals("t=Array@http://schemas.xmlsoap.org/soap/encoding/") || |
| signature.startsWith("t=Array@http://www.w3.org/") |
| && signature.endsWith("/soap-encoding")) |
| return true; |
| sType = sType.getBaseType(); |
| } |
| return false; |
| } |
| |
| private static final QName arrayType = new QName("http://schemas.xmlsoap.org/soap/encoding/", "arrayType"); |
| |
| /** |
| * Returns an XmlName describing a SOAP array. |
| */ |
| private static XmlName soapArrayTypeName(SchemaType sType) |
| { |
| // first, look for wsdl:arrayType default - this will help us with multidimensional arrays |
| SOAPArrayType defaultArrayType = null; |
| SchemaLocalAttribute attr = sType.getAttributeModel().getAttribute(arrayType); |
| if (attr != null) |
| defaultArrayType = ((SchemaWSDLArrayType)attr).getWSDLArrayType(); |
| |
| // method 1: trust wsdl:arrayType |
| if (defaultArrayType != null) |
| return XmlName.forSoapArrayType(defaultArrayType); |
| |
| // method 2: SOAP 1.2 equivalent? |
| // todo: track what do WSDLs do in the world of SOAP 1.2. |
| |
| // method 3: look at the type of a unique element. |
| SchemaType itemType = XmlObject.type; |
| SchemaProperty[] props = sType.getElementProperties(); |
| if (props.length == 1) |
| itemType = props[0].getType(); |
| |
| return XmlName.forNestedNumber(XmlName.SOAP_ARRAY, 1, XmlName.forSchemaType(itemType)); |
| } |
| |
| /** |
| * Climbs the structure of a schema type to find the namespace within |
| * which it was defined. |
| */ |
| private String findContainingNamespace(SchemaType sType) |
| { |
| for (;;) |
| { |
| if (sType.isDocumentType()) |
| return sType.getDocumentElementName().getNamespaceURI(); |
| else if (sType.isAttributeType()) |
| return sType.getAttributeTypeAttributeName().getNamespaceURI(); |
| else if (sType.getName() != null) |
| return sType.getName().getNamespaceURI(); |
| sType = sType.getOuterType(); |
| } |
| } |
| |
| /** |
| * Picks a unique fully-qualified Java class name for the given schema |
| * type. Uses and updates the "usedNames" set. |
| */ |
| private JavaName pickUniqueJavaName(SchemaType sType) |
| { |
| QName qname = null; |
| while (qname == null) |
| { |
| if (sType.isDocumentType()) |
| qname = sType.getDocumentElementName(); |
| else if (sType.isAttributeType()) |
| qname = sType.getAttributeTypeAttributeName(); |
| else if (sType.getName() != null) |
| qname = sType.getName(); |
| else if (sType.getContainerField() != null) |
| { |
| qname = sType.getContainerField().getName(); |
| if (qname.getNamespaceURI().length() == 0) |
| qname = new QName(findContainingNamespace(sType), qname.getLocalPart()); |
| } |
| sType = sType.getOuterType(); |
| } |
| |
| String baseName = NameUtil.getClassNameFromQName(qname); |
| String pickedName = baseName; |
| |
| for (int i = 1; usedNames.contains(pickedName); i += 1) |
| pickedName = baseName + i; |
| |
| usedNames.add(pickedName); |
| |
| return JavaName.forString(pickedName); |
| } |
| |
| /** |
| * Resolves an atomic scratch all at once, including its |
| * JavaName and basedOn fields. |
| * |
| * This resolution method sets up a scratch so that is |
| * is "based on" another binding type. It finds the |
| * underlying binding type by climing the base type |
| * chain, and grabbing the first hit. |
| */ |
| private void resolveSimpleScratch(Scratch scratch) |
| { |
| assert(scratch.getCategory() == Scratch.ATOMIC_TYPE); |
| |
| if (scratch.getJavaName() != null) |
| return; |
| |
| SchemaType baseType = scratch.getSchemaType().getBaseType(); |
| while (baseType != null) |
| { |
| // find a base type within this type system |
| Scratch basedOnScratch = scratchForSchemaType(baseType); |
| if (basedOnScratch != null) |
| { |
| if (basedOnScratch.getCategory() != Scratch.ATOMIC_TYPE) |
| throw new IllegalStateException("Atomic types should only inherit from atomic types"); |
| resolveSimpleScratch(basedOnScratch); |
| scratch.setJavaName(basedOnScratch.getJavaName()); |
| scratch.setAsIf(basedOnScratch.getXmlName()); |
| return; |
| } |
| |
| // or if not within this type system, find the base type on the path |
| XmlName treatAs = XmlName.forSchemaType(baseType); |
| BindingType basedOnBinding = path.getBindingType(path.lookupPojoFor(treatAs)); |
| if (basedOnBinding != null) |
| { |
| scratch.setJavaName(basedOnBinding.getName().getJavaName()); |
| scratch.setAsIf(treatAs); |
| return; |
| } |
| |
| // or go to the next base type up |
| baseType = baseType.getBaseType(); |
| } |
| |
| // builtin at least should give us xs:anyType |
| throw new IllegalStateException("Builtin binding type loader is not on path."); |
| } |
| |
| /** |
| * Looks on both the path and in the current scratch area for |
| * the binding type corresponding to the given schema type. Must |
| * be called after all the binding types have been created. |
| */ |
| private BindingType bindingTypeForSchemaType(SchemaType sType) |
| { |
| Scratch scratch = scratchForSchemaType(sType); |
| if (scratch != null) |
| return scratch.getBindingType(); |
| return path.getBindingType(path.lookupPojoFor(XmlName.forSchemaType(sType))); |
| } |
| |
| /** |
| * Returns the scratch area for a given schema type. Notice that |
| * SOAP arrays have an XmlName but not a schema type. |
| */ |
| private Scratch scratchForSchemaType(SchemaType sType) |
| { |
| return (Scratch)scratchFromSchemaType.get(sType); |
| } |
| |
| /** |
| * Returns the scratch area for a given XmlName. |
| */ |
| private Scratch scratchForXmlName(XmlName xmlName) |
| { |
| return (Scratch)scratchFromXmlName.get(xmlName); |
| } |
| |
| /** |
| * Returns the scratch area for a given JavaName. Notice that only |
| * structures generate a java class, so not non-strucuture scratch areas |
| * cannot be referenced this way. |
| */ |
| private Scratch scratchForJavaNameString(String javaName) |
| { |
| return (Scratch)scratchFromJavaNameString.get(javaName); |
| } |
| |
| /** |
| * Extracts the schema type for the array items for a literal array. |
| */ |
| private static SchemaType getLiteralArrayItemType(SchemaType sType) |
| { |
| // consider: must the type be named "ArrayOf..."? |
| |
| if (sType.isSimpleType() || sType.getContentType() == SchemaType.SIMPLE_CONTENT) |
| return null; |
| SchemaProperty[] prop = sType.getProperties(); |
| if (prop.length != 1 || prop[0].isAttribute()) |
| return null; |
| BigInteger max = prop[0].getMaxOccurs(); |
| if (max != null && max.compareTo(BigInteger.ONE) <= 0) |
| return null; |
| return prop[0].getType(); |
| } |
| |
| /** |
| * True if the given schema type is interpreted as a .NET-style |
| * array. |
| */ |
| private static boolean isLiteralArray(SchemaType sType) |
| { |
| return getLiteralArrayItemType(sType) != null; |
| } |
| |
| /** |
| * Scratch area corresponding to a schema type, used for the binding |
| * computation. |
| */ |
| private static class Scratch |
| { |
| Scratch(SchemaType schemaType, XmlName xmlName, int category) |
| { |
| this.schemaType = schemaType; |
| this.xmlName = xmlName; |
| this.category = category; |
| } |
| |
| private BindingType bindingType; |
| private SchemaType schemaType; // may be null |
| private JavaName javaName; |
| private XmlName xmlName; |
| |
| private int category; |
| |
| // atomic types get a treatAs |
| private XmlName asIf; |
| private boolean isStructureResolved; |
| |
| // categories of Scratch, established at ctor time |
| public static final int ATOMIC_TYPE = 1; |
| public static final int STRUCT_TYPE = 2; |
| public static final int LITERALARRAY_TYPE = 3; |
| public static final int SOAPARRAY_REF = 4; |
| public static final int SOAPARRAY = 5; |
| public static final int ELEMENT = 6; |
| public static final int ATTRIBUTE = 7; |
| |
| public int getCategory() |
| { |
| return category; |
| } |
| |
| public JavaName getJavaName() |
| { |
| return javaName; |
| } |
| |
| public void setJavaName(JavaName javaName) |
| { |
| this.javaName = javaName; |
| } |
| |
| public BindingType getBindingType() |
| { |
| return bindingType; |
| } |
| |
| public void setBindingType(BindingType bindingType) |
| { |
| this.bindingType = bindingType; |
| } |
| |
| public SchemaType getSchemaType() |
| { |
| return schemaType; |
| } |
| |
| public XmlName getXmlName() |
| { |
| return xmlName; |
| } |
| |
| public void setXmlName(XmlName xmlName) |
| { |
| this.xmlName = xmlName; |
| } |
| |
| public XmlName getAsIf() |
| { |
| return asIf; |
| } |
| |
| public void setAsIf(XmlName xmlName) |
| { |
| this.asIf = xmlName; |
| } |
| |
| public void addQNameProperty(QNameProperty prop) |
| { |
| if (!(bindingType instanceof ByNameBean)) |
| throw new IllegalStateException(); |
| ((ByNameBean)bindingType).addProperty(prop); |
| } |
| |
| public Collection getQNameProperties() |
| { |
| if (!(bindingType instanceof ByNameBean)) |
| throw new IllegalStateException(); |
| return ((ByNameBean)bindingType).getProperties(); |
| } |
| |
| public boolean isStructureResolved() |
| { |
| return this.isStructureResolved; |
| } |
| |
| public void setStructureResolved(boolean isStructureResolved) |
| { |
| this.isStructureResolved = isStructureResolved; |
| } |
| } |
| |
| /** |
| * Returns an iterator for all the Scratch's |
| */ |
| private Iterator scratchIterator() |
| { |
| return scratchFromXmlName.values().iterator(); |
| } |
| |
| /** |
| * Returns an iterator for all the schema types |
| */ |
| private Iterator allTypeIterator() |
| { |
| class AllTypeIterator implements Iterator |
| { |
| int index; |
| List allSeenTypes; |
| |
| AllTypeIterator(SchemaTypeSystem sts) |
| { |
| allSeenTypes = new ArrayList(); |
| allSeenTypes.addAll(Arrays.asList(sts.documentTypes())); |
| allSeenTypes.addAll(Arrays.asList(sts.attributeTypes())); |
| allSeenTypes.addAll(Arrays.asList(sts.globalTypes())); |
| index = 0; |
| } |
| |
| public boolean hasNext() |
| { |
| return index < allSeenTypes.size(); |
| } |
| |
| public Object next() |
| { |
| SchemaType next = (SchemaType)allSeenTypes.get(index); |
| allSeenTypes.addAll(Arrays.asList(next.getAnonymousTypes())); |
| index += 1; |
| return next; |
| } |
| |
| public void remove() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| return new AllTypeIterator(sts); |
| } |
| |
| /** |
| * Iterates over all the top-level Java class names generated |
| * by this binding. Used by getToplevelClasses. |
| */ |
| private class TopLevelClassNameIterator implements Iterator |
| { |
| private final Iterator si = scratchIterator(); |
| private Scratch next = nextStructure(); |
| |
| public boolean hasNext() |
| { |
| return next != null; |
| } |
| |
| public Object next() |
| { |
| // todo: need to strip off x= from xmlobject name |
| String result = next.getJavaName().toString(); |
| next = nextStructure(); |
| return result; |
| } |
| |
| private Scratch nextStructure() |
| { |
| while (si.hasNext()) |
| { |
| Scratch scratch = (Scratch)si.next(); |
| if (scratch.getCategory() == Scratch.STRUCT_TYPE) |
| return scratch; |
| } |
| return null; |
| } |
| |
| public void remove() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| |
| /** |
| * Returns a collection of fully-qualified Java class name strings |
| * generated by this binding. |
| */ |
| public Collection getToplevelClasses() |
| { |
| return new AbstractCollection() |
| { |
| public Iterator iterator() |
| { |
| return new TopLevelClassNameIterator(); |
| } |
| |
| public int size() |
| { |
| return structureCount; |
| } |
| }; |
| } |
| |
| /** |
| * Prints the Java source code for the given generated java class name. |
| */ |
| public void printSourceCode(String topLevelClassName, OutputStream output) throws IOException |
| { |
| Scratch scratch = scratchForJavaNameString(topLevelClassName); |
| if (scratch == null) |
| throw new IllegalArgumentException(); |
| Writer writer = new OutputStreamWriter(output); |
| ScratchCodePrinter printer = new ScratchCodePrinter(scratch, writer); |
| printer.print(); |
| writer.flush(); |
| } |
| |
| /** |
| * Handles the printing for one generated java class. |
| */ |
| private class ScratchCodePrinter extends JavaCodePrinter |
| { |
| private Scratch scratch; |
| |
| ScratchCodePrinter(Scratch scratch, Writer writer) |
| { |
| super(writer); |
| assert(scratch.getCategory() == Scratch.STRUCT_TYPE); |
| this.scratch = scratch; |
| } |
| |
| private void print() throws IOException |
| { |
| JavaName javaName = scratch.getJavaName(); |
| |
| String packageName = javaName.getPackage(); |
| String shortClassName = javaName.getShortClassName(); |
| BindingType baseType = bindingTypeForSchemaType(scratch.getSchemaType().getBaseType()); |
| String baseJavaname = null; |
| if (baseType != null) |
| { |
| baseJavaname = baseType.getName().getJavaName().toString(); |
| if (baseJavaname.equals("java.lang.Object")) |
| baseJavaname = null; |
| } |
| |
| line("package " + packageName); |
| line(); |
| javadoc("Generated from schema type " + scratch.getXmlName()); |
| line("class " + shortClassName + (baseJavaname != null ? " extends " + baseJavaname : "")); |
| startBlock(); |
| |
| Collection props = scratch.getQNameProperties(); |
| Map fieldNames = new HashMap(); |
| Set seenFieldNames = new HashSet(); |
| |
| // pick field names |
| for (Iterator i = props.iterator(); i.hasNext(); ) |
| { |
| QNameProperty prop = (QNameProperty)i.next(); |
| fieldNames.put(prop, pickUniqueFieldName(prop.getGetterName(), seenFieldNames)); |
| } |
| |
| // print private fields |
| for (Iterator i = props.iterator(); i.hasNext(); ) |
| { |
| QNameProperty prop = (QNameProperty)i.next(); |
| JavaName jType = prop.getTypeName().getJavaName(); |
| if (prop.getCollectionClass() != null) |
| jType = prop.getCollectionClass(); |
| |
| line("private " + jType.toString() + " " + fieldNames.get(prop) + ";"); |
| } |
| |
| // print getters and setters |
| for (Iterator i = props.iterator(); i.hasNext(); ) |
| { |
| QNameProperty prop = (QNameProperty)i.next(); |
| JavaName jType = prop.getTypeName().getJavaName(); |
| if (prop.getCollectionClass() != null) |
| jType = prop.getCollectionClass(); |
| String fieldName = (String)fieldNames.get(prop); |
| |
| line(); |
| line("public " + jType.toString() + " " + prop.getGetterName() + "()"); |
| startBlock(); |
| line("return " + fieldName + ";"); |
| endBlock(); |
| line(); |
| line("public void " + prop.getSetterName() + "(" + jType.toString() + " " + fieldName + ")"); |
| startBlock(); |
| line("this." + fieldName + " = " + fieldName + ";"); |
| endBlock(); |
| } |
| |
| endBlock(); |
| } |
| |
| private String pickUniqueFieldName(String getter, Set seenNames) |
| { |
| String baseName; |
| |
| if (getter.length() > 3 && getter.startsWith("get")) |
| baseName = Character.toLowerCase(getter.charAt(3)) + getter.substring(4); |
| else |
| baseName = "field"; |
| |
| String fieldName = baseName; |
| for (int i = 1; seenNames.contains(fieldName); i += 1) |
| fieldName = baseName + i; |
| |
| seenNames.add(fieldName); |
| return fieldName; |
| } |
| } |
| |
| /** |
| * Prints the binding file generated by this binding. |
| */ |
| public void printBindingFile(OutputStream output) throws IOException |
| { |
| bindingFile.write().save(output, new XmlOptions().setSavePrettyPrint()); |
| } |
| |
| /** |
| * Returns a BindingFileGenerator for this SchemaToJavaResult. |
| */ |
| public BindingFileGenerator getBindingFileGenerator() |
| { |
| return this; |
| } |
| |
| /** |
| * Returns the JavaCodeGenerator for this SchemaToJavaResult. |
| */ |
| public JavaCodeGenerator getJavaCodeGenerator() |
| { |
| return this; |
| } |
| } |