/*
* 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