/* * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 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 name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``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 * INTALIO, INC. 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. * * Copyright 1999-2004 (C) Intalio Inc. All Rights Reserved. * * This file was originally developed by Keith Visco during the course * of employment at Intalio Inc. * Portions of this file developed by Keith Visco after Jan 19 2005 are * Copyright (C) 2005 Keith Visco. All Rights Reserverd. * * $Id$ */ package org.exolab.castor.builder.factory; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import org.exolab.castor.builder.BuilderConfiguration; import org.exolab.castor.builder.ClassInfoResolver; import org.exolab.castor.builder.GroupNaming; import org.exolab.castor.builder.SGTypes; import org.exolab.castor.builder.SourceGenerator; import org.exolab.castor.builder.SourceGeneratorConstants; import org.exolab.castor.builder.binding.XMLBindingComponent; import org.exolab.castor.builder.info.ClassInfo; import org.exolab.castor.builder.info.CollectionInfo; import org.exolab.castor.builder.info.FieldInfo; import org.exolab.castor.builder.info.XMLInfo; import org.exolab.castor.builder.info.NodeType; import org.exolab.castor.builder.info.nature.XMLInfoNature; import org.exolab.castor.builder.types.XSList; import org.exolab.castor.builder.types.XSListType; import org.exolab.castor.builder.types.XSClass; import org.exolab.castor.builder.types.XSString; import org.exolab.castor.builder.types.XSType; import org.exolab.castor.xml.schema.AttributeDecl; import org.exolab.castor.xml.schema.ComplexType; import org.exolab.castor.xml.schema.ElementDecl; import org.exolab.castor.xml.schema.Facet; import org.exolab.castor.xml.schema.Group; import org.exolab.castor.xml.schema.Order; import org.exolab.castor.xml.schema.Schema; import org.exolab.castor.xml.schema.SchemaNames; import org.exolab.castor.xml.schema.SimpleType; import org.exolab.castor.xml.schema.Structure; import org.exolab.castor.xml.schema.Wildcard; import org.exolab.castor.xml.schema.XMLType; import org.exolab.castor.xml.schema.simpletypes.ListType; import org.exolab.javasource.JArrayType; import org.exolab.javasource.JClass; import org.exolab.javasource.JPrimitiveType; import org.exolab.javasource.JType; /** * The "Factory" responsible for creating fields for the given schema components. * * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a> * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ */ public final class MemberFactory extends BaseFactory { /** * Creates a new MemberFactory using the given FieldInfo factory. * * @param config the BuilderConfiguration * @param infoFactory the FieldInfoFactory to use * @param groupNaming Grou pnaming scheme to be used. * @param sourceGenerator Calling source generator */ public MemberFactory(final BuilderConfiguration config, final FieldInfoFactory infoFactory, final GroupNaming groupNaming, final SourceGenerator sourceGenerator) { super(config, infoFactory, groupNaming, sourceGenerator); if (getConfig().generateExtraCollectionMethods()) { this.getInfoFactory().setCreateExtraMethods(true); } String suffix = getConfig().getProperty(CollectionInfo.REFERENCE_SUFFIX_PROPERTY, null); this.getInfoFactory().setReferenceMethodSuffix(suffix); if (getConfig().boundPropertiesEnabled()) { this.getInfoFactory().setBoundProperties(true); } } //-- MemberFactory /** * Creates a FieldInfo for content models that support "any" element. * * @param any the wildcard we will operate on * @param useJava50 if true then we will generate code for Java 5 * * @return the new FieldInfo */ public FieldInfo createFieldInfoForAny(final Wildcard any, final boolean useJava50) { if (any == null) { return null; } //--currently anyAttribute is not supported if (any.isAttributeWildcard()) { return null; } XSType xsType = new XSClass(SGTypes.OBJECT, "any"); String vName = "_anyObject"; String xmlName = null; FieldInfo result = null; if (any.getMaxOccurs() > 1 || any.getMaxOccurs() < 0) { result = this.getInfoFactory().createCollection(xsType, vName, "anyObject", getJavaNaming(), useJava50); XSListType xsList = ((CollectionInfo) result).getXSList(); xsList.setMinimumSize(any.getMinOccurs()); xsList.setMaximumSize(any.getMaxOccurs()); } else { result = this.getInfoFactory().createFieldInfo(xsType, vName); } if (result.hasNature(XMLInfoNature.class.getName())) { XMLInfoNature xmlNature = new XMLInfoNature(result); if (any.getMinOccurs() > 0) { xmlNature.setRequired(true); } else { xmlNature.setRequired(false); } xmlNature.setNodeName(xmlName); //--LIMITATION: //-- 1- we currently support only the FIRST namespace //-- 2- ##other, ##any are not supported if (any.getNamespaces().hasMoreElements()) { String nsURI = (String) any.getNamespaces().nextElement(); if (nsURI.length() > 0) { if (nsURI.equals("##targetNamespace")) { Schema schema = any.getSchema(); if (schema != null) { xmlNature.setNamespaceURI(schema.getTargetNamespace()); } } else if (!nsURI.startsWith("##")) { xmlNature.setNamespaceURI(nsURI); } } } //--first namespace } return result; } /** * Creates a FieldInfo to hold the value of a choice. * * @return the new FieldInfo */ public FieldInfo createFieldInfoForChoiceValue() { String fieldName = "_choiceValue"; XSType xsType = new XSClass(SGTypes.OBJECT, "any"); FieldInfo fInfo = null; fInfo = this.getInfoFactory().createFieldInfo(xsType, fieldName); fInfo.setComment("Internal choice value storage"); fInfo.setTransient(true); fInfo.setMethods(FieldInfo.READ_METHOD); if (fInfo.hasNature(XMLInfoNature.class.getName())) { XMLInfoNature xmlNature = new XMLInfoNature(fInfo); xmlNature.setNodeType(NodeType.ELEMENT); xmlNature.setRequired(false); xmlNature.setNodeName("##any"); } return fInfo; } /** * Creates a FieldInfo for content. * @param component {@link XMLBindingComponent} instance for accessing binding information. * @param xsType the type of content * @param useJava50 if true, code will be generated for Java 5 * * @return the new FieldInfo */ public FieldInfo createFieldInfoForContent(final XMLBindingComponent component, final XSType xsType, final boolean useJava50) { String fieldName = "_content"; //new xsType()??? if (component.getContentMemberName() != null) { fieldName = component.getContentMemberName(); } FieldInfo fInfo = null; if (xsType.isCollection()) { fInfo = this.getInfoFactory().createCollection( ((XSListType) xsType).getContentType(), fieldName, null, getJavaNaming(), useJava50); } else { fInfo = this.getInfoFactory().createFieldInfo(xsType, fieldName); } fInfo.setComment("internal content storage"); if (xsType instanceof XSString) { fInfo.setDefaultValue("\"\""); } if (fInfo.hasNature(XMLInfoNature.class.getName())) { XMLInfoNature xmlNature = new XMLInfoNature(fInfo); xmlNature.setNodeType(NodeType.TEXT); xmlNature.setRequired(false); xmlNature.setNodeName("#text"); } return fInfo; } /** * Creates a FieldInfo object for the given XMLBindingComponent. * * @param component the XMLBindingComponent to create the FieldInfo for * @param resolver resolver to use to find ClassInfo * @param useJava50 if true, code will be generated for Java 5 * @return the FieldInfo for the given attribute declaration */ public FieldInfo createFieldInfo(final XMLBindingComponent component, final ClassInfoResolver resolver, final boolean useJava50) { String xmlName = component.getXMLName(); String memberName = component.getJavaMemberName(); if (!memberName.startsWith("_")) { memberName = "_" + memberName; } XMLType xmlType = component.getXMLType(); ClassInfo classInfo = resolver.resolve(component); XSType xsType = null; FieldInfo fieldInfo = null; boolean enumeration = false; boolean simpleTypeCollection = false; if (xmlType != null) { if (xmlType.isSimpleType() ) { SimpleType simpleType = (SimpleType) xmlType; SimpleType baseType = null; String derivationMethod = simpleType.getDerivationMethod(); if (derivationMethod != null) { if (SchemaNames.RESTRICTION.equals(derivationMethod)) { baseType = (SimpleType) simpleType.getBaseType(); } } //-- handle special case for enumerated types if (simpleType.hasFacet(Facet.ENUMERATION)) { //-- LOok FoR CLasSiNfO iF ReSoLvR is NoT NuLL enumeration = true; if (resolver != null) { classInfo = resolver.resolve(xmlType); } if (classInfo != null) { XMLInfoNature xmlNature = new XMLInfoNature(classInfo); xsType = xmlNature.getSchemaType(); } } else if ((simpleType instanceof ListType) || (baseType instanceof ListType)) { if (baseType != null) { if (!baseType.isBuiltInType()) { simpleTypeCollection = true; } } else { if (!simpleType.isBuiltInType()) { simpleTypeCollection = true; } } // handle special case where the list type uses an item type // that has enumeration facets defined. ListType listType = (ListType) simpleType; if (listType == null) { listType = (ListType) baseType; } SimpleType itemType = listType.getItemType(); if (itemType.hasFacet(Facet.ENUMERATION)) { ClassInfo itemClassInfo = resolver.resolve(itemType); if (itemClassInfo != null) { xsType = new XMLInfoNature(itemClassInfo).getSchemaType(); } else { XMLBindingComponent temp = new XMLBindingComponent( getConfig(), getGroupNaming()); temp.setBinding(component.getBinding()); temp.setView(itemType); String packageName = temp.getJavaPackage(); if (packageName != null && packageName.length() > 0) { packageName = packageName + "." + SourceGeneratorConstants.TYPES_PACKAGE; } else { packageName = SourceGeneratorConstants.TYPES_PACKAGE; } JClass tempClass = new JClass(packageName+ "." + temp.getJavaClassName()); xsType = new XSClass(tempClass); xsType.setAsEnumerated(true); } } } if (xsType == null) { xsType = component.getJavaType(); } } else if (xmlType.isAnyType()) { //-- Just treat as java.lang.Object. if (classInfo != null) { XMLInfoNature xmlNature = new XMLInfoNature(classInfo); xsType = xmlNature.getSchemaType(); } if (xsType == null) { xsType = new XSClass(SGTypes.OBJECT); } } else if (xmlType.isComplexType() && (xmlType.getName() != null)) { //--if we use the type method then no class is output for //--the element we are processing if (getConfig().mappingSchemaType2Java()) { XMLBindingComponent temp = new XMLBindingComponent( getConfig(), getGroupNaming()); temp.setBinding(component.getBinding()); temp.setView(xmlType); ClassInfo typeInfo = resolver.resolve(xmlType); if (typeInfo != null) { // if we have not processed the <complexType> referenced // by the ClassInfo yet, this will return null // TODO find a way to resolve an unprocessed <complexType> XMLInfoNature xmlNature = new XMLInfoNature(typeInfo); xsType = xmlNature.getSchemaType(); } else { String className = temp.getQualifiedName(); if (className != null) { JClass jClass = new JClass(className); if (((ComplexType) xmlType).isAbstract()) { jClass.getModifiers().setAbstract(true); } xsType = new XSClass(jClass); className = null; } } } } // complexType } else { if (xsType == null) { xsType = component.getJavaType(); } if (xsType == null) { //-- patch for bug 1471 (No XMLType specified) //-- treat unspecified type as anyType switch (component.getAnnotated().getStructureType()) { case Structure.ATTRIBUTE: AttributeDecl attribute = (AttributeDecl) component.getAnnotated(); if (!attribute.hasXMLType()) { xsType = new XSClass(SGTypes.OBJECT); } break; case Structure.ELEMENT: ElementDecl element = (ElementDecl) component.getAnnotated(); if (!element.hasXMLType()) { xsType = new XSClass(SGTypes.OBJECT); } break; default: // probably a model-group break; } } } // is the XSType found? if (xsType == null) { String className = component.getQualifiedName(); JClass jClass = new JClass(className); if (component.isAbstract()) { jClass.getModifiers().setAbstract(true); } if (getConfig().isAutomaticConflictResolution()) { getSourceGenerator().getXMLInfoRegistry().bind(jClass, component, "field"); } xsType = new XSClass(jClass); if (xmlType != null && xmlType.isComplexType()) { ComplexType complexType = (ComplexType) xmlType; if (complexType.isAbstract() || getConfig().mappingSchemaElement2Java()) { jClass.getModifiers().setAbstract(true); } } className = null; } // create the fieldInfo // check whether this should be a collection or not int maxOccurs = component.getUpperBound(); int minOccurs = component.getLowerBound(); if (simpleTypeCollection || ((maxOccurs < 0 || maxOccurs > 1) && !this.isChoice(component))) { String vName = memberName + "List"; // if xmlName is null it means that // we are processing a container object (group) // so we need to adjust the name of the members of the collection CollectionInfo cInfo; cInfo = this.getInfoFactory().createCollection(xsType, vName, memberName, component.getCollectionType(), getJavaNaming(), useJava50); XSListType xsList = cInfo.getXSList(); if (!simpleTypeCollection) { xsList.setMaximumSize(maxOccurs); xsList.setMinimumSize(minOccurs); } else { if (xsList instanceof XSList) { ((XSList) xsList).setDerivedFromXSList(true); } } fieldInfo = cInfo; } else { switch (xsType.getType()) { case XSType.ID_TYPE: fieldInfo = this.getInfoFactory().createIdentity(memberName); break; case XSType.COLLECTION: case XSType.IDREFS_TYPE: case XSType.NMTOKENS_TYPE: String collectionName = component.getCollectionType(); XSType contentType = ((XSListType) xsType).getContentType(); fieldInfo = this.getInfoFactory().createCollection(contentType, memberName, memberName, collectionName, getJavaNaming(), useJava50); break; default: fieldInfo = this.getInfoFactory().createFieldInfo(xsType, memberName); break; } } // initialize the field XMLInfoNature xmlNature = new XMLInfoNature(fieldInfo); xmlNature.setNodeName(xmlName); xmlNature.setRequired(minOccurs > 0); switch (component.getAnnotated().getStructureType()) { case Structure.ELEMENT: xmlNature.setNodeType(NodeType.ELEMENT); break; case Structure.ATTRIBUTE: xmlNature.setNodeType(NodeType.ATTRIBUTE); break; case Structure.MODELGROUP: case Structure.GROUP: xmlNature.setNodeName(XMLInfo.CHOICE_NODE_NAME_ERROR_INDICATION); fieldInfo.setContainer(true); break; default: break; } //-- handle namespace URI / prefix String nsURI = component.getTargetNamespace(); if ((nsURI != null) && (nsURI.length() > 0)) { xmlNature.setNamespaceURI(nsURI); // TODO set the prefix used in the XML Schema // in order to use it inside the Marshaling Framework } // handle default value (if any is set) handleDefaultValue(component, classInfo, xsType, fieldInfo, enumeration); //-- handle nillable values if (component.isNillable()) { fieldInfo.setNillable(true); } //-- add annotated comments String comment = createComment(component.getAnnotated()); if (comment != null) { fieldInfo.setComment(comment); } //--specific field handler or validator? if (component.getXMLFieldHandler() != null) { fieldInfo.setXMLFieldHandler(component.getXMLFieldHandler()); } if (component.getValidator() != null) { fieldInfo.setValidator(component.getValidator()); } if (component.getVisiblity() != null) { String visibility = component.getVisiblity(); fieldInfo.setVisibility(visibility); } // deal with substitution groups switch (component.getAnnotated().getStructureType()) { case Structure.ELEMENT: ElementDecl elementDeclaration = (ElementDecl) component.getAnnotated(); if (elementDeclaration.isReference()) { elementDeclaration = elementDeclaration.getReference(); } Enumeration<ElementDecl> possibleSubstitutes = elementDeclaration.getSubstitutionGroupMembers(); if (possibleSubstitutes.hasMoreElements()) { List<String> substitutionGroupMembers = new ArrayList<String>(); while (possibleSubstitutes.hasMoreElements()) { ElementDecl substitute = possibleSubstitutes.nextElement(); substitutionGroupMembers.add(substitute.getName()); } fieldInfo.setSubstitutionGroupMembers(substitutionGroupMembers); } default: } return fieldInfo; } /** * Determines if the given <code>component</code> represents a choice. * * @param component * The XMLBindingComponent to check. * @return <code>true</code> if and only if the given XMLBindingComponent * represents a choice. Otherwise returns <code>false</code> */ private boolean isChoice(final XMLBindingComponent component) { Group group = this.getGroup(component.getAnnotated()); if (group == null || group.getOrder() == null) { return false; } return group.getOrder() == Order.choice; } /** * Returns the given <code>structure</code> as Group if it represents one.<br> * <br> * If the given <code>structure</code> has the structure type * <code>GROUP</code> this method returns the given <code>structure</code> * itself (casted to Group). If the structure is of any other type or is * <code>null</code> this method will return <code>null</code> * * @param structure * The Structure to be returned as Group. * @return The given <code>structure</code> if and only if it is a group; * otherwise returns <code>null</code>. */ private Group getGroup(final Structure structure) { if (structure == null) { return null; } if (structure.getStructureType() == Structure.GROUP) { return (Group) structure; } return null; } /** * Handle default or fixed value, if any is set. * @param component The component on which a default value is set * @param classInfo The corresponding ClassInfo instance. * @param xsType The schema type of the component. * @param fieldInfo The FieldInfo into which to inject a default value * @param enumeration If we are looking at an enumeration. */ private void handleDefaultValue(final XMLBindingComponent component, final ClassInfo classInfo, final XSType xsType, final FieldInfo fieldInfo, final boolean enumeration) { String value = component.getValue(); if (value == null) { return; } value = adjustDefaultValue(xsType, value); if (value.length() == 0) { value = "\"\""; } // TODO Need to change this...and to validate the value...to be done at reading time. //-- clean up value //-- if the xsd field is mapped into a java.lang.String if (xsType.getJType().toString().equals("java.lang.String")) { char ch = value.charAt(0); if (ch != '\'' && ch != '\"') { value = '\"' + value + '\"'; } // deals with special characters, e.g. line feed StringBuffer buffer = new StringBuffer(value.length()); for (int i = 0; i < value.length(); i++) { char character = value.charAt(i); switch(character) { case '\n': buffer.append("\\n"); break; default: buffer.append(character); } } value = buffer.toString(); } else if (enumeration) { JType jType = (classInfo != null) ? classInfo.getJClass() : xsType.getJType(); if (getSourceGenerator().useJava5Enums()) { value = jType.getName() + ".fromValue(\"" + value + "\")"; } else { value = jType.getName() + ".valueOf(\"" + value + "\")"; } } else if (xsType.getJType().isArray()) { JType componentType = ((JArrayType) xsType.getJType()).getComponentType(); if (componentType.isPrimitive()) { JPrimitiveType primitive = (JPrimitiveType) componentType; value = "new " + primitive.getName() + "[] { " + primitive.getWrapperName() + ".valueOf(\"" + value + "\")." + primitive.getName() + "Value() }"; } else { value = "new " + componentType.getName() + "[] { " + componentType.getName() + ".valueOf(\"" + value + "\") }"; } } else if (!(xsType.getJType().isPrimitive())) { if (xsType.isDateTime()) { // Castor marshals DATETIME_TYPE into java.util.Date(), so we need to convert it if (xsType.getType() == XSType.DATETIME_TYPE) { // FIXME This fails if the DateTIme has a time zone // because we throw away the time zone in toDate() value = "new org.exolab.castor.types.DateTime(\"" + value + "\").toDate()"; } else { value = "new " + xsType.getJType().getName() + "(\"" + value + "\")"; } } else { // FIXME This works only if a constructor with String as parameter exists value = "new " + xsType.getJType().getName() + "(\"" + value + "\")"; } } if (component.isFixed()) { fieldInfo.setFixedValue(value); } else { fieldInfo.setDefaultValue(value); } } /** * Adjusts the default value string represenation to reflect the semantics * of various 'special' data types. * * @param xsType The XMl schems type of the value to adjust * @param value The actual value to adjust * @return an adjusted default value. */ private String adjustDefaultValue(final XSType xsType, final String value) { switch (xsType.getType()) { case XSType.FLOAT_TYPE: return value + 'f'; case XSType.BOOLEAN_TYPE: Boolean bool = new Boolean(value); return bool.toString(); default: break; } return value; } } //-- MemberFactory