/* * 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 2002 (C) Intalio Inc. All Rights Reserved. * * $Id$ */ package org.exolab.castor.builder.binding; //--Castor imports import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; import org.exolab.castor.builder.binding.xml.AutomaticNamingType; import org.exolab.castor.builder.binding.xml.Binding; import org.exolab.castor.builder.binding.xml.ComponentBindingType; import org.exolab.castor.builder.binding.xml.Exclude; import org.exolab.castor.builder.binding.xml.Excludes; import org.exolab.castor.builder.binding.xml.Forces; import org.exolab.castor.xml.schema.Annotated; import org.exolab.castor.xml.schema.AttributeDecl; import org.exolab.castor.xml.schema.ElementDecl; import org.exolab.castor.xml.schema.Structure; /** * This class adds the necessary logic to a Binding Object to bring the gap * between the XML Schema Object Model and the Binding File. It queries the * Binding Object to retrieve the the associated ComponentBinding. * <p> * An "XPath like" representation of an XML Schema structure is built to lookup * the component bindings in their storage structure. The algorithm used to * build the "XPath like" representation is summarized in the following example: * Given the XML schema declaration: * * <pre> * <xsd:element name="foo"> * <xsd:complextype> * <xsd:attribute name="bar" type="xsd:string"/> * </xsd:complextype> * </xsd:element> * </pre> * * The path to identify the attribute 'bar' will be: * * <pre> * /foo/@bar * </pre> * * The keywords <tt>complexType</tt> and <tt>group</tt> are used to identify * respectively an XML Schema ComplexType and a Model Group <b>definition</b>. * * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a> * @version $Revision$ $Date: 2005-03-05 06:42:06 -0700 (Sat, 05 Mar 2005) $ */ public final class ExtendedBinding extends Binding { /** * Constants needed to create the XPath. */ protected static final String PATH_SEPARATOR = "/"; /** * Prefix used to identify an attribute. */ protected static final String ATTRIBUTE_PREFIX = "@"; /** * Prefix used to identify a complexType. */ public static final String COMPLEXTYPE_ID = "complexType:"; /** * Prefix used to identity a simplyType. */ public static final String SIMPLETYPE_ID = "simpleType:"; /** * Prefix used to identify an enumeration. */ public static final String ENUMTYPE_ID = "enumType:"; /** * Prefix used to identify a model group. */ public static final String GROUP_ID = "group:"; private static final short ATTRIBUTE = 10; private static final short ELEMENT = 11; private static final short COMPLEXTYPE = 12; private static final short GROUP = 13; private static final short ENUM_TYPE = 14; private static final short SIMPLETYPE = 15; /** * The hashtables that contain the different componentBindings. */ private Hashtable<String, ComponentBindingType> _componentBindings; /** * A flag that indicates if the component bindings of that Binding have been * processed. */ private boolean _bindingProcessed = false; /** * Maintains a list of element names where automatic name conflict resolution should be * used all times, incl. the first one. */ private Set<String> _automaticNameResolutionForced = new HashSet<String>(); /** * Maintains a map of exclusions from the automatic name conflict. */ private Map<String, Exclude> _automaticNameResolutionExcludes = new HashMap<String, Exclude>(); /** * Default constructor. * @see java.lang.Object#Object() */ public ExtendedBinding() { super(); _componentBindings = new Hashtable<String, ComponentBindingType>(); } /** * Returns the ComponentBinding that corresponds to the given Annotated XML * Schema structure An Schema location will be built for the given Annotated * XML schema structure. * * @param annotated the XML Schema annotated structure for which to query * the Binding object for a ComponentBinding. * * @return the ComponentBinding that corresponds to the given Annotated XML * Schema structure. */ public ComponentBindingType getComponentBindingType(final Annotated annotated) { if (annotated == null) { return null; } //--no binding can be defined for a GROUP if (annotated.getStructureType() == Structure.GROUP) { return null; } if (!_bindingProcessed) { processBindingComponents(); } String xPath = XPathHelper.getSchemaLocation(annotated); ComponentBindingType result = lookupComponentBindingType(xPath); if (result == null) { //--handle reference switch (annotated.getStructureType()) { case Structure.ELEMENT : //--handle reference: if the element referred a //--global element then we use the global binding if (result == null) { ElementDecl element = (ElementDecl) annotated; if (element.isReference()) { xPath = XPathHelper.getSchemaLocation(element.getReference()); result = lookupComponentBindingType(xPath); } //--discard the element element = null; } break; case Structure.ATTRIBUTE : if (result == null) { //--handle reference: if the element referred a //--global element then we use the global binding AttributeDecl attribute = (AttributeDecl) annotated; if (attribute.isReference()) { xPath = XPathHelper.getSchemaLocation(attribute.getReference()); result = lookupComponentBindingType(xPath); } attribute = null; } break; default : break; } } //--result == null return result; } /** * Returns the ComponentBindingType that corresponds to the given Schema * location XPath. This is a direct lookup in the hashtable, null is * returned if no ComponentBindingType corresponds to the given Schema * Location XPath. * * @param xPath the schema location xpath * @return The ComponentBindingType that correspond to the given Schema * Location XPath, Null is returned when no ComponentBindingType is * found. * @see org.exolab.castor.builder.binding.XPathHelper#getSchemaLocation(Structure) */ private ComponentBindingType lookupComponentBindingType(final String xPath) { if (xPath == null) { return null; } ComponentBindingType componentBinding = _componentBindings.get(xPath); // if no component binding has been found, retry with xpath without namespaces // this is to ensure backwards compatibility if (componentBinding == null) { int occurence = xPath.indexOf('{'); String xPathNoNamespaces = xPath; if (occurence > 0) { while (occurence > 0) { String xPathOld = xPathNoNamespaces; xPathNoNamespaces = xPathOld.substring(0, occurence); int closingOccurence = xPathOld.indexOf('}'); xPathNoNamespaces += xPathOld.substring(closingOccurence + 1); occurence = xPathNoNamespaces.indexOf('{'); } componentBinding = lookupComponentBindingType(xPathNoNamespaces); } } return componentBinding; } /** * Process the top-level Binding Component definitions and their children. * Processing a binding component is a 2-step process: * <ul> * <li>Create a key for the component for direct lookup.</li> * <li>Process its children</li> * </ul> */ private void processBindingComponents() { ComponentBindingType temp; ComponentBindingType[] tempBindings = getAttributeBinding(); //1--attributes for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; //--top-level attribute --> no location computation handleComponent(temp, null, ATTRIBUTE); } //2--complexTypes tempBindings = getComplexTypeBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, null, COMPLEXTYPE); } //3--elements tempBindings = getElementBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, null, ELEMENT); } //4--groups tempBindings = getGroupBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, null, GROUP); } //5--enums tempBindings = getEnumBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, null, ENUM_TYPE); } //6--simpleTypes tempBindings = getSimpleTypeBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, null, SIMPLETYPE); } temp = null; tempBindings = null; _bindingProcessed = true; } /** * Process automatic name conflict resolution section, and memorize definitions. * @param type {@link AutomaticNamingType} instance */ void handleAutomaticNaming(final AutomaticNamingType type) { Forces forcesOuter = type.getForces(); if (forcesOuter != null) { String[] forces = forcesOuter.getForce(); for (int i = 0; i < forces.length; i++) { String elementName = forces[i]; _automaticNameResolutionForced.add(elementName); } } Excludes excludesOuter = type.getExcludes(); if (excludesOuter != null) { Exclude[] excludes = excludesOuter.getExclude(); for (int i = 0; i < excludes.length; i++) { Exclude exclude = excludes[i]; _automaticNameResolutionExcludes.put(exclude.getName(), exclude); } } } /** * Processes the given ComponentBindingType given its type. * * @param binding the ComponentBindingType for which we want to process the * children. * @param xPath the current XPath location that points to the parent of the * given ComponentBindingType. * @param type an integer that indicates the type of the given * ComponentBindingType */ private void handleComponent( final ComponentBindingType binding, final String xPath, final int type) { if (binding == null) { return; } String currentPath = xPath; if (currentPath == null) { currentPath = new String(); } String name = binding.getName(); boolean xpathUsed = (name.indexOf("/") != -1); switch (type) { case ATTRIBUTE : //--handle attributes if (!xpathUsed) { currentPath = currentPath + PATH_SEPARATOR + ATTRIBUTE_PREFIX; } currentPath += name; _componentBindings.put(currentPath, binding); break; case SIMPLETYPE : //--handle simpleType if (!xpathUsed) { currentPath += SIMPLETYPE_ID; } currentPath += name; _componentBindings.put(currentPath, binding); break; case ELEMENT : //--handle element if (!xpathUsed) { currentPath += PATH_SEPARATOR; } currentPath += name; _componentBindings.put(currentPath, binding); break; case COMPLEXTYPE : //--handle complexType if (!xpathUsed) { currentPath += COMPLEXTYPE_ID; } else { if (!name.substring(1, 12).equals("complexType")) { currentPath += PATH_SEPARATOR + COMPLEXTYPE_ID; currentPath += name.substring(1); } else { currentPath += name; } } _componentBindings.put(currentPath, binding); break; case ENUM_TYPE : //--handle enum if (!xpathUsed) { currentPath += ENUMTYPE_ID; } currentPath += name; _componentBindings.put(currentPath, binding); break; case GROUP : //--handle group if (!xpathUsed) { currentPath += GROUP_ID; } currentPath += name; _componentBindings.put(currentPath, binding); break; default : //--there's a problem somewhere throw new IllegalStateException("Invalid ComponentBindingType: the" + " type (attribute, element, complextype or group) is unknown"); } //--process children ComponentBindingType temp; ComponentBindingType[] tempBindings = binding.getAttributeBinding(); //1--attributes for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; //--top-level attribute --> no location computation handleComponent(temp, currentPath, ATTRIBUTE); } //2--complexTypes tempBindings = binding.getComplexTypeBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, currentPath, COMPLEXTYPE); } //X--simpleTypes tempBindings = binding.getSimpleTypeBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, currentPath, SIMPLETYPE); } //3--elements tempBindings = binding.getElementBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, currentPath, ELEMENT); } //4--groups tempBindings = binding.getGroupBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, currentPath, GROUP); } //5--enums tempBindings = binding.getEnumBinding(); for (int i = 0; i < tempBindings.length; i++) { temp = tempBindings[i]; handleComponent(temp, currentPath, ENUM_TYPE); } // temp = null; tempBindings = null; } /** * Indicates whether an <exclude> element has been specified in a binding * file for the given 'local name' of an element definition. * @param localName 'local name' of an element definition * @return True if an <exclude> element has been specified */ public boolean existsExclusion(final String localName) { return _automaticNameResolutionExcludes.containsKey(localName); } /** * Returns the {@link Exclude} instance for the element identified by the given local name. * @param localName Local name for an element (definition). * @return The {@link Exclude} instance. */ public Exclude getExclusion(final String localName) { return _automaticNameResolutionExcludes.get(localName); } /** * Indicates whether an <force> element has been specified in a binding * file for the given 'local name' of an element definition. * @param localName 'local name' of an element definition * @return True if an <force> element has been specified */ public boolean existsForce(final String localName) { return _automaticNameResolutionForced.contains(localName); } /** * Returns all <force> elements defined in the binding file. * @return all <force> elements defined in the binding file */ public Set<String> getForces() { return _automaticNameResolutionForced; } }