/* Copyright (c) 2003-2008, Dennis M. Sosnoski All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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. */ package org.jibx.binding.def; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import org.apache.bcel.Constants; import org.apache.bcel.classfile.Utility; import org.jibx.binding.classes.BoundClass; import org.jibx.binding.classes.BranchTarget; import org.jibx.binding.classes.BranchWrapper; import org.jibx.binding.classes.ClassCache; import org.jibx.binding.classes.ClassFile; import org.jibx.binding.classes.ClassItem; import org.jibx.binding.classes.ExceptionMethodBuilder; import org.jibx.binding.classes.MethodBuilder; import org.jibx.runtime.JiBXException; import org.jibx.runtime.QName; import org.jibx.runtime.impl.UnmarshallingContext; /** * Binding definition builder. This processes the binding definition file to * generate the code generation structure. * * @author Dennis M. Sosnoski * @version 1.0 */ public abstract class BindingBuilder { /** Element namespace used for binding definition file. */ private static final String URI_ELEMENTS = null; /** Attribute namespace used for binding definition file. */ private static final String URI_ATTRIBUTES = null; /* Common style attribute. */ private static final String COMMON_STYLE = "value-style"; /* Common linkage attributes. */ private static final String COMMON_AUTOLINK = "auto-link"; private static final String COMMON_ACCESSLEVEL = "access-level"; private static final String COMMON_STRIPPREFIX = "strip-prefix"; private static final String COMMON_STRIPSUFFIX = "strip-suffix"; private static final String COMMON_NAMESTYLE = "name-style"; /* Common name attributes. */ private static final String COMMON_NAME = "name"; private static final String COMMON_NAMESPACE = "ns"; /* Common object attributes. */ private static final String COMMON_FACTORY = "factory"; private static final String COMMON_PRESET = "pre-set"; private static final String COMMON_POSTSET = "post-set"; private static final String COMMON_PREGET = "pre-get"; private static final String COMMON_MARSHALLER = "marshaller"; private static final String COMMON_UNMARSHALLER = "unmarshaller"; private static final String COMMON_CREATETYPE = "create-type"; /* Common property attributes. */ private static final String COMMON_FIELD = "field"; private static final String COMMON_TYPE = "type"; private static final String COMMON_USAGE = "usage"; private static final String COMMON_TESTMETHOD = "test-method"; private static final String COMMON_GETMETHOD = "get-method"; private static final String COMMON_SETMETHOD = "set-method"; /* Common string attributes. */ private static final String COMMON_DEFAULT = "default"; private static final String COMMON_SERIALIZER = "serializer"; private static final String COMMON_DESERIALIZER = "deserializer"; private static final String COMMON_ENUMVALUEMETHOD = "enum-value-method"; /* Common label attributes. */ private static final String COMMON_LABEL = "label"; private static final String COMMON_USING = "using"; /* Common ordered and choice attributes. */ private static final String COMMON_ORDERED = "ordered"; private static final String COMMON_CHOICE = "choice"; private static final String COMMON_FLEXIBLE = "flexible"; private static final String COMMON_DUPLICATES = "allow-repeats"; /* Common nillable attribute. */ private static final String COMMON_NILLABLE = "nillable"; /** Definitions for "binding" element use "BINDING" prefix. */ private static final String BINDING_ELEMENT = "binding"; private static final String BINDING_NAME = "name"; private static final String BINDING_DIRECTION = "direction"; private static final String BINDING_GLOBALID = "global-id"; private static final String BINDING_FORWARDS = "forwards"; private static final String BINDING_PACKAGE = "package"; private static final String BINDING_TRACKING = "track-source"; private static final String BINDING_FORCE = "force-classes"; // also COMMON_STYLE, and linkage group /** Definitions for "namespace" element use "NAMESPACE" prefix. */ private static final String NAMESPACE_ELEMENT = "namespace"; private static final String NAMESPACE_URI = "uri"; private static final String NAMESPACE_PREFIX = "prefix"; private static final String NAMESPACE_DEFAULT = "default"; /** Definitions for "format" element use "FORMAT" prefix. */ private static final String FORMAT_ELEMENT = "format"; private static final String FORMAT_NAME = "label"; private static final String FORMAT_TYPE = "type"; // also string group /** Definitions for "mapping" element use "MAPPING" prefix. */ private static final String MAPPING_ELEMENT = "mapping"; private static final String MAPPING_CLASS = "class"; private static final String MAPPING_ABSTRACT = "abstract"; private static final String MAPPING_EXTENDS = "extends"; private static final String MAPPING_TYPENAME = "type-name"; // also COMMON_STYLE, name, object, ordered, and linkage groups /** Definitions for "value" element use "VALUE" prefix. */ private static final String VALUE_ELEMENT = "value"; private static final String VALUE_STYLE = "style"; private static final String VALUE_FORMAT = "format"; private static final String VALUE_CONSTANT = "constant"; private static final String VALUE_IDENT = "ident"; // also name, property, and string groups /** Definitions for "structure" element use "STRUCTURE" prefix. */ private static final String STRUCTURE_ELEMENT = "structure"; private static final String STRUCTURE_MAPAS = "map-as"; // also COMMON_STYLE, name, object, ordered, property, and label groups /** Definitions for "collection" element use "COLLECTION" prefix. */ private static final String COLLECTION_ELEMENT = "collection"; private static final String COLLECTION_LOADMETHOD = "load-method"; private static final String COLLECTION_SIZEMETHOD = "size-method"; private static final String COLLECTION_STOREMETHOD = "store-method"; private static final String COLLECTION_ADDMETHOD = "add-method"; private static final String COLLECTION_ITERMETHOD = "iter-method"; private static final String COLLECTION_ITEMTYPE = "item-type"; // also COMMON_STYLE, name, ordered, property, and label groups /** Definitions for "include" element use "INCLUDE" prefix. */ private static final String INCLUDE_ELEMENT = "include"; private static final String INCLUDE_PATH = "path"; // // Value style enumeration. private static final String[] VALUE_STYLE_NAMES = { "attribute", "cdata", "element", "text" }; private static final int[] VALUE_STYLE_NUMS = { ValueChild.ATTRIBUTE_STYLE, ValueChild.CDATA_STYLE, ValueChild.ELEMENT_STYLE, ValueChild.TEXT_STYLE }; private static final String[] CONTAINING_STYLE_NAMES = { "attribute", "element" }; private static final int[] CONTAINING_STYLE_NUMS = { ValueChild.ATTRIBUTE_STYLE, ValueChild.ELEMENT_STYLE }; // // Enumeration for auto-link types. /*package*/ static final int LINK_NONE = 0; /*package*/ static final int LINK_FIELDS = 1; /*package*/ static final int LINK_METHODS = 2; private static final String[] AUTO_LINK_NAMES = { "fields", "none", "methods" }; private static final int[] AUTO_LINK_NUMS = { LINK_FIELDS, LINK_NONE, LINK_METHODS }; // // Enumeration for access level. /*package*/ static final int ACC_PRIVATE = 0; /*package*/ static final int ACC_PACKAGE = 1; /*package*/ static final int ACC_PROTECTED = 2; /*package*/ static final int ACC_PUBLIC = 3; private static final String[] ACCESS_LEVEL_NAMES = { "package", "private", "protected", "public" }; private static final int[] ACCESS_LEVEL_NUMS = { ACC_PACKAGE, ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC }; // // Enumeration for name generation styles. /*package*/ static final int NAME_HYPHENS = 0; /*package*/ static final int NAME_MIXED = 1; private static final String[] NAME_GENERATE_NAMES = { "hyphens", "mixed-case" }; private static final int[] NAME_GENERATE_NUMS = { NAME_HYPHENS, NAME_MIXED }; // // Attributes that imply a component object private static final String[] COMPONENT_OBJECT_NAMESPACES = { URI_ATTRIBUTES, URI_ATTRIBUTES, URI_ATTRIBUTES, URI_ATTRIBUTES }; private static final String[] COMPONENT_OBJECT_NAMES = { COMMON_FACTORY, COMMON_PRESET, COMMON_POSTSET, COMMON_PREGET }; // // Enumeration for namespace usage. private static final String[] NAMESPACEACCESS_NAMES = { "all", "attributes", "elements", "none" }; private static final int[] NAMESPACEACCESS_NUMS = { NamespaceDefinition.ALLDEFAULT_USAGE, NamespaceDefinition.ATTRIBUTES_USAGE, NamespaceDefinition.ELEMENTS_USAGE, NamespaceDefinition.NODEFAULT_USAGE }; // // Ident type enumeration. private static final String[] IDENTTYPE_NAMES = { "auto", "def", "direct", "ref" }; private static final int[] IDENTTYPE_NUMS = { ValueChild.AUTO_IDENT, ValueChild.DEF_IDENT, ValueChild.DIRECT_IDENT, ValueChild.REF_IDENT }; // // Binding direction enumeration. private static final int DIRECTION_INPUT = 0; private static final int DIRECTION_OUTPUT = 1; private static final int DIRECTION_BOTH = 2; private static final String[] BINDINGDIR_NAMES = { "both", "input", "output" }; private static final int[] BINDINGDIR_NUMS = { DIRECTION_BOTH, DIRECTION_INPUT, DIRECTION_OUTPUT }; // // Constants for property usage values private static final String USAGE_OPTIONAL = "optional"; private static final String USAGE_REQUIRED = "required"; // // Checking and code generation constants private static final String UNMARSHALLER_INTERFACE = "org.jibx.runtime.IUnmarshaller"; private static final String MARSHALLER_INTERFACE = "org.jibx.runtime.IMarshaller"; private static final String UNMARSHALLER_INTERFACETYPE = "Lorg/jibx/runtime/IUnmarshaller;"; private static final String MARSHALLER_INTERFACETYPE = "Lorg/jibx/runtime/IMarshaller;"; private static final String CUSTOM_ENUM_SERIALIZER_NAME = "_jibx_serialize"; private static final String CUSTOM_ENUM_DESERIALIZER_NAME = "_jibx_deserialize"; /** * Check if attributes supply a name definition. * * @param ctx unmarshalling context information * @return true if attributes define a name, * false if not */ private static boolean isNamePresent(UnmarshallingContext ctx) { return ctx.attributeText(URI_ATTRIBUTES, COMMON_NAME, null) != null; } /** * Check for property definition present. Just checks the attributes of * the current element. * * @param ctx unmarshalling context information */ private static boolean isPropertyPresent(UnmarshallingContext ctx) { return ctx.attributeText(URI_ATTRIBUTES, COMMON_FIELD, null) != null || ctx.attributeText(URI_ATTRIBUTES, COMMON_GETMETHOD, null) != null || ctx.attributeText(URI_ATTRIBUTES, COMMON_SETMETHOD, null) != null || ctx.attributeText(URI_ATTRIBUTES, COMMON_TESTMETHOD, null) != null; } /** * Check if attributes define a direct object reference. Just checks the * attributes of the current element. * * @param ctx unmarshalling context information */ private static boolean isDirectObject(UnmarshallingContext ctx) { return ctx.attributeText(URI_ATTRIBUTES, COMMON_MARSHALLER, null) != null || ctx.attributeText(URI_ATTRIBUTES, COMMON_UNMARSHALLER, null) != null; } /** * Check if attributes define a mapping reference. * * @param ctx unmarshalling context information * @return true if attributes define a mapping reference, * false if not * @throws JiBXException if error in unmarshalling */ private static boolean isMappingRef(UnmarshallingContext ctx) throws JiBXException { return ctx.hasAttribute(URI_ATTRIBUTES, STRUCTURE_MAPAS); } /** * Check for component object present. Just checks the attributes of the * current element, so this is not definitive - there may still be child * binding definitions even without attributes. * * @param ctx unmarshalling context information * @throws JiBXException if error in unmarshalling */ private static boolean isObjectBinding(UnmarshallingContext ctx) throws JiBXException { return ctx.hasAnyAttribute(COMPONENT_OBJECT_NAMESPACES, COMPONENT_OBJECT_NAMES); } /** * Unmarshal name definition. This unmarshals directly from attributes of * the current element. * * @param ctx unmarshalling context information * @param attr flag for attribute name definition * @throws JiBXException if error in unmarshalling */ private static NameDefinition unmarshalName(UnmarshallingContext ctx, boolean attr) throws JiBXException { String name = ctx.attributeText(URI_ATTRIBUTES, COMMON_NAME); String ns = ctx.attributeText(URI_ATTRIBUTES, COMMON_NAMESPACE, null); return new NameDefinition(name, ns, attr); } /** * Unmarshal namespace definition. * * @param ctx unmarshalling context information * @throws JiBXException if error in unmarshalling */ private static NamespaceDefinition unmarshalNamespace (UnmarshallingContext ctx) throws JiBXException { // set up the basic information String uri = ctx.attributeText(URI_ATTRIBUTES, NAMESPACE_URI); String prefix = ctx.attributeText(URI_ATTRIBUTES, NAMESPACE_PREFIX, null); if ("".equals(prefix)) { prefix = null; } // check default usage attribute int usage = ctx.attributeEnumeration(URI_ATTRIBUTES, NAMESPACE_DEFAULT, NAMESPACEACCESS_NAMES, NAMESPACEACCESS_NUMS, NamespaceDefinition.NODEFAULT_USAGE); // finish parsing the element ctx.parsePastEndTag(URI_ELEMENTS, NAMESPACE_ELEMENT); return new NamespaceDefinition(uri, prefix, usage); } /** * Add serializer and deserializer methods to enum class with special value * method. This allows enums to be used even when the values present in XML * are not valid Java names. * * @param type * @param evmeth * @throws JiBXException */ private static void buildEnumValueMethods(String type, String evmeth) throws JiBXException { // set up for adding serialize/deserialize methods to enum class BoundClass bndclas = BoundClass.getInstance(type, null); String typesig = bndclas.getClassFile().getSignature(); ClassFile cf = bndclas.getMungedFile(); String evfull = type + '.' + evmeth; // build the serializer method (just delegates to value method) String sersig = "(" + typesig + ")Ljava/lang/String;"; ExceptionMethodBuilder smeth = new ExceptionMethodBuilder (CUSTOM_ENUM_SERIALIZER_NAME, sersig, cf, Constants.ACC_PUBLIC | Constants.ACC_STATIC); smeth.appendLoadLocal(0); BranchWrapper nonnull = smeth.appendIFNONNULL(smeth); smeth.appendACONST_NULL(); smeth.appendReturn("java.lang.String"); smeth.targetNext(nonnull); smeth.appendLoadLocal(0); smeth.appendCallVirtual(evfull, "()Ljava/lang/String;"); smeth.appendReturn("java.lang.String"); bndclas.getUniqueNamed(smeth); // create the deserializer method String dsersig = "(Ljava/lang/String;)" + typesig; ExceptionMethodBuilder dmeth = new ExceptionMethodBuilder (CUSTOM_ENUM_DESERIALIZER_NAME, dsersig, cf, Constants.ACC_PUBLIC | Constants.ACC_STATIC); dmeth.addException(MethodBuilder.FRAMEWORK_EXCEPTION_CLASS); // start by handling the null string case dmeth.appendLoadLocal(0); nonnull = dmeth.appendIFNONNULL(dmeth); dmeth.appendACONST_NULL(); dmeth.appendReturn(type); dmeth.targetNext(nonnull); // set up locals for array of values and decrementing index dmeth.appendCallStatic(type + ".values", "()[" + typesig); dmeth.appendDUP(); int arraylocal = dmeth.addLocal("values", ClassItem.typeFromName(type + "[]")); dmeth.appendARRAYLENGTH(); int arrayindex = dmeth.addLocal("index", ClassItem.typeFromName("int")); // start comparison loop with check for off bottom of array BranchTarget start = dmeth.appendTargetNOP(); dmeth.appendIncrementLocal(-1, arrayindex); dmeth.appendLoadLocal(arrayindex); BranchWrapper loadnext = dmeth.appendIFGE(dmeth); // throw an exception for value not found dmeth.appendCreateNew("java.lang.StringBuffer"); dmeth.appendDUP(); dmeth.appendLoadConstant("No match found for value '"); dmeth.appendCallInit("java.lang.StringBuffer", "(Ljava/lang/String;)V"); dmeth.appendLoadLocal(0); dmeth.appendCallVirtual("java.lang.StringBuffer.append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;"); dmeth.appendLoadConstant("' in enum class " + type); dmeth.appendCallVirtual("java.lang.StringBuffer.append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;"); dmeth.appendCallVirtual("java.lang.StringBuffer.toString", "()Ljava/lang/String;"); dmeth.appendCreateNew(MethodBuilder.FRAMEWORK_EXCEPTION_CLASS); dmeth.appendDUP_X1(); dmeth.appendSWAP(); dmeth.appendCallInit(MethodBuilder.FRAMEWORK_EXCEPTION_CLASS, MethodBuilder.EXCEPTION_CONSTRUCTOR_SIGNATURE1); dmeth.appendThrow(); // load and compare the value dmeth.targetNext(loadnext); dmeth.appendLoadLocal(0); dmeth.appendLoadLocal(arraylocal); dmeth.appendLoadLocal(arrayindex); dmeth.appendALOAD(type); dmeth.appendCallVirtual(evfull, "()Ljava/lang/String;"); dmeth.appendCallVirtual("java.lang.Object.equals", "(Ljava/lang/Object;)Z"); BranchWrapper tonext = dmeth.appendIFEQ(dmeth); tonext.setTarget(start, dmeth); // return the matching instance dmeth.appendLoadLocal(arraylocal); dmeth.appendLoadLocal(arrayindex); dmeth.appendALOAD(type); dmeth.appendReturn(type); // add completed method to class bndclas.getUniqueNamed(dmeth); } /** * Unmarshal string conversion. Unmarshals conversion information directly * from the attributes of the current start tag. * * @param ctx unmarshalling context information * @param base conversion used as base for this conversion * @param type fully qualified class name of type handled by conversion * @throws JiBXException if error in unmarshalling */ private static StringConversion unmarshalStringConversion (UnmarshallingContext ctx, StringConversion base, String type) throws JiBXException { String dflt = ctx.attributeText(URI_ATTRIBUTES, COMMON_DEFAULT, null); String ser = ctx.attributeText(URI_ATTRIBUTES, COMMON_SERIALIZER, null); String dser = ctx.attributeText(URI_ATTRIBUTES, COMMON_DESERIALIZER, null); String evmeth = ctx.attributeText(URI_ATTRIBUTES, COMMON_ENUMVALUEMETHOD, null); if (evmeth != null) { ser = type + '.' + CUSTOM_ENUM_SERIALIZER_NAME; dser = type + '.' + CUSTOM_ENUM_DESERIALIZER_NAME; buildEnumValueMethods(type, evmeth); } return base.derive(type, ser, dser, dflt); } /** * Check for optional property. Just checks for the attribute and makes sure * it has a valid value if present, returning either the default or the * defined value. * * @param ctx unmarshalling context information * @return true if attribute present with value "true", * false otherwise * @throws JiBXException if error in unmarshalling */ private static boolean isOptionalProperty(UnmarshallingContext ctx) throws JiBXException { boolean opt = false; String value = ctx.attributeText(URI_ATTRIBUTES, COMMON_USAGE, USAGE_REQUIRED); if (USAGE_OPTIONAL.equals(value)) { opt = true; } else if (!USAGE_REQUIRED.equals(value)) { ctx.throwStartTagException("Illegal value for \"" + COMMON_USAGE+ "\" attribute"); } return opt; } /** * Unmarshal property definition. This unmarshals directly from attributes * of the current element. * * @param ctx unmarshalling context information * @param parent containing binding definition structure * @param cobj context object information * @param opt force optional value flag * @throws JiBXException if error in unmarshalling */ private static PropertyDefinition unmarshalProperty (UnmarshallingContext ctx, IContainer parent, IContextObj cobj, boolean opt) throws JiBXException { // read basic attribute values for property definition String type = ctx.attributeText(URI_ATTRIBUTES, COMMON_TYPE, null); if (!(parent instanceof NestedCollection) && isOptionalProperty(ctx)) { opt = true; } // read and validate method and/or field attribute values PropertyDefinition pdef = null; try { String fname = ctx.attributeText(URI_ATTRIBUTES, COMMON_FIELD, null); String test = ctx.attributeText(URI_ATTRIBUTES, COMMON_TESTMETHOD, null); String get = ctx.attributeText(URI_ATTRIBUTES, COMMON_GETMETHOD, null); String set = ctx.attributeText(URI_ATTRIBUTES, COMMON_SETMETHOD, null); boolean isthis = fname == null && get == null && set == null; pdef = new PropertyDefinition (parent, cobj, type, isthis, opt, fname, test, get, set); } catch (JiBXException ex) { // rethrow error message with position information ctx.throwStartTagException(ex.getMessage()); } return pdef; } /** * Unmarshal value definition. This handles the complete element supplying * the value binding. * * @param ctx unmarshalling context information * @param parent containing binding definition structure * @param cobj context object information * @param uord unordered collection member flag * @param impl implicit value from collection flag * @param itype base type for value * @throws JiBXException if error in unmarshalling */ private static ValueChild unmarshalValue(UnmarshallingContext ctx, IContainer parent, IContextObj cobj, boolean uord, boolean impl, String itype) throws JiBXException { // find the style for this value int style = ctx.attributeEnumeration(URI_ATTRIBUTES, VALUE_STYLE, VALUE_STYLE_NAMES, VALUE_STYLE_NUMS, parent.getStyleDefault()); // set up the basic structures boolean isatt = style == ValueChild.ATTRIBUTE_STYLE; NameDefinition name = null; if (isatt || style == ValueChild.ELEMENT_STYLE) { name = unmarshalName(ctx, isatt); name.fixNamespace(parent.getDefinitionContext()); } else if (isNamePresent(ctx)) { ctx.throwStartTagException ("Name not allowed for text or CDATA value"); } String constant = ctx.attributeText(URI_ATTRIBUTES, VALUE_CONSTANT, null); // handle property information (unless auto ident or implicit value) int ident = ctx.attributeEnumeration(URI_ATTRIBUTES, VALUE_IDENT, IDENTTYPE_NAMES, IDENTTYPE_NUMS, ValueChild.DIRECT_IDENT); PropertyDefinition prop = null; if (ident == ValueChild.AUTO_IDENT) { ctx.throwStartTagException ("Automatic id generation not yet supported"); } else if (impl) { String type = ctx.attributeText(URI_ATTRIBUTES, COMMON_TYPE, itype); prop = new PropertyDefinition(type, cobj, !(parent instanceof NestedCollection) && isOptionalProperty(ctx)); } else { prop = unmarshalProperty(ctx, parent, cobj, ctx.hasAttribute(URI_ATTRIBUTES, COMMON_DEFAULT)); /* if (constant == null && prop.isThis()) { ctx.throwStartTagException("No property for value"); } else if (prop.isOptional()) { if (ident == ValueChild.DEF_IDENT) { ctx.throwStartTagException("Object ID cannot be optional"); } } else if (uord) { ctx.throwStartTagException("All items in unordered " + "structure must be optional"); } */ } if (ident != ValueChild.DIRECT_IDENT && uord) { ctx.throwStartTagException(VALUE_IDENT + " not allowed in unordered structure"); } // get serializer/deserializer attributes now to allow enum override String ser = ctx.attributeText(URI_ATTRIBUTES, COMMON_SERIALIZER, null); String dser = ctx.attributeText(URI_ATTRIBUTES, COMMON_DESERIALIZER, null); // check for enum with value method String type = (prop == null || constant != null) ? "java.lang.String" : prop.getTypeName(); String evmeth = ctx.attributeText(URI_ATTRIBUTES, COMMON_ENUMVALUEMETHOD, null); boolean basicenum = false; ClassFile target = ClassCache.getClassFile(type); if (evmeth != null) { // handle enum using generated serialize and deserialize methods ser = type + '.' + CUSTOM_ENUM_SERIALIZER_NAME; dser = type + '.' + CUSTOM_ENUM_DESERIALIZER_NAME; buildEnumValueMethods(type, evmeth); } else { // check for a Java 5 enumeration ClassFile sclas = target; while ((sclas = sclas.getSuperFile()) != null) { if (sclas.getName().equals("java.lang.Enum")) { basicenum = true; break; } } } // find the basic converter to use StringConversion convert = null; String format = ctx.attributeText(URI_ATTRIBUTES, VALUE_FORMAT, null); DefinitionContext defc = parent.getDefinitionContext(); if (format == null) { // no format name, get specific convertor for type or best match convert = defc.getSpecificConversion(type); if (convert == null) { // find best match converter for type convert = defc.getConversion(target); // generate specific converter based on general form if (basicenum) { String basedeser = type + '.' + "valueOf"; convert = convert.derive(type, null, basedeser, null); } else if (convert.getTypeName().equals("java.lang.Object")) { convert = new ObjectStringConversion(type, (ObjectStringConversion)convert); } } } else { // format name supplied, look it up and check compatibility QName qname = QName.deserialize(format, ctx); convert = defc.getNamedConversion(qname); if (convert == null) { ctx.throwStartTagException ("Unknown format \"" + format + "\""); } String ctype = convert.getTypeName(); if (!ClassItem.isAssignable(type, ctype) && !ClassItem.isAssignable(ctype, type)) { ctx.throwStartTagException ("Converter type not compatible with value type"); } } // check for any special conversion handling String dflt = ctx.attributeText(URI_ATTRIBUTES, COMMON_DEFAULT, null); if (dflt != null || ser != null || dser != null) { convert = convert.derive(type, ser, dser, dflt); } // create the instance to be returned boolean nillable = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_NILLABLE, false); if (nillable) { parent.getBindingRoot().setSchemaInstanceUsed(); } ValueChild value = new ValueChild(parent, cobj, name, prop, convert, style, ident, constant, nillable); // handle identifier property flag if (ident == ValueChild.DEF_IDENT || ident == ValueChild.AUTO_IDENT) { if (!cobj.setIdChild(value)) { ctx.throwStartTagException ("Duplicate ID definition for containing mapping"); } else if (!"java.lang.String".equals(type)) { ctx.throwStartTagException ("ID property must be a String"); } } // finish with skipping past end tag ctx.parsePastEndTag(URI_ELEMENTS, VALUE_ELEMENT); return value; } /** * Unmarshal direct object component. Just constructs the component to * be returned along with the supporting objects, and verifies that no * disallowed properties are present. * * @param ctx unmarshalling context information * @param type fully qualified class name of object type handled * @param parent containing binding definition structure * @param defc definition context to be used (if separate from parent, * otherwise null) * @param slot marshaller/unmarshaller slot number * @param name element name information (null if no element * name) * @return constructed direct object component * @throws JiBXException if error in unmarshalling */ private static DirectObject unmarshalDirectObj(UnmarshallingContext ctx, String type, IContainer parent, DefinitionContext defc, int slot, NameDefinition name) throws JiBXException { // define and validate marshaller ClassFile mcf = null; if (parent.getBindingRoot().isOutput()) { String clas = ctx.attributeText(URI_ATTRIBUTES, COMMON_MARSHALLER); mcf = ClassCache.getClassFile(clas); if (!mcf.isImplements(MARSHALLER_INTERFACETYPE)) { ctx.throwStartTagException("Marshaller class " + clas + " does not implement required interface " + MARSHALLER_INTERFACE); } } // define and validate unmarshaller ClassFile ucf = null; if (parent.getBindingRoot().isInput()) { String clas = ctx.attributeText(URI_ATTRIBUTES, COMMON_UNMARSHALLER); ucf = ClassCache.getClassFile(clas); if (!ucf.isImplements(UNMARSHALLER_INTERFACETYPE)) { ctx.throwStartTagException("Unmarshaller class " + clas + " does not implement required interface " + UNMARSHALLER_INTERFACE); } } // make sure none of the prohibited attributes are present if (isObjectBinding(ctx)) { ctx.throwStartTagException("Other object attributes not " + "allowed when using marshaller or unmarshaller"); } else if (isMappingRef(ctx)) { ctx.throwStartTagException("Mapping not allowed when " + "using marshaller or unmarshaller"); } else if (ctx.hasAttribute(URI_ATTRIBUTES, COMMON_USING)) { ctx.throwStartTagException(COMMON_USING + " attribute not allowed when using marshaller or unmarshaller"); } // return constructed instance return new DirectObject(parent, defc, ClassCache.getClassFile(type), false, mcf, ucf, slot, name); } /** * Unmarshal mapping reference component. Just constructs the component to * be returned along with the supporting objects, and verifies that no * disallowed properties are present. * * @param ctx unmarshalling context information * @param parent containing binding definition structure * @param objc current object context * @param prop property definition * @param name reference name definition (only allowed with abstract * mappings) * @return constructed mapping reference component * @throws JiBXException if error in unmarshalling */ private static IComponent unmarshalMappingRef(UnmarshallingContext ctx, IContainer parent, IContextObj objc, PropertyDefinition prop, NameDefinition name) throws JiBXException { // make sure no forbidden attributes are present if (isObjectBinding(ctx)) { ctx.throwStartTagException("Other object attributes not " + "allowed when using mapping reference"); } else if (ctx.hasAttribute(URI_ATTRIBUTES, COMMON_USING)) { ctx.throwStartTagException(COMMON_USING + " attribute not allowed when using mapping reference"); } // build the actual component to be returned String type = (prop == null) ? null : prop.getTypeName(); String text = ctx.attributeText(URI_ATTRIBUTES, STRUCTURE_MAPAS, type); QName qname = QName.deserialize(text, ctx); boolean nillable = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_NILLABLE, false); if (nillable) { parent.getBindingRoot().setSchemaInstanceUsed(); } return new MappingReference(parent, prop, type, text, qname.toString(), objc, name, false, nillable); } /** * Unmarshal structure reference component. Just constructs the component to * be returned along with the supporting objects, and verifies that no * disallowed properties are present. * * @param ctx unmarshalling context information * @param contain containing binding component * @param name element name information (null if no element * name) * @param prop property definition (null if no separate * property) * @param cobj context object * @return constructed structure reference component * @throws JiBXException if error in unmarshalling */ private static IComponent unmarshalStructureRef(UnmarshallingContext ctx, IContainer contain, NameDefinition name, PropertyDefinition prop, IContextObj cobj) throws JiBXException { // make sure no forbidden attributes are present if (isObjectBinding(ctx)) { ctx.throwStartTagException("Other object attributes not " + "allowed when using structure reference"); } // build the actual component to be returned String ident = ctx.attributeText(URI_ATTRIBUTES, COMMON_USING); IComponent comp = new StructureReference(contain, ident, prop, name != null, cobj); if (name != null) { boolean nillable = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_NILLABLE, false); if (nillable) { contain.getBindingRoot().setSchemaInstanceUsed(); } comp = new ElementWrapper(contain.getDefinitionContext(), name, comp, nillable); if (prop != null && prop.isOptional()) { ((ElementWrapper)comp).setOptionalNormal(true); ((ElementWrapper)comp).setStructureObject(true); comp = new OptionalStructureWrapper(comp, prop, true); prop.setOptional(false); } } return comp; } /** * Unmarshal child bindings for a nested structure definition. * * @param ctx unmarshalling context information * @param nest nested structure definition * @param objc context object definition * @param impl property value implicit flag * @param itype item type for child components * @throws JiBXException if error in unmarshalling */ private static void unmarshalStructureChildren(UnmarshallingContext ctx, NestedBase nest, IContextObj objc, boolean impl, String itype) throws JiBXException { boolean uord = !nest.isContentOrdered(); while (true) { // unmarshal next child binding definition IComponent comp; if (ctx.isAt(URI_ELEMENTS, VALUE_ELEMENT)) { ValueChild child = unmarshalValue(ctx, nest, objc, uord, impl, itype); comp = child; } else if (ctx.isAt(URI_ELEMENTS, STRUCTURE_ELEMENT)) { comp = unmarshalStructure(ctx, nest, objc, false, uord, impl); } else if (ctx.isAt(URI_ELEMENTS, COLLECTION_ELEMENT)) { comp = unmarshalStructure(ctx, nest, objc, true, uord, impl); } else { break; } // add component to structure nest.addComponent(comp); } } /** * Unmarshal object binding component. Just constructs the component to * be returned along with the supporting objects. This handles both the * unmarshalling of attributes, and of nested binding components. * * @param ctx unmarshalling context information * @param parent containing binding definition structure * @param objc current object context * @param type fully qualified name of object class * @return constructed structure reference component * @throws JiBXException if error in unmarshalling */ private static ObjectBinding unmarshalObjectBinding (UnmarshallingContext ctx, IContextObj objc, IContainer parent, String type) throws JiBXException { // set method names from attributes of start tag String fact = ctx.attributeText(URI_ATTRIBUTES, COMMON_FACTORY, null); String pres = ctx.attributeText(URI_ATTRIBUTES, COMMON_PRESET, null); String posts = ctx.attributeText(URI_ATTRIBUTES, COMMON_POSTSET, null); String preg = ctx.attributeText(URI_ATTRIBUTES, COMMON_PREGET, null); String ctype = ctx.attributeText(URI_ATTRIBUTES, COMMON_CREATETYPE, null); ObjectBinding bind = null; try { bind = new ObjectBinding(parent, objc, type, fact, pres, posts, preg, ctype); } catch (JiBXException ex) { ctx.throwStartTagException(ex.getMessage(), ex); } return bind; } /** * Unmarshal namespace definitions. Any namespace definitions present are * unmarshalled and added to the supplied definition context. * * @param ctx unmarshalling context information * @param defc definition context for defined namespaces * @throws JiBXException if error in unmarshalling */ private static void unmarshalNamespaces(UnmarshallingContext ctx, DefinitionContext defc) throws JiBXException { while (ctx.isAt(URI_ELEMENTS, NAMESPACE_ELEMENT)) { defc.addNamespace(unmarshalNamespace(ctx)); } } /** * Unmarshal format definitions. Any format definitions present are * unmarshalled and added to the supplied definition context. * * @param ctx unmarshalling context information * @param defc definition context for defined formats * @throws JiBXException if error in unmarshalling */ private static void unmarshalFormats(UnmarshallingContext ctx, DefinitionContext defc) throws JiBXException { // process all format definitions at level while (ctx.isAt(URI_ELEMENTS, FORMAT_ELEMENT)) { // find the current default format information for type String type = ctx.attributeText(URI_ATTRIBUTES, FORMAT_TYPE); String sig = Utility.getSignature(type); StringConversion base = null; if (sig.length() == 1) { // must be a primitive, check type directly base = defc.getSpecificConversion(type); if (base == null) { ctx.throwStartTagException("Unsupported \"" + FORMAT_TYPE + "\" value"); } } else { // must be an object type, find best match ClassFile cf = ClassCache.getClassFile(type); base = defc.getConversion(cf); } // unmarshal with defaults provided by existing format StringConversion format = unmarshalStringConversion(ctx, base, type); // handle based on presence or absence of name attribute String text = ctx.attributeText(URI_ATTRIBUTES, FORMAT_NAME, null); if (text == null) { defc.setConversion(format); } else { QName qname = QName.deserialize(text, ctx); defc.setNamedConversion(qname, format); } // scan past end of definition ctx.parsePastEndTag(URI_ELEMENTS, FORMAT_ELEMENT); } } /** * Unmarshal mapping definitions. Any mapping definitions present are * unmarshalled and added to the supplied definition context. * * @param ctx unmarshalling context information * @param parent containing binding definition structure * @param nss extra namespaces to be included in this mapping definition * (may be null) * @param uord container is unordered structure flag * @throws JiBXException if error in unmarshalling */ private static void unmarshalMappings(UnmarshallingContext ctx, IContainer parent, ArrayList nss, boolean uord) throws JiBXException { while (ctx.isAt(URI_ELEMENTS, MAPPING_ELEMENT)) { unmarshalMapping(ctx, parent, nss, uord); } } /** * Unmarshal subclass instance for structure definition. This handles all * combinations of attributes on the start tag, generating the appropriate * structure of nested components and other classes to represent the binding * information within the current element. This must be called with the * parse positioned at the start tag of the element to be unmarshalled. * * TODO: At least split this up, or organize a better way to build binding * * @param ctx unmarshalling context information * @param contain containing binding definition structure * @param cobj context object information * @param coll collection structure flag * @param uord container is unordered structure flag * @param implic property value implicit flag * @return root of component tree constructed from binding * @throws JiBXException if error in unmarshalling */ public static IComponent unmarshalStructure(UnmarshallingContext ctx, IContainer contain, IContextObj cobj, boolean coll, boolean uord, boolean implic) throws JiBXException { // get name definition if supplied (check later to see if valid) NameDefinition name = null; if (isNamePresent(ctx)) { name = unmarshalName(ctx, false); } // check for optional flag on structure boolean opt = isOptionalProperty(ctx); boolean incoll = false; if (contain instanceof NestedCollection) { incoll = true; opt = false; } // check for property definition supplied IComponent comp; boolean hasprop = isPropertyPresent(ctx); boolean thisref = false; if (!hasprop) { thisref = incoll || ctx.hasAttribute(URI_ATTRIBUTES, COMMON_TYPE); } boolean mapping = isMappingRef(ctx); if (hasprop || coll || implic || thisref) { // set up the property definition to be used PropertyDefinition prop = null; boolean hasobj = hasprop; if (implic) { // make sure no override of implicit property from collection if (hasprop) { ctx.throwStartTagException("Property definition not " + "allowed for collection items"); } else { String type = ctx.attributeText(URI_ATTRIBUTES, COMMON_TYPE, null); if (type == null) { if (!mapping) { if (incoll) { type = ((NestedCollection)contain).getItemType(); hasobj = true; } else { type = "java.lang.Object"; } } } else { hasobj = true; } prop = new PropertyDefinition(type, cobj, opt); } } else if (hasprop || thisref) { prop = unmarshalProperty(ctx, contain, cobj, opt); } else { prop = new PropertyDefinition(cobj, opt); } // check if using direct object marshalling and unmarshalling if (isDirectObject(ctx)) { // validate and configure direct marshalling and unmarshalling comp = new DirectProperty(prop, unmarshalDirectObj(ctx, prop.getTypeName(), contain, null, -1, name)); } else if (mapping) { // validate and configure reference to mapping in context comp = unmarshalMappingRef(ctx, contain, cobj, prop, name); } else { // check for object binding needed IContextObj icobj = cobj; ObjectBinding bind = null; boolean typed = false; if (implic) { typed = !prop.getTypeName().equals("java.lang.Object"); } else { typed = !prop.getTypeName().equals (cobj.getBoundClass().getClassName()); } if ((hasobj && !prop.isThis()) || (!hasobj && typed)) { bind = unmarshalObjectBinding(ctx, cobj, contain, prop.getTypeName()); icobj = bind; } // validate and configure reference to structure in context if (ctx.hasAttribute(URI_ATTRIBUTES, COMMON_USING)) { comp = unmarshalStructureRef(ctx, contain, name, prop, icobj); } else { // validate and configure actual binding definition DefinitionContext defc = contain.getDefinitionContext(); IComponent top = bind; // check for optional label definition String label = ctx.attributeText(URI_ATTRIBUTES, COMMON_LABEL, null); // set load and store handlers for collection NestedCollection.CollectionLoad load = null; NestedCollection.CollectionStore store = null; String itype = null; if (coll) { // get any method names and type supplied by user String stname = ctx.attributeText(URI_ATTRIBUTES, COLLECTION_STOREMETHOD, null); String aname = ctx.attributeText(URI_ATTRIBUTES, COLLECTION_ADDMETHOD, null); String lname = ctx.attributeText(URI_ATTRIBUTES, COLLECTION_LOADMETHOD, null); String szname = ctx.attributeText(URI_ATTRIBUTES, COLLECTION_SIZEMETHOD, null); String iname = ctx.attributeText(URI_ATTRIBUTES, COLLECTION_ITERMETHOD, null); itype = ctx.attributeText(URI_ATTRIBUTES, COLLECTION_ITEMTYPE, "java.lang.Object"); // verify combinations of attributes supplied if ((lname == null || szname == null) && !(lname == null && szname == null)) { ctx.throwStartTagException(COLLECTION_LOADMETHOD + " and " + COLLECTION_SIZEMETHOD + " attributes must be used together"); } if (iname != null && lname != null) { ctx.throwStartTagException(COLLECTION_ITERMETHOD + " and " + COLLECTION_LOADMETHOD + " attributes cannot be used together"); } if (aname != null && stname != null) { ctx.throwStartTagException(COLLECTION_ADDMETHOD + " and " + COLLECTION_STOREMETHOD + " attributes cannot be used together"); } // set defaults based on collection type ClassFile cf = ClassCache.getClassFile (prop.getTypeName()); if (cf.isSuperclass("java.util.Vector")|| cf.isSuperclass("java.util.ArrayList")) { if (stname == null && aname == null) { aname = "add"; } if (iname == null && lname == null) { lname = "get"; szname = "size"; } } else if (cf.isImplements("Ljava/util/Collection;")) { if (stname == null && aname == null) { aname = "add"; } if (iname == null && lname == null) { iname = "iterator"; } } else if (cf.isArray()) { String ptype = prop.getTypeName(); itype = ptype.substring(0, ptype.length()-2); } // check binding direction(s) BindingDefinition bdef = contain.getBindingRoot(); boolean isdoub = "long".equals(itype) || "double".equals(itype); if (bdef.isInput()) { // define strategy for adding items to collection if (aname != null) { ClassItem meth = cf.getBestMethod(aname, null, new String[] { itype }); if (meth == null) { ctx.throwStartTagException ("Add method " + aname + " not found in collection type " + cf.getName()); } boolean hasval = !"void".equals(meth.getTypeName()); store = new NestedCollection.AddStore(meth, isdoub, hasval); } else if (stname != null) { ClassItem meth = cf.getBestMethod(stname, null, new String[] { "int", itype }); if (meth == null) { ctx.throwStartTagException ("Indexed store method " + stname + " not found in collection type " + cf.getName()); } boolean hasval = !"void".equals(meth.getTypeName()); store = new NestedCollection.IndexedStore(meth, isdoub, hasval); } else if (cf.isArray()) { store = new NestedCollection.ArrayStore(itype, isdoub); } else { ctx.throwStartTagException ("Unknown collection " + "type with no add or store method defined"); } } if (bdef.isOutput()) { // define strategy for loading items from collection if (lname != null) { ClassItem smeth = cf.getMethod(szname, "()I"); if (smeth == null) { ctx.throwStartTagException ("Size method " + szname + " not found in collection type " + cf.getName()); } ClassItem lmeth = cf.getBestMethod(lname, itype, new String[] { "int" }); if (lmeth == null) { ctx.throwStartTagException ("Load method " + lname + " not found in collection type " + cf.getName()); } load = new NestedCollection. IndexedLoad(smeth, isdoub, lmeth); } else if (iname != null) { String mname = "hasNext"; String nname = "next"; ClassItem meth = cf.getMethod(iname, "()Ljava/util/Iterator;"); if (meth == null) { mname = "hasMoreElements"; nname = "nextElement"; meth = cf.getMethod(iname, "()Ljava/util/Enumeration;"); if (meth == null) { ctx.throwStartTagException ("Iterator method " + iname + " not found in collection type " + cf.getName()); } } load = new NestedCollection. IteratorLoad(meth, isdoub, "java.util.Iterator." + mname, "java.util.Iterator." + nname); } else if (cf.isArray()) { load = new NestedCollection.ArrayLoad(itype, isdoub); } else { ctx.throwStartTagException ("Unknown collection " + "type with no load method defined"); } } } // unmarshal basics of nested structure NestedBase nest; boolean ordered = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_ORDERED, true); boolean flex = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_FLEXIBLE, false); boolean nillable = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_NILLABLE, false); if (nillable) { contain.getBindingRoot().setSchemaInstanceUsed(); } if (coll) { // create collection definition nest = new NestedCollection(contain, icobj, ordered, opt, flex, itype, load, store); nest.unmarshal(ctx); ctx.parsePastStartTag(URI_ELEMENTS, COLLECTION_ELEMENT); } else { // create structure definition boolean choice = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_CHOICE, false); boolean dupl = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_DUPLICATES, false); nest = new NestedStructure(contain, icobj, ordered, choice, flex, false, hasobj, dupl); nest.unmarshal(ctx); ctx.parsePastStartTag(URI_ELEMENTS, STRUCTURE_ELEMENT); } // unmarshal child bindings with optional label String ctype = (itype == null) ? "java.lang.Object" : itype; unmarshalFormats(ctx, nest.getDefinitionContext()); unmarshalMappings(ctx, contain, null, uord); unmarshalStructureChildren(ctx, nest, icobj, coll | (implic && !hasobj), ctype); if (top == null) { top = nest; } // special check for structure wrapping value boolean impstruct = false; boolean childs = nest.hasContent(); if (implic && !coll && childs) { ArrayList contents = nest.getContents(); impstruct = true; for (int i = 0; i < contents.size(); i++) { if (!(contents.get(i) instanceof ValueChild)) { impstruct = false; break; } else { ValueChild vchild = (ValueChild)contents.get(i); if (!vchild.isImplicit()) { impstruct = false; break; } } } } if (impstruct) { comp = nest; nest.setObjectContext(cobj); if (name != null) { comp = new ElementWrapper(defc, name, comp, nillable); if (bind != null && implic) { if (!hasprop) { ((ElementWrapper)comp).setDirect(true); ArrayList contents = nest.getContents(); impstruct = true; for (int i = 0; i < contents.size(); i++) { if (contents.get(i) instanceof ValueChild) { ValueChild vchild = (ValueChild)contents.get(i); vchild.switchProperty(); } } } prop.setOptional(false); } } } else { // check for children defined boolean addref = false; if (!childs) { if (coll) { // add mapping as only child if (ctype.equals("java.lang.Object")) { nest.addComponent (new DirectGeneric(nest, null)); } else { nest.addComponent(new MappingReference(contain, new PropertyDefinition(ctype, cobj, false), ctype, null, null, icobj, null, true, false)); } childs = true; } else if (name != null) { // must be abstract mappping reference, create child addref = true; } } // handle nested children comp = top; if (childs || addref) { // define component property wrapping object binding boolean optprop = hasprop && prop.isOptional(); if (bind != null) { boolean skip = name != null && optprop; comp = new ComponentProperty(prop, comp, skip); bind.setWrappedComponent(nest); } // create reference to mapping as special case // this allows structure with name but no children to // use abstract mapping if (addref) { PropertyDefinition thisprop = new PropertyDefinition(bind, false); nest.addComponent(new MappingReference (nest, thisprop, comp.getType(), null, null, icobj, null, false, false)); } if (name != null) { comp = new ElementWrapper(defc, name, comp, nillable); if (bind != null && implic) { if (!hasprop) { ((ElementWrapper)comp).setDirect(true); } prop.setOptional(false); } if (optprop) { ((ElementWrapper)comp).setOptionalNormal(true); boolean isobj = bind != null; ((ElementWrapper)comp). setStructureObject(isobj); ((ElementWrapper)comp).setDirect(isobj); comp = new OptionalStructureWrapper(comp, prop, isobj); prop.setOptional(false); } else if (opt && !implic) { ((ElementWrapper)comp).setOptionalNormal(true); comp = new OptionalStructureWrapper(comp, prop, false); prop.setOptional(false); } } } else { // treat as mapping, with either type or generic String type = prop.getTypeName(); if (prop.equals("java.lang.Object")) { comp = new ComponentProperty(prop, new DirectGeneric(contain, null), false); } else { comp = new MappingReference(contain, prop, type, null, null, icobj, name, false, false); } } } // set object binding as definition for label if (label != null) { defc.addNamedStructure(label, top); } } } } else { // structure with no separate object, verify no forbidden attributes if (isObjectBinding(ctx)) { ctx.throwStartTagException("Object attributes not " + "allowed without property definition"); } else if (isDirectObject(ctx)) { ctx.throwStartTagException("Marshaller and unmarshaller not " + "allowed without property definition"); } // check for reference to structure defined elsewhere if (mapping) { // handle "this" reference as anonymous property PropertyDefinition prop = new PropertyDefinition(cobj, opt); // handle reference to defined mapping comp = unmarshalMappingRef(ctx, contain, cobj, prop, name); implic = true; } else if (ctx.hasAttribute(URI_ATTRIBUTES, COMMON_USING)) { // make sure forbidden attribute not used if (ctx.hasAttribute(URI_ATTRIBUTES, COMMON_ORDERED)) { ctx.throwStartTagException(COMMON_ORDERED + " attribute " + " not allowed with " + COMMON_USING + " attribute"); } // validate and configure reference to structure in context comp = unmarshalStructureRef(ctx, contain, name, null, cobj); } else { // unmarshal children as nested structure boolean ordered = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_ORDERED, true); boolean choice = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_CHOICE, false); boolean flex = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_FLEXIBLE, false); boolean dupl = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_DUPLICATES, false); NestedStructure nest = new NestedStructure(contain, cobj, ordered, choice, flex, false, hasprop, dupl); nest.unmarshal(ctx); // unmarshal child bindings with optional label String label = ctx.attributeText(URI_ATTRIBUTES, COMMON_LABEL, null); ctx.parsePastStartTag(URI_ELEMENTS, STRUCTURE_ELEMENT); unmarshalFormats(ctx, nest.getDefinitionContext()); unmarshalMappings(ctx, contain, null, uord); unmarshalStructureChildren(ctx, nest, cobj, false, "java.lang.Object"); // check for children defined DefinitionContext defc = contain.getDefinitionContext(); if (nest.hasContent()) { // build structure to access children or with elment wrapper if (name == null) { comp = nest; } else { comp = new ElementWrapper(defc, name, nest, false); if (opt) { ((ElementWrapper)comp).setOptionalNormal(true); ((ElementWrapper)comp).setStructureObject(true); } } if (label != null) { defc.addNamedStructure(label, nest); } } else { // make sure there's a name defined if (name == null) { ctx.throwException ("Property, name, or child component required"); } // treat as throwaway portion of document comp = new ElementWrapper(defc, name, null, false); if (opt) { ((ElementWrapper)comp).setOptionalIgnored(true); } } } } // finish by parsing past end tag ctx.parsePastEndTag(URI_ELEMENTS, coll ? COLLECTION_ELEMENT : STRUCTURE_ELEMENT); return comp; } /** * Unmarshal mapping definition. This handles all combinations of attributes * on the start tag, generating the appropriate structure of nested * components and other classes to represent the binding information within * the current element. This must be called with the parse positioned at the * start tag of the element to be unmarshalled. * * @param ctx unmarshalling context information * @param parent containing binding definition structure * @param nss extra namespaces to be included in this mapping definition * (may be null) * @param uord container is unordered structure flag * @return mapping definition constructed from binding * @throws JiBXException if error in unmarshalling */ public static IMapping unmarshalMapping(UnmarshallingContext ctx, IContainer parent, ArrayList nss, boolean uord) throws JiBXException { // first check for an abstract mapping boolean abs = ctx.attributeBoolean(URI_ATTRIBUTES, MAPPING_ABSTRACT, false); String type = ctx.attributeText(URI_ATTRIBUTES, MAPPING_CLASS); // get name definition if supplied NameDefinition name = null; if (isNamePresent(ctx)) { name = unmarshalName(ctx, false); } // get type name information String text = ctx.attributeText(URI_ATTRIBUTES, MAPPING_TYPENAME, null); String tname = null; if (text != null) { tname = QName.deserialize(text, ctx).toString(); } // check if using direct object marshalling and unmarshalling IMapping mapping; if (isDirectObject(ctx)) { // check for definition context needed DefinitionContext defc = null; if (nss != null && nss.size() > 0) { // add all outer namespaces to context defc = new DefinitionContext(parent); if (nss != null && nss.size() > 0) { for (int j = 0; j < nss.size(); j++) { defc.addNamespace((NamespaceDefinition)nss.get(j)); } } } // validate and configure direct marshalling and unmarshalling int slot = parent.getBindingRoot().getMappedClassIndex(type); mapping = new MappingDirect(parent, type, tname, unmarshalDirectObj(ctx, type, parent, defc, slot, name), abs); } else { // not direct mapping, check for missing required name if (!abs && name == null) { ctx.throwStartTagException("Non-abstract mapping must define " + "an element name"); } // check for optional definitions String label = ctx.attributeText(URI_ATTRIBUTES, COMMON_LABEL, null); // create definition context for namespaces and formats String base = ctx.attributeText(URI_ATTRIBUTES, MAPPING_EXTENDS, null); ObjectBinding bind = unmarshalObjectBinding(ctx, null, parent, type); boolean ordered = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_ORDERED, true); boolean choice = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_CHOICE, false); boolean flex = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_FLEXIBLE, false); boolean nillable = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_NILLABLE, false); if (nillable) { parent.getBindingRoot().setSchemaInstanceUsed(); } boolean dupl = ctx.attributeBoolean(URI_ATTRIBUTES, COMMON_DUPLICATES, false); NestedStructure nest = new NestedStructure(parent, bind, ordered, choice, flex, true, true, dupl); nest.unmarshal(ctx); // add all outer namespaces to context DefinitionContext defc = nest.getDefinitionContext(); if (nss != null && nss.size() > 0) { for (int j = 0; j < nss.size(); j++) { defc.addNamespace((NamespaceDefinition)nss.get(j)); } } // unmarshal all contained binding information ctx.parsePastStartTag(URI_ELEMENTS, MAPPING_ELEMENT); unmarshalNamespaces(ctx, nest.getDefinitionContext()); unmarshalFormats(ctx, nest.getDefinitionContext()); unmarshalMappings(ctx, nest, null, uord); unmarshalStructureChildren(ctx, nest, bind, false, "java.lang.Object"); // validate and configure actual binding definition bind.setWrappedComponent(nest); mapping = new MappingDefinition(parent, nest.getDefinitionContext(), type, name, tname, abs, base, bind, nillable); // set label if defined if (label != null) { defc.addNamedStructure(label, bind); } } // finish by adding mapping and parsing past end tag parent.getDefinitionContext().addMapping(mapping); ctx.parsePastEndTag(URI_ELEMENTS, MAPPING_ELEMENT); return mapping; } /** * Unmarshal included binding. This handles the actual include element along * with the actual included binding. The current implementation allows for * nested includes, but requires that all the included bindings use * compatible settings for the attributes of the root element, and only * allows mapping elements as children of the included bindings (no * namespace or format elements). * * @param ctx unmarshalling context information * @param bdef binding defintion at root of includes * @param root base URL for binding, or null if unknown * @param nslist list of namespaces defined * @param paths set of binding paths processed * @throws JiBXException if error in unmarshalling */ public static void unmarshalInclude(UnmarshallingContext ctx, BindingDefinition bdef, URL root, ArrayList nslist, HashSet paths) throws JiBXException { // make sure path hasn't already been processed ctx.parseToStartTag(URI_ELEMENTS, INCLUDE_ELEMENT); String path = ctx.attributeText(URI_ATTRIBUTES, INCLUDE_PATH); URL url; try { if (root == null) { url = new URL(path); } else { url = new URL(root, path); } } catch (MalformedURLException e) { throw new JiBXException("Unable to handle include path " + path, e); } String fpath = url.toExternalForm(); if (paths.add(fpath)) { try { // access the included binding as input stream UnmarshallingContext ictx = new UnmarshallingContext(); ictx.setDocument(url.openStream(), null); // unmarshal namespaces (to apply to these mappings, only) ictx.parseToStartTag(URI_ELEMENTS, BINDING_ELEMENT); ictx.parsePastStartTag(URI_ELEMENTS, BINDING_ELEMENT); ArrayList nss = new ArrayList(nslist); while (ictx.isAt(URI_ELEMENTS, NAMESPACE_ELEMENT)) { nss.add(unmarshalNamespace(ictx)); } // process any format definitions (for global context) unmarshalFormats(ictx, bdef.getDefinitionContext()); // check for nested includes while (ictx.isAt(URI_ELEMENTS, INCLUDE_ELEMENT)) { unmarshalInclude(ictx, bdef, url, nss, paths); } // process all mappings defined in included binding unmarshalMappings(ictx, bdef, nss, false); } catch (IOException e) { throw new JiBXException ("Error accessing included binding with path " + path, e); } } // finish by skipping past end of tag in main binding ctx.parsePastEndTag(URI_ELEMENTS, INCLUDE_ELEMENT); } /** * Unmarshal binding definition. This handles the entire binding definition * document. * * @param ctx unmarshalling context information * @param name default name for binding * @param root base URL for binding, or null if unknown * @throws JiBXException if error in unmarshalling */ public static BindingDefinition unmarshalBindingDefinition (UnmarshallingContext ctx, String name, URL root) throws JiBXException { // start by reading optional binding name ctx.parseToStartTag(URI_ELEMENTS, BINDING_ELEMENT); name = ctx.attributeText(URI_ATTRIBUTES, BINDING_NAME, name); // set the binding direction flags int dir = ctx.attributeEnumeration(URI_ATTRIBUTES, BINDING_DIRECTION, BINDINGDIR_NAMES, BINDINGDIR_NUMS, DIRECTION_BOTH); boolean ibind = dir == DIRECTION_BOTH || dir == DIRECTION_INPUT; boolean obind = dir == DIRECTION_BOTH || dir == DIRECTION_OUTPUT; // read other attribute values String tpack = ctx.attributeText(URI_ATTRIBUTES, BINDING_PACKAGE, null); boolean glob = ctx.attributeBoolean(URI_ATTRIBUTES, BINDING_GLOBALID, true); boolean forward = ctx.attributeBoolean(URI_ATTRIBUTES, BINDING_FORWARDS, true); boolean track = ctx.attributeBoolean(URI_ATTRIBUTES, BINDING_TRACKING, false); boolean force = ctx.attributeBoolean(URI_ATTRIBUTES, BINDING_FORCE, false); // create actual binding instance BindingDefinition bdef = new BindingDefinition(name, ibind, obind, tpack, glob, forward, track, force); bdef.unmarshal(ctx); // unmarshal namespaces and formats defined under root ctx.parsePastStartTag(URI_ELEMENTS, BINDING_ELEMENT); ArrayList nss = new ArrayList(); while (ctx.isAt(URI_ELEMENTS, NAMESPACE_ELEMENT)) { nss.add(unmarshalNamespace(ctx)); } unmarshalFormats(ctx, bdef.getDefinitionContext()); // process any included binding definitions HashSet paths = new HashSet(); if (root != null) { paths.add(root.toExternalForm()); } while (ctx.isAt(URI_ELEMENTS, INCLUDE_ELEMENT)) { unmarshalInclude(ctx, bdef, root, nss, paths); } // finish with common handling for elements which can be nested unmarshalMappings(ctx, bdef, nss, false); ctx.parsePastEndTag(URI_ELEMENTS, BINDING_ELEMENT); return bdef; } /** * Base class for containers. This just handles unmarshalling and checking * the values of attributes used by all containers. The container class * should set the appropriate default values for all these attributes in its * constructor, using -1 (for int values) and * null (for String values) if the default is to * simply use setting inherited from a containing component. The binding * definition root object must always define actual values as the defaults, * since otherwise the code will fall off the end of the chain of ancestors. */ /*package*/ static class ContainerBase { /** Containing binding component. */ protected IContainer m_container; /** Default style for value expression. */ protected int m_styleDefault; /** Auto-link style for default mappings. */ protected int m_autoLink; /** Access level for default mappings. */ protected int m_accessLevel; /** Prefix text to be stripped from names. */ protected String m_stripPrefix; /** Suffix text to be stripped from names. */ protected String m_stripSuffix; /** Style used for generating element or attribute names. */ protected int m_nameStyle; /** * Constructor. * * @param parent containing binding definition context */ public ContainerBase(IContainer parent) { m_container = parent; } /** * Unmarshal common container attributes. * * @param ctx unmarshalling context information * @throws JiBXException if error in unmarshalling */ public void unmarshal(UnmarshallingContext ctx) throws JiBXException { m_styleDefault = ctx.attributeEnumeration(URI_ATTRIBUTES, COMMON_STYLE, CONTAINING_STYLE_NAMES, CONTAINING_STYLE_NUMS, m_styleDefault); m_autoLink = ctx.attributeEnumeration(URI_ATTRIBUTES, COMMON_AUTOLINK, AUTO_LINK_NAMES, AUTO_LINK_NUMS, m_autoLink); m_accessLevel = ctx.attributeEnumeration(URI_ATTRIBUTES, COMMON_ACCESSLEVEL, ACCESS_LEVEL_NAMES, ACCESS_LEVEL_NUMS, m_accessLevel); m_stripPrefix = ctx.attributeText(URI_ATTRIBUTES, COMMON_STRIPPREFIX, m_stripPrefix); m_stripSuffix = ctx.attributeText(URI_ATTRIBUTES, COMMON_STRIPSUFFIX, m_stripSuffix); m_nameStyle = ctx.attributeEnumeration(URI_ATTRIBUTES, COMMON_NAMESTYLE, NAME_GENERATE_NAMES, NAME_GENERATE_NUMS, m_nameStyle); } // // IContainer interface method definitions (partial list) public int getStyleDefault() { if (m_styleDefault >= 0) { return m_styleDefault; } else { return m_container.getStyleDefault(); } } } }