/*
* 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.
* All 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.descriptors;
import java.util.Iterator;
import java.util.List;
import org.exolab.castor.builder.BuilderConfiguration;
import org.exolab.castor.builder.SGTypes;
import org.exolab.castor.builder.factory.XMLFieldHandlerFactory;
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.XSType;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.xml.Validator;
import org.exolab.castor.xml.XMLConstants;
import org.exolab.castor.xml.XMLFieldDescriptor;
import org.exolab.javasource.JClass;
import org.exolab.javasource.JConstant;
import org.exolab.javasource.JConstructor;
import org.exolab.javasource.JField;
import org.exolab.javasource.JMember;
import org.exolab.javasource.JModifiers;
import org.exolab.javasource.JNaming;
import org.exolab.javasource.JPrimitiveType;
import org.exolab.javasource.JSourceCode;
import org.exolab.javasource.JType;
/**
* A factory for creating the source code of descriptor classes.
*
* @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
* @version $Revision$ $Date: 2006-04-13 07:37:49 -0600 (Thu, 13 Apr 2006) $
*/
public final class DescriptorSourceFactory {
/** GeneralizedFieldHandler. */
private static final JClass GENERALIZED_FIELD_HANDLER_CLASS =
new JClass("org.exolab.castor.mapping.GeneralizedFieldHandler");
/** Name of the field validator instance variable in generated code. */
private static final String FIELD_VALIDATOR_NAME = "fieldValidator";
/** The BuilderConfiguration instance. */
private final BuilderConfiguration _config;
/** Factory for creating XMLFieldHandler instances embedded in descriptors. */
private XMLFieldHandlerFactory _xmlFieldHandlerFactory;
/**
* Creates a new DescriptorSourceFactory with the given configuration.
*
* @param config the BuilderConfiguration instance
*/
public DescriptorSourceFactory(final BuilderConfiguration config) {
if (config == null) {
String err = "The argument 'config' must not be null.";
throw new IllegalArgumentException(err);
}
_config = config;
_xmlFieldHandlerFactory = new XMLFieldHandlerFactory(config);
} //-- DescriptorSourceFactory
/**
* Creates the Source code of a MarshalInfo for a given XML Schema element
* declaration.
*
* @param classInfo the XML Schema element declaration
* @return the JClass representing the MarshalInfo source code
*/
public JClass createSource(final ClassInfo classInfo) {
JClass jClass = classInfo.getJClass();
String localClassName = jClass.getLocalName();
String descriptorClassName = getQualifiedDescriptorClassName(jClass.getName());
DescriptorJClass classDesc = new DescriptorJClass(_config, descriptorClassName, jClass);
//-- get handle to default constuctor
JConstructor cons = classDesc.getConstructor(0);
JSourceCode jsc = cons.getSourceCode();
XMLInfoNature xmlNature = new XMLInfoNature(classInfo);
//-- Set namespace prefix
String nsPrefix = xmlNature.getNamespacePrefix();
if ((nsPrefix != null) && (nsPrefix.length() > 0)) {
jsc.add("_nsPrefix = \"");
jsc.append(nsPrefix);
jsc.append("\";");
}
//-- Set namespace URI
String nsURI = xmlNature.getNamespaceURI();
if ((nsURI != null) && (nsURI.length() > 0)) {
jsc.add("_nsURI = \"");
jsc.append(nsURI);
jsc.append("\";");
}
//-- set XML Name
String xmlName = xmlNature.getNodeName();
if (xmlName != null) {
jsc.add("_xmlName = \"");
jsc.append(xmlName);
jsc.append("\";");
}
//-- set Element Definition flag
boolean elementDefinition = xmlNature.isElementDefinition();
jsc.add("_elementDefinition = ");
jsc.append(new Boolean(elementDefinition).toString());
jsc.append(";");
//-- set grouping compositor
if (xmlNature.isChoice()) {
jsc.add("");
jsc.add("//-- set grouping compositor");
jsc.add("setCompositorAsChoice();");
} else if (xmlNature.isSequence()) {
jsc.add("");
jsc.add("//-- set grouping compositor");
jsc.add("setCompositorAsSequence();");
}
// handle substitution groups
List<String> substitutionGroups = xmlNature.getSubstitutionGroups();
if (!substitutionGroups.isEmpty()) {
jsc.add("java.util.List substitutionGroups = new java.util.ArrayList();");
Iterator<String> substitutionGroupIter = substitutionGroups.iterator();
while (substitutionGroupIter.hasNext()) {
String substitutionGroup = substitutionGroupIter.next();
jsc.add("substitutionGroups.add(\"");
jsc.append(substitutionGroup);
jsc.append("\");");
}
jsc.add("setSubstitutes(substitutionGroups);");
}
//-- To prevent compiler warnings...make sure
//-- we don't declare temp variables if field count is 0;
if (classInfo.getFieldCount() == 0) {
return classDesc;
}
//-- declare temp variables
jsc.add("org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null;");
jsc.add("org.exolab.castor.mapping.FieldHandler handler = null;");
jsc.add("org.exolab.castor.xml.FieldValidator fieldValidator = null;");
//-- handle content
if (classInfo.allowContent()) {
createDescriptor(classDesc, classInfo.getTextField(), localClassName, null, jsc);
}
ClassInfo base = classInfo.getBaseClass();
FieldInfo[] atts = classInfo.getAttributeFields();
//--------------------------------/
//- Create attribute descriptors -/
//--------------------------------/
jsc.add("//-- initialize attribute descriptors");
jsc.add("");
for (int i = 0; i < atts.length; i++) {
FieldInfo member = atts[i];
//-- skip transient members
if (member.isTransient()) {
continue;
}
if (base != null) {
String baseNodeName = new XMLInfoNature(member).getNodeName();
if (baseNodeName.equals(XMLInfo.CHOICE_NODE_NAME_ERROR_INDICATION)) {
createDescriptor(classDesc, member, localClassName, nsURI, jsc);
} else {
if (base.getAttributeField(baseNodeName) != null) {
createRestrictedDescriptor(member, jsc);
} else {
createDescriptor(classDesc, member, localClassName,
nsURI, jsc);
}
}
} else {
createDescriptor(classDesc, member, localClassName, nsURI, jsc);
}
}
//------------------------------/
//- Create element descriptors -/
//------------------------------/
FieldInfo[] elements = classInfo.getElementFields();
jsc.add("//-- initialize element descriptors");
jsc.add("");
for (int i = 0; i < elements.length; i++) {
FieldInfo member = elements[i];
XMLInfoNature fieldNature = new XMLInfoNature(member);
//-- skip transient members
if (member.isTransient()) {
continue;
}
if (base != null) {
String baseNodeName = fieldNature.getNodeName();
if (baseNodeName == null) {
createDescriptor(classDesc, member, localClassName, nsURI, jsc);
} else if (baseNodeName.equals(XMLInfo.CHOICE_NODE_NAME_ERROR_INDICATION)) {
createDescriptor(classDesc, member, localClassName, nsURI, jsc);
} else {
if (base.getElementField(baseNodeName) != null) {
createRestrictedDescriptor(member, jsc);
} else {
createDescriptor(classDesc, member, localClassName,
nsURI, jsc);
}
}
} else {
createDescriptor(classDesc, member, localClassName, nsURI, jsc);
}
}
return classDesc;
} //-- createSource
//-------------------/
//- Private Methods -/
//-------------------/
/**
* Returns the fully-qualified class name of the Descriptor to create. Given
* the fully-qualified class name of the class we are creating a Descriptor
* for, return the correct fully-qualified name for the Descriptor.
*
* @param name
* fully-qualified class name of the class we are describing
* @return the fully-qualified class name of the Descriptor to create
*/
private String getQualifiedDescriptorClassName(final String name) {
String descPackage = JNaming.getPackageFromClassName(name);
String descClassName = JNaming.getLocalNameFromClassName(name);
if (descPackage != null && descPackage.length() > 0) {
descPackage = descPackage + "." + XMLConstants.DESCRIPTOR_PACKAGE + ".";
} else {
descPackage = "";
}
return descPackage + descClassName + XMLConstants.DESCRIPTOR_SUFFIX;
}
/**
* Create special code for handling a member that is a restriction,
* only changing the validation code.
*
* This basically works by obtaining the {@link FieldDescriptor} instance from the
* base class, and setting a new {@link Validator} instance.
*
* @param member the restricted member for which we generate the restriction handling.
* @param jsc the source code to which we append the validation code.
*/
private static void createRestrictedDescriptor(final FieldInfo member, final JSourceCode jsc) {
jsc.add("desc = (org.exolab.castor.xml.util.XMLFieldDescriptorImpl) getFieldDescriptor(\"");
XMLInfoNature xmlNature = new XMLInfoNature(member);
jsc.append(xmlNature.getNodeName());
jsc.append("\"");
jsc.append(", _nsURI");
NodeType nodeType = xmlNature.getNodeType();
if (nodeType == NodeType.ELEMENT) {
jsc.append(", org.exolab.castor.xml.NodeType.Element);");
} else if (nodeType == NodeType.ATTRIBUTE) {
jsc.append(", org.exolab.castor.xml.NodeType.Attribute);");
} else {
jsc.append("org.exolab.castor.xml.NodeType.Text);");
}
//-- deal with amended cardinality
if (xmlNature.isRequired()) {
jsc.add("desc.setRequired(true);");
}
//--modify the validation code
addValidationCode(member, jsc);
}
/**
* Creates a specific descriptor for a given member (whether an attribute or
* an element) represented by a given Class name.
*
* @param classDesc JClass-equivalent descriptor for this Descriptor class
* @param member the member for which to create a descriptor
* @param localClassName unqualified (no package) name of this class
* @param nsURI namespace URI
* @param jsc the source code to which we'll add this descriptor
*/
private void createDescriptor(final DescriptorJClass classDesc, final FieldInfo member,
final String localClassName, final String nsURI,
final JSourceCode jsc) {
XMLInfoNature xmlNature = new XMLInfoNature(member);
XSType xsType = xmlNature.getSchemaType();
XSType xsCollectionType = null;
boolean any = false;
NodeType nodeType = xmlNature.getNodeType();
boolean isElement = (nodeType == NodeType.ELEMENT);
boolean isAttribute = (nodeType == NodeType.ATTRIBUTE);
boolean isText = (nodeType == NodeType.TEXT);
jsc.add("//-- ");
jsc.append(member.getName());
//-- a hack, I know, I will change later (kv)
if (member.getName().equals("_anyObject")) {
any = true;
}
if (xsType.isCollection()) {
//Attributes can handle COLLECTION type for NMTOKENS or IDREFS for instance
xsCollectionType = xsType;
xsType = new XMLInfoNature(((CollectionInfo) member).getContent()).getSchemaType();
}
// Resolve how the node name parameter to the XMLFieldDescriptorImpl constructor is supplied
String nodeName = xmlNature.getNodeName();
String nodeNameParam = null;
if ((nodeName != null) && (!isText)) {
//-- By default the node name parameter is a literal string
nodeNameParam = "\"" + nodeName + "\"";
if (_config.classDescFieldNames()) {
//-- The node name parameter is a reference to a public static final
nodeNameParam = nodeName.toUpperCase();
//-- Expose node name as public static final (reused by XMLFieldDescriptorImpl)
JConstant constant = new JConstant(SGTypes.STRING, nodeNameParam);
constant.setInitString("\"" + nodeName + "\"");
classDesc.addMember(constant);
}
}
//-- Generate code to new XMLFieldDescriptorImpl instance
jsc.add("desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl(");
jsc.append(classType(xsType.getJType()));
jsc.append(", \"");
jsc.append(member.getName());
jsc.append("\", ");
if (nodeNameParam != null) {
jsc.append(nodeNameParam);
} else if (isText) {
jsc.append("\"PCDATA\"");
} else {
jsc.append("(java.lang.String) null");
}
if (isElement) {
jsc.append(", org.exolab.castor.xml.NodeType.Element);");
} else if (isAttribute) {
jsc.append(", org.exolab.castor.xml.NodeType.Attribute);");
} else if (isText) {
jsc.append(", org.exolab.castor.xml.NodeType.Text);");
}
switch (xsType.getType()) {
case XSType.STRING_TYPE :
jsc.add("desc.setImmutable(true);");
break;
//only for attributes
case XSType.IDREF_TYPE :
jsc.add("desc.setReference(true);");
break;
case XSType.ID_TYPE :
jsc.add("this._identity = desc;");
break;
case XSType.QNAME_TYPE :
jsc.add("desc.setSchemaType(\"QName\");");
break;
default :
break;
} //switch
//-- handler access methods
if (member.getXMLFieldHandler() != null) {
String handler = member.getXMLFieldHandler();
jsc.add("handler = new " + handler + "();");
jsc.add("//-- test for generalized field handler");
jsc.add("if (handler instanceof ");
jsc.append(GENERALIZED_FIELD_HANDLER_CLASS.getName());
jsc.append(")");
jsc.add("{");
jsc.indent();
jsc.add("//-- save reference to user-specified handler");
jsc.add(GENERALIZED_FIELD_HANDLER_CLASS.getName());
jsc.append(" gfh = (");
jsc.append(GENERALIZED_FIELD_HANDLER_CLASS.getName());
jsc.append(") handler;");
_xmlFieldHandlerFactory.createXMLFieldHandler(
member, xsType, localClassName, jsc, true);
jsc.add("gfh.setFieldHandler(handler);");
jsc.add("handler = gfh;");
jsc.unindent();
jsc.add("}");
} else {
_xmlFieldHandlerFactory.createXMLFieldHandler(
member, xsType, localClassName, jsc, false);
addSpecialHandlerLogic(member, xsType, jsc);
}
// Add the schema type as defined in the schema
if (xsCollectionType == null) {
jsc.add("desc.setSchemaType(\"" + xsType.getName() + "\");");
} else {
jsc.add("desc.setSchemaType(\"list\");");
jsc.add("desc.setComponentType(\"" + xsType.getName() + "\");");
if (xsCollectionType instanceof XSList && ((XSList) xsCollectionType).isDerivedFromXSList()) {
jsc.add("desc.setDerivedFromXSList(true);");
}
}
jsc.add("desc.setHandler(handler);");
//-- container
if (member.isContainer()) {
jsc.add("desc.setContainer(true);");
String className = xsType.getName(); //set the class descriptor
//Try to prevent endless loop. Note: we only compare the localClassName.
//If the packages are different an error can happen here
if (className.equals(localClassName)) {
jsc.add("desc.setClassDescriptor(this);");
} else {
String descriptorClassName = getQualifiedDescriptorClassName(className);
jsc.add("desc.setClassDescriptor(new " + descriptorClassName + "());");
}
}
//-- Handle namespaces
//-- FieldInfo namespace has higher priority than ClassInfo namespace.
if (xmlNature.getNamespaceURI() != null) {
jsc.add("desc.setNameSpaceURI(\"");
jsc.append(xmlNature.getNamespaceURI());
jsc.append("\");");
}
//-- required
if (xmlNature.isRequired()) {
jsc.add("desc.setRequired(true);");
}
//-- nillable
if (member.isNillable()) {
jsc.add("desc.setNillable(true);");
}
//-- if any it can match all the names
if (any) {
jsc.add("desc.setMatches(\"*\");");
}
//-- mark as multi or single valued for elements
if (isElement || isAttribute) {
jsc.add("desc.setMultivalued(" + xmlNature.isMultivalued());
jsc.append(");");
}
jsc.add("addFieldDescriptor(desc);");
if (isElement) {
jsc.add("addSequenceElement(desc);");
}
jsc.add("");
if (isElement) {
// handle substitution groups
addSubstitutionGroups(member, jsc);
}
//-- Add Validation Code
addValidationCode(member, jsc);
}
/**
* Adds substitution groups to the {@link XMLFieldDescriptor} instance .
* @param member The {@link FieldInfo} instance holding substitution group information
* @param jsc The {@link JSourceCode} instance to write the substitution groups to.
*/
private void addSubstitutionGroups(final FieldInfo member, final JSourceCode jsc) {
List<String> substitutionGroupMembers = member.getSubstitutionGroupMembers();
if (!substitutionGroupMembers.isEmpty()) {
jsc.add("// set possible substitutes for member " + member.getName());
jsc.add("java.util.List substitutionGroups" + member.getName()
+ " = new java.util.ArrayList();");
Iterator<String> substitutionGroupIter = substitutionGroupMembers.iterator();
while (substitutionGroupIter.hasNext()) {
String substitutionGroup = substitutionGroupIter.next();
jsc.add("substitutionGroups" + member.getName() + ".add(\"");
jsc.append(substitutionGroup);
jsc.append("\");");
}
jsc.add("desc.setSubstitutes(substitutionGroups" + member.getName() + ");");
}
}
/**
* Adds additional logic or wrappers around the core handler for special
* types such as dates, enumerated types, collections, etc.
*
* @param member the member for which extra special handler logic may be created
* @param xsType the field type for which extra special handler logic may be created
* @param jsc the java source code to which this will be written
*/
private void addSpecialHandlerLogic(final FieldInfo member, final XSType xsType,
final JSourceCode jsc) {
XMLInfoNature xmlNature = new XMLInfoNature(member);
if (xsType.isEnumerated()) {
jsc.add("handler = new org.exolab.castor.xml.handlers.EnumFieldHandler(");
jsc.append(classType(xsType.getJType()));
jsc.append(", handler);");
jsc.add("desc.setImmutable(true);");
} else if (xsType.getType() == XSType.DATETIME_TYPE) {
jsc.add("handler = new org.exolab.castor.xml.handlers.DateFieldHandler(");
jsc.append("handler);");
jsc.add("desc.setImmutable(true);");
} else if (xsType.getType() == XSType.DECIMAL_TYPE) {
jsc.add("desc.setImmutable(true);");
} else if (xmlNature.getSchemaType().isCollection()) {
//-- Handle special Collection Types such as NMTOKENS and IDREFS
switch (xsType.getType()) {
case XSType.NMTOKEN_TYPE:
case XSType.NMTOKENS_TYPE:
//-- use CollectionFieldHandler
jsc.add("handler = new org.exolab.castor.xml.handlers.CollectionFieldHandler(");
jsc.append("handler, new org.exolab.castor.xml.validators.NameValidator(");
jsc.append("org.exolab.castor.xml.XMLConstants.NAME_TYPE_NMTOKEN));");
break;
case XSType.QNAME_TYPE:
//-- use CollectionFieldHandler
jsc.add("handler = new org.exolab.castor.xml.handlers.CollectionFieldHandler(");
jsc.append("handler, null);");
break;
case XSType.IDREF_TYPE:
case XSType.IDREFS_TYPE:
//-- uses special code in UnmarshalHandler
//-- see UnmarshalHandler#processIDREF
jsc.add("desc.setMultivalued(");
jsc.append("" + xmlNature.isMultivalued());
jsc.append(");");
break;
default:
break;
}
}
} //-- addSpecialHandlerLogic
/**
* Creates the validation code for a given member. This code will be
* appended to the given JSourceCode.
*
* @param member the member for which to create the validation code.
* @param jsc the JSourceCode to fill in.
*/
private static void addValidationCode(final FieldInfo member, final JSourceCode jsc) {
if (member == null || jsc == null) {
return;
}
jsc.add("//-- validation code for: ");
jsc.append(member.getName());
String validator = member.getValidator();
if (validator != null && validator.length() > 0) {
jsc.add("fieldValidator = new " + validator + "();");
} else {
jsc.add("fieldValidator = new org.exolab.castor.xml.FieldValidator();");
//-- a hack, I know, I will change later
if (member.getName().equals("_anyObject")) {
jsc.add("desc.setValidator(fieldValidator);");
return;
}
XMLInfoNature xmlNature = new XMLInfoNature(member);
XSType xsType = xmlNature.getSchemaType();
//--handle collections
if (xsType.isCollection()) {
XSListType xsList = (XSListType) xsType;
jsc.add("fieldValidator.setMinOccurs(");
jsc.append(Integer.toString(xsList.getMinimumSize()));
jsc.append(");");
if (xsList.getMaximumSize() > 0) {
jsc.add("fieldValidator.setMaxOccurs(");
jsc.append(Integer.toString(xsList.getMaximumSize()));
jsc.append(");");
}
} else if (xmlNature.isRequired()) {
jsc.add("fieldValidator.setMinOccurs(1);");
}
jsc.add("{ //-- local scope");
jsc.indent();
xsType.validationCode(jsc, member.getFixedValue(), FIELD_VALIDATOR_NAME);
jsc.unindent();
jsc.add("}");
}
jsc.add("desc.setValidator(fieldValidator);");
}
/**
* Returns the Class type (as a String) for the given XSType.
* @param jType the JType whose Class type will be returned
* @return the Class type (as a String) for the given XSType.
*/
private static String classType(final JType jType) {
if (jType.isPrimitive()) {
JPrimitiveType primitive = (JPrimitiveType) jType;
return primitive.getWrapperName() + ".TYPE";
}
return jType.toString() + ".class";
} //-- classType
} //-- DescriptorSourceFactory