/* * Copyright (c) 2006-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.schema.codegen; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.InfixExpression.Operator; import org.jibx.binding.model.BindingHolder; import org.jibx.binding.model.CollectionElement; import org.jibx.binding.model.ContainerElementBase; import org.jibx.binding.model.ElementBase; import org.jibx.binding.model.FormatDefaults; import org.jibx.binding.model.FormatElement; import org.jibx.binding.model.MappingElement; import org.jibx.binding.model.NestingAttributes; import org.jibx.binding.model.PropertyAttributes; import org.jibx.binding.model.StructureElement; import org.jibx.binding.model.StructureElementBase; import org.jibx.binding.model.ValueElement; import org.jibx.binding.util.NameUtilities; import org.jibx.runtime.QName; import org.jibx.runtime.Utility; import org.jibx.schema.IArity; import org.jibx.schema.INamed; import org.jibx.schema.SchemaUtils; import org.jibx.schema.codegen.ClassHolder.Value; import org.jibx.schema.elements.AnnotatedBase; import org.jibx.schema.elements.AttributeElement; import org.jibx.schema.elements.CommonCompositorBase; import org.jibx.schema.elements.ElementElement; import org.jibx.schema.elements.SchemaBase; /** * Information for a data class to be included in code generated from schema. * * @author Dennis M. Sosnoski */ public class StructureClassHolder extends ClassHolder { /** Logger for class. */ private static final Logger s_logger = Logger.getLogger(StructureClassHolder.class.getName()); /** Default format definitions map. */ private static final Map s_formatMap; static { s_formatMap = new HashMap(); for (int i = 0; i < FormatDefaults.s_defaultFormats.length; i++) { FormatElement format = FormatDefaults.s_defaultFormats[i]; s_formatMap.put(format.getTypeName(), format); } } /** Flag for collection present in class. */ private boolean m_collectionPresent; /** Parent wrapper for all items included in class. */ private Wrapper m_classGroup; /** Binding definition element for this class. */ private ContainerElementBase m_bindingElement; /** * Constructor. * * @param name class name * @param base base class name * @param pack package information * @param nconv name converter * @param inner use inner classes for substructures */ public StructureClassHolder(String name, String base, PackageHolder pack, NameConverter nconv, boolean inner) { super(name, base, pack, nconv, inner); } /** * Constructor for creating a child inner class definition. * * @param name class name * @param context parent class */ private StructureClassHolder(String name, StructureClassHolder context) { super(name, context); } /** * Derive group names from the containing group prefix and the simple name of the group. * * @param group * @param container (null if none) * @return name */ // static String deriveGroupName(GroupItem group, Group container) { // String prefix = null; // if (container != null) { // prefix = group.getClassName(); // String prior = container.getPrefix(); // if (prior == null) { // prefix = NameConverter.toNameLead(prefix); // } else { // prefix = prior + NameConverter.toNameWord(prefix); // } // prefix = container.uniqueChildPrefix(prefix); // } // return prefix; // } /** * Add the items in a structure to the class representation. This creates a grouping for the items, adding the * grouping to the containing grouping. It should only be used for structures with child items. * * @param struct * @param container */ private void addItems(GroupItem struct, Wrapper container) { for (Item item = struct.getFirstChild(); item != null; item = item.getNext()) { if ((USE_COLLECTIONS || USE_GENERIC_TYPES) && item.isCollection()) { m_collectionPresent = true; } if (item instanceof GroupItem) { GroupItem group = (GroupItem)item; if (group.isInline()) { // create a new group for an inlined compositor only if it's or nested Wrapper into = container; AnnotatedBase comp = item.getSchemaComponent(); if (comp instanceof CommonCompositorBase) { if (comp.type() == SchemaBase.CHOICE_TYPE || comp.getParent() instanceof CommonCompositorBase) { into = new Wrapper(group, container); } } else { into = new Wrapper(group, container); } addItems(group, into); into.adjustName(); } else { // create a new class and populate that ClassHolder child; String text = group.getEffectiveClassName(); if (m_useInnerClasses) { if (m_nameSet.contains(text)) { StructureClassHolder outer = this; while (outer != null) { if (outer.getName().equals(text)) { text += "Inner"; break; } else { outer = (StructureClassHolder)outer.m_outerClass; } } } text = m_nameSet.add(text); child = group.isEnumeration() ? new EnumerationClassHolder(text, this) : new StructureClassHolder(text, this); m_inners.add(child); if (s_logger.isDebugEnabled()) { s_logger.debug("Added inner class " + child.getFullName()); } } else { String fullname = m_baseName + text; child = m_package.addClass(fullname, m_baseName, m_nameConverter, group.isEnumeration()); addImport(child.getFullName(), true); text = child.getName(); if (s_logger.isDebugEnabled()) { s_logger.debug("Added derived class " + child.getFullName()); } } group.setClassName(text); group.setGenerateClass(child); Value value = new Value(item, container); if (!group.isEnumeration()) { group.convertExtensionReference(); importValueType(value); } child.createStructure(group); } } else { importValueType(new Value(item, container)); } } } /** * Get the binding component linked to this class. * * @return binding definition element (<mapping> or <structure>) */ public ContainerElementBase getBinding() { return m_bindingElement; } /** * Set the binding component linked to this class. * * @param container binding definition element (<mapping> or <structure>) */ public void setBinding(ContainerElementBase container) { m_bindingElement = container; } /** * Recursively add all inner enumeration classes as formats to a <mapping> definition. This is used to create the * <format> elements for all nested enumerations, which need to be direct children of the <mapping> element * for the top-level class. * * @param mapping */ private void addInnerFormats(MappingElement mapping) { for (int i = 0; i < m_inners.size(); i++) { ClassHolder inner = (ClassHolder)m_inners.get(i); if (inner instanceof EnumerationClassHolder) { FormatElement format = new FormatElement(); format.setTypeName(inner.getBindingName()); ((EnumerationClassHolder)inner).setBinding(format); mapping.addTopChild(format); } else { ((StructureClassHolder)inner).addInnerFormats(mapping); } } } /** * Convert an item structure to a class representation. This may include creating child classes, where necessary. * * @param group */ public void createStructure(GroupItem group) { if (group.isEnumeration()) { throw new IllegalArgumentException("Internal error - group is an enumeration"); } else { // set the basic configuration information setNamespace(group.getSchemaComponent().getSchema().getEffectiveNamespace()); m_classGroup = new Wrapper(group, null); // populate the actual definition structure addItems(group, m_classGroup); // import the list type if needed if (m_collectionPresent) { addImport(COLLECTION_VARIABLE_TYPE, false); } // import the serializable interface if needed if (IMPLEMENT_SERIALIZABLE) { addImport("java.io.Serializable", false); } } } /** * Add all fixed names in a group to the set of names defined for this class. This calls itself recursively to * handle nested groups. * * @param wrapper */ private void addFixedNames(Wrapper wrapper) { ArrayList values = wrapper.getValues(); for (int i = 0; i < values.size(); i++) { Value value = (Value)values.get(i); Item item = value.getItem(); boolean addname = item.isFixedName(); if (value instanceof Wrapper) { Wrapper childgrp = (Wrapper)value; addFixedNames(childgrp); addname = addname && (childgrp.isSelectorNeeded() || wrapper.isSelectorNeeded()); } if (addname) { String name = item.getEffectiveName(); if (!m_nameSet.add(name).equals(name)) { // TODO: pass in the validation context, create an error throw new IllegalStateException("Name '" + name + "' cannot be used twice in same context"); } } } } /** * Handle value name assignments for a group within this class. This calls itself recursively to handle nested * groups. TODO: set up use-specific linked name sets, so that the same name can be used in different ways without * conflict * * @param wrapper * @param innamed flag for parent group name already fixed */ private void fixFlexibleNames(Wrapper wrapper, boolean innamed) { // check for group which uses a selector (choice or union) String suffix = null; ArrayList values = wrapper.getValues(); if (wrapper.isSelectorNeeded()) { // add the actual variable name used to record current state Item item = wrapper.getItem(); item.setName(m_nameSet.add(m_nameConverter.toBaseName(item.getEffectiveName()) + "Select")); // generate constant for each child value if (wrapper.getSchemaComponent().type() == SchemaBase.UNION_TYPE) { suffix = "_Form"; } else { suffix = "_Choice"; } for (int i = 0; i < values.size(); i++) { Value value = (Value)values.get(i); if (!(value instanceof Wrapper)) { String name = m_nameConverter.toConstantName(value.getItem().getEffectiveName() + suffix); value.setSelectValue(m_nameSet.add(name)); } } } // handle name conversions and recording for (int i = 0; i < values.size(); i++) { Value value = (Value)values.get(i); Item item = value.getItem(); if (value instanceof Wrapper) { // use recursive call to set child group names (adopting name for group if same as first child, for // group inside choice, in order to avoid adding the same name twice for non-conflicting usages) boolean adoptname = false; ArrayList childvals = ((Wrapper)value).getValues(); if (childvals.size() > 0) { Item firstchlditem = ((Value)childvals.get(0)).getItem(); String compname = firstchlditem.getEffectiveName(); String currname = item.getEffectiveName(); if (wrapper.isSelectorNeeded() && Utility.safeEquals(compname, currname)) { adoptname = true; } } boolean passname = item.isFixedName() || (innamed && item.getName() == null); fixFlexibleNames((Wrapper)value, passname); if (adoptname) { item.setName(((Value)childvals.get(0)).getItem().getEffectiveName()); } if (wrapper.isSelectorNeeded()) { String name = m_nameConverter.toConstantName(item.getEffectiveName() + suffix); value.setSelectValue(adoptname ? name : m_nameSet.add(name)); } } else if (!item.isFixedName()) { // just use inherited value name directly if already added String name = item.getEffectiveName(); if (item.getName() == null && innamed) { item.setName(name); } else { // convert and add the value name if (value.isCollection()) { String singular = NameUtilities.depluralize(name); if (!singular.equals(name)) { s_logger.debug("Converted name " + name + " to " + singular); } name = singular; } if (NameConverter.isReserved(name)) { name = name + '_'; } item.setName(m_nameSet.add(name)); } } } } /** * Generate the code to check and set the selection on any containing selector group. This should be used when * setting any value, including inside selector methods (if used), since selector groups may be nested. * * @param selectcall * @param value * @param block * @param builder */ private void generateSelectorSet(boolean selectcall, Value value, BlockBuilder block, ClassBuilder builder) { Wrapper group; while ((group = value.getWrapper()) != null) { if (group.isSelectorNeeded()) { if (selectcall) { // when using select method call, just call that method (it will call containing group method, if // any) InvocationBuilder call = builder.createMemberMethodCall(group.getSelectMethod()); call.addVariableOperand(value.getSelectValue()); block.addCall(call); break; } else { // if setting directly, set this one and continue up to next containing group block.addAssignVariableToField(value.getSelectValue(), group.getSelectField()); } } value = group; } } /** * Generate a test method for a value, if it's part of a group with a selector. * * @param value * @param propname * @param builder */ private void checkIfMethod(Value value, String propname, ClassBuilder builder) { if (value.getWrapper().isSelectorNeeded()) { // add the if method definition for selector group MethodBuilder ifmeth = builder.addMethod("if" + propname, "boolean"); ifmeth.setPublic(); InfixExpressionBuilder testexpr = builder.buildNameOp(value.getWrapper().getSelectField(), Operator.EQUALS); testexpr.addVariableOperand(value.getSelectValue()); ifmeth.createBlock().addReturnExpression(testexpr); } } /** * Set the attributes for a <structure> or <collection> element to represent a wrapped group of values in the * binding. * * @param value * @param struct */ private void setStructureAttributes(Value value, StructureElementBase struct) { struct.setFieldName(value.getFieldName()); if (value.isOptional()) { struct.setUsage(PropertyAttributes.OPTIONAL_USAGE); } } /** * Set the name for a <structure> or <collection> element based on the schema component associated with a * value. * * @param value * @param struct */ private void setStructureName(Value value, StructureElementBase struct) { AnnotatedBase comp = findNamedComponent(value); if (comp != null) { struct.setName(((INamed)comp).getName()); } } /** * Find the component supplying the name to be used for a value. If the value doesn't define an element or attribute * name directly, this searches up through any containing wrappers with no other children until a named component is * found. If that name has not already been represented in the binding this returns that name. Whether obtained * directly or indirectly, the returned name is flagged as used so that it won't be used again. * * @param value * @return component supply name, or null if none */ private AnnotatedBase findNamedComponent(Value value) { // loop to find wrapper with name and no other children while (!value.isNamed()) { Wrapper wrapper = value.getWrapper(); if (wrapper != null && wrapper.getValues().size() == 1) { value = wrapper; } else { return null; } } // check for name used, either directly or at ancestor level with same component AnnotatedBase comp = value.getSchemaComponent(); Value match = value; while (match != null && match.getSchemaComponent() == comp) { if (match.isNameUsed()) { return null; } else { match.setNameUsed(true); match = match.getWrapper(); } } return comp; } /** * Build a <value> binding component for a field. * * @param value * @param propname * @return constructed binding component */ private ValueElement buildValueBinding(Value value, String propname) { // add element to binding structure for simple (primitive or text) value ValueElement element = new ValueElement(); element.setFieldName(value.getFieldName()); // set test method if needed to pick between alternatives Wrapper wrapper = value.getWrapper(); if (wrapper.isSelectorNeeded()) { element.setTestName("if" + propname); element.setUsage(PropertyAttributes.OPTIONAL_USAGE); } else if (value.isOptional()) { element.setUsage(PropertyAttributes.OPTIONAL_USAGE); } // get the schema component supplying an element or attribute name AnnotatedBase comp = findNamedComponent(value); if (comp == null) { element.setEffectiveStyle(ValueElement.TEXT_STYLE); } else { if (comp.type() == SchemaBase.ELEMENT_TYPE) { // value is an element, set the name directly element.setEffectiveStyle(NestingAttributes.ELEMENT_STYLE); ElementElement elem = (ElementElement)comp; element.setName(elem.getName()); if (SchemaUtils.isOptionalElement(elem)) { // TODO: this is needed because the optional status doesn't inherit downward for embedded items, as when // a simpleType is nested inside an optional attribute. should the code be changed to inherit instead? element.setUsage(PropertyAttributes.OPTIONAL_USAGE); } // check for embedded inside a if (wrapper.isSelectorNeeded()) { element.setUsage(PropertyAttributes.OPTIONAL_USAGE); element.setTestName("if" + propname); } } else if (comp.type() == SchemaBase.ATTRIBUTE_TYPE) { // value is an attribute, set the name directly element.setEffectiveStyle(NestingAttributes.ATTRIBUTE_STYLE); AttributeElement attr = (AttributeElement)comp; element.setName(attr.getName()); if (SchemaUtils.isOptionalAttribute(attr)) { // TODO: this is needed because the optional status doesn't inherit downward for embedded items, as when // a simpleType is nested inside an optional attribute. should the code be changed to inherit instead? element.setUsage(PropertyAttributes.OPTIONAL_USAGE); } } else { throw new IllegalStateException("Internal error - expected wrapper element or attribute not found"); } } return element; } /** * Generate the fields and methods for a group, and add to binding. This calls itself recursively to handle nested * groups. * * @param wrapper * @param builder * @param binding * @param holder */ private void buildDataModel(Wrapper wrapper, ClassBuilder builder, ContainerElementBase binding, BindingHolder holder) { // first check if group requires a selector field ArrayList values = wrapper.getValues(); Item grpitem = wrapper.getItem(); if (wrapper.isSelectorNeeded()) { // build the selector field String basename = grpitem.getEffectiveName(); String fieldname = m_nameConverter.toFieldName(basename); wrapper.setSelectField(fieldname); builder.addIntField(fieldname, "-1").setPrivate(); // create constants for each alternative value String descript; if (wrapper.getSchemaComponent().type() == SchemaBase.UNION_TYPE) { descript = "form"; } else { descript = "choice"; } for (int i = 0; i < values.size(); i++) { Value value = (Value)values.get(i); builder.addIntField(value.getSelectValue(), Integer.toString(i)).setPrivateFinal(); } // add selector set method String namesuffix = NameConverter.toNameWord(basename); String resetname = "clear" + namesuffix; String selectname = "set" + namesuffix; wrapper.setSelectMethod(selectname); MethodBuilder setmeth = builder.addMethod(selectname, "void"); setmeth.setPrivate(); BlockBuilder block = setmeth.createBlock(); setmeth.addParameter(descript, "int"); // start by setting any containing selectors generateSelectorSet(USE_SELECTION_SET_METHODS, wrapper, block, builder); // create the set block for when there's no current choice BlockBuilder assignblock = builder.newBlock(); assignblock.addAssignVariableToField(descript, fieldname); // create the exception thrown when choice does not match current setting BlockBuilder throwblock = builder.newBlock(); throwblock.addThrowException("IllegalStateException", "Need to call " + resetname + "() before changing existing " + descript); // finish with the if statement that decides which to execute InfixExpressionBuilder iftest = builder.buildNameOp(fieldname, Operator.EQUALS); iftest.addNumberLiteralOperand("-1"); InfixExpressionBuilder elsetest = builder.buildNameOp(fieldname, Operator.NOT_EQUALS); elsetest.addVariableOperand(descript); block.addIfElseIfStatement(iftest, elsetest, assignblock, throwblock); // add selector clear method MethodBuilder resetmeth = builder.addMethod(resetname, "void"); resetmeth.setPublic(); block = resetmeth.createBlock(); block.addAssignToName(block.numberLiteral("-1"), fieldname); if (s_logger.isDebugEnabled()) { s_logger.debug("Created selector for grouping component " + SchemaUtils.describeComponent(grpitem.getSchemaComponent()) + " in class " + getFullName()); } } // generate all values in group for (int i = 0; i < values.size(); i++) { // get the base name to be used for item Value value = (Value)values.get(i); Item item = value.getItem(); String basename = item.getEffectiveName(); if (value.isCollection()) { basename += "List"; } // generate test method, if inside selector group String propname = NameConverter.toNameWord(basename); checkIfMethod(value, propname, builder); if (value instanceof Wrapper) { // check if wrapper needed for name Wrapper childwrap = (Wrapper)value; if (childwrap.isNamed() && (item.isElementPresent() || item.isAttributePresent())) { // create named or wrapper and add wrapped values to that StructureElementBase struct; StructureElementBase inner; if (childwrap.isCollectionWrapper()) { if (binding.type() == ElementBase.COLLECTION_ELEMENT && ((CollectionElement)binding).getFieldName() == null) { // reuse the already-created collection element in binding struct = inner = (StructureElementBase)binding; } else { // create a new collection element for the binding struct = inner = new CollectionElement(); binding.addChild(struct); String name = ((INamed)childwrap.getSchemaComponent()).getName(); if (name != null) { // use name as wrapper on collection or as embedded element name // TODO: will this work for nested repeating elements? if (item.isCollection()) { // name represents repeated element, use it that way inner = new StructureElement(); struct.addChild(inner); setStructureName(value, struct); } else { // name is a wrapper element for collection, set it that way setStructureName(value, struct); } } } } else { struct = inner = new StructureElement(); setStructureName(value, struct); binding.addChild(struct); } setStructureAttributes(value, struct); if (wrapper.isSelectorNeeded()) { struct.setTestName("if" + propname); struct.setUsage(PropertyAttributes.OPTIONAL_USAGE); } if (childwrap.isSelectorNeeded()) { struct.setChoice(true); struct.setOrdered(false); } buildDataModel(childwrap, builder, inner, holder); if (struct.type() == ElementBase.COLLECTION_ELEMENT && struct.getFieldName() == null) { System.out.println(" with no field"); } } else if (childwrap.isSelectorNeeded() || wrapper.isSelectorNeeded()) { // create unnamed wrapper and add wrapped values to that StructureElement struct = new StructureElement(); setStructureName(value, struct); setStructureAttributes(value, struct); if (childwrap.isSelectorNeeded()) { struct.setChoice(true); struct.setOrdered(false); } binding.addChild(struct); buildDataModel(childwrap, builder, struct, holder); } else { // just ignore this wrapper and add wrapped values directly buildDataModel(childwrap, builder, binding, holder); } } else { // remaining code generation depends on simple or collection item s_logger.debug("Adding value item " + basename); String type = value.getType(); if (type != null) { // set the names to be used for value String fname = m_nameConverter.toFieldName(basename); value.setFieldName(fname); String getname = "get" + propname; value.setGetMethodName(getname); String setname = "set" + propname; value.setSetMethodName(setname); if (value.isCollection() || value.isList()) { // find the types to be used for field and actual instance Type fieldtype; Type gettype; Type settype; Type insttype; if (USE_GENERIC_TYPES) { fieldtype = builder.createParameterizedType(COLLECTION_VARIABLE_TYPE, type); gettype = builder.createParameterizedType(COLLECTION_VARIABLE_TYPE, type); settype = builder.createParameterizedType(COLLECTION_VARIABLE_TYPE, type); insttype = builder.createParameterizedType(COLLECTION_INSTANCE_TYPE, type); } else if (USE_COLLECTIONS) { fieldtype = builder.createType(COLLECTION_VARIABLE_TYPE); gettype = builder.createType(COLLECTION_VARIABLE_TYPE); settype = builder.createType(COLLECTION_VARIABLE_TYPE); insttype = builder.createType(COLLECTION_INSTANCE_TYPE); } else { fieldtype = builder.createType(type + "[]"); gettype = builder.createType(type + "[]"); settype = builder.createType(type + "[]"); insttype = null; } // generate the field as a collection (uninitialized if optional) FieldBuilder field = builder.addField(fname, fieldtype); // if (!value.isOptional()) { if (insttype != null) { field.setInitializer(builder.newInstance(insttype)); } // } field.setPrivate(); // add get method definition (unchecked, but result meaningless if not the selected group item) MethodBuilder getmeth = builder.addMethod(getname, gettype); getmeth.setPublic(); getmeth.createBlock().addReturnNamed(fname); // add the set method definition MethodBuilder setmeth = builder.addMethod(setname, "void"); setmeth.setPublic(); setmeth.addParameter(COLLECTION_VARIABLE_NAME, settype); BlockBuilder block = setmeth.createBlock(); generateSelectorSet(USE_SELECTION_SET_METHODS, value, block, builder); block.addAssignVariableToField(COLLECTION_VARIABLE_NAME, fname); // process list and collection differently for binding CollectionElement collect = null; if (value.isCollection()) { // create a new unless inside a wrapper element if (binding.type() == ElementBase.COLLECTION_ELEMENT) { if (((CollectionElement)binding).getField() == null) { collect = (CollectionElement)binding; } } if (collect == null) { collect = new CollectionElement(); binding.addChild(collect); } // fill in the collection details collect.setFieldName(fname); if (wrapper.isSelectorNeeded()) { collect.setUsage(PropertyAttributes.OPTIONAL_USAGE); collect.setTestName("if" + propname); } if (USE_COLLECTIONS) { collect.setCreateType(COLLECTION_INSTANCE_TYPE); } // check the content (if any) for boolean usevalue = true; String usetype = type; if (item instanceof ReferenceItem) { DefinitionItem definition = ((ReferenceItem)item).getDefinition(); ClassHolder defclas = definition.getGenerateClass(); if (defclas instanceof StructureClassHolder) { // reference to mapped class, configure to handle it properly usevalue = false; if (definition.getSchemaComponent().type() == SchemaBase.ELEMENT_TYPE) { // must be a non-abstract , so use it directly collect.setItemTypeName(defclas.getBindingName()); } else { // abstract mapping reference, create child with map-as type StructureElement struct = new StructureElement(); INamed named = (INamed)definition.getSchemaComponent(); QName qname = named.getQName(); holder.addDependency(qname.getUri()); struct.setMapAsQName(qname); AnnotatedBase comp = findNamedComponent(value); if (comp != null) { struct.setName(((INamed)comp).getName()); } collect.addChild(struct); } } else { usetype = defclas.getBindingName(); } } else if (item instanceof GroupItem) { // handle group directly if a structure class, else just as ClassHolder groupclas = ((GroupItem)item).getGenerateClass(); if (groupclas instanceof StructureClassHolder) { // add element to be filled in by inner class generation usevalue = false; StructureClassHolder classholder = ((StructureClassHolder)groupclas); StructureElement struct = new StructureElement(); struct.setDeclaredType(classholder.getBindingName()); // set attributes without field name (since that will be for collection) setStructureName(value, struct); setStructureAttributes(value, struct); struct.setFieldName(null); // set component for dependent class generation classholder.setBinding(struct); collect.addChild(struct); } else { usetype = groupclas.getBindingName(); } } if (usevalue) { // add element to collection for simple (primitive or text) value ValueElement element = new ValueElement(); element.setEffectiveStyle(NestingAttributes.ELEMENT_STYLE); AnnotatedBase comp = findNamedComponent(value); if (comp == null) { throw new IllegalStateException("Internal error - no name for value in collection"); } else { element.setName(((INamed)comp).getName()); } element.setDeclaredType(usetype); collect.addChild(element); } } else { // determine format conversion handling for type String valsername = null; String valdesername = null; String valuename = null; FormatElement format = (FormatElement)s_formatMap.get(type); if (format != null) { valsername = format.getSerializerName(); valdesername = format.getDeserializerName(); if (valsername == null && !"java.lang.String".equals(type)) { valuename = "toString"; } } else if (item instanceof ReferenceItem) { DefinitionItem def = ((ReferenceItem)item).getDefinition(); if (def.isEnumeration()) { EnumerationClassHolder genclas = (EnumerationClassHolder)def.getGenerateClass(); valsername = EnumerationClassHolder.CONVERTFORCE_METHOD; valuename = genclas.getName() + '.' + EnumerationClassHolder.INSTANCEVALUE_METHOD; } } // add list serializer method to class String sername = "serialize" + propname; MethodBuilder sermeth = builder.addMethod(sername, "java.lang.String"); sermeth.addParameter("values", (Type)builder.clone(settype)); sermeth.setPublicStatic(); // create a simple null return for null parameter string BlockBuilder nullblock = builder.newBlock(); nullblock.addReturnNull(); // create block for actual serialization when parameter non-null BlockBuilder serblock = builder.newBlock(); NewInstanceBuilder newbuff = builder.newInstance("java.lang.StringBuffer"); serblock.addLocalVariableDeclaration("java.lang.StringBuffer", "buff", newbuff); // create body of loop to handle the conversion BlockBuilder forblock = builder.newBlock(); if (USE_GENERIC_TYPES) { forblock.addLocalVariableDeclaration(type, "value", builder.createNormalMethodCall( "iter", "next")); } else if (USE_COLLECTIONS) { CastBuilder castexpr = builder.buildCast(type); castexpr.addOperand(builder.createNormalMethodCall("iter", "next")); forblock.addLocalVariableDeclaration(type, "value", castexpr); } else { forblock.addLocalVariableDeclaration(type, "value", builder.buildArrayIndexAccess( "values", "index")); } // append space to buffer unless empty InfixExpressionBuilder lengthexpr = builder.buildInfix(Operator.GREATER); lengthexpr.addOperand(builder.createNormalMethodCall("buff", "length")); lengthexpr.addNumberLiteralOperand("0"); InvocationBuilder appendcall = builder.createNormalMethodCall("buff", "append"); appendcall.addCharacterLiteralOperand(' '); BlockBuilder spaceblock = builder.newBlock(); spaceblock.addExpressionStatement(appendcall); forblock.addIfStatement(lengthexpr, spaceblock); // append the current value to the buffer appendcall = builder.createNormalMethodCall("buff", "append"); if (valuename != null) { appendcall.addOperand(builder.createNormalMethodCall("value", valuename)); } else if (valdesername != null) { InvocationBuilder desercall = builder.createStaticMethodCall(valdesername); desercall.addVariableOperand("value"); appendcall.addOperand(desercall); } else { appendcall.addVariableOperand("value"); } forblock.addExpressionStatement(appendcall); // build the for loop around the conversion if (USE_GENERIC_TYPES) { Type itertype = builder.createParameterizedType("java.util.Iterator", type); serblock.addIteratedForStatement("iter", itertype, builder.createNormalMethodCall( "values", "iterator"), forblock); } else if (USE_COLLECTIONS) { serblock.addIteratedForStatement("iter", builder.createType("java.util.Iterator"), builder.createNormalMethodCall("values", "iterator"), forblock); } else { serblock.addIndexedForStatement("iter", builder.createNormalMethodCall("values", "iterator"), forblock); } // finish non-null serialization block with buffer conversion serblock.addReturnExpression(builder.createNormalMethodCall("buff", "toString")); // finish with the if statement that decides which to execute InfixExpressionBuilder iftest = builder.buildNameOp("values", Operator.EQUALS); iftest.addNullOperand(); sermeth.createBlock().addIfElseStatement(iftest, nullblock, serblock); // add list deserializer method to class String desername = "deserialize" + propname; MethodBuilder desermeth = builder.addMethod(desername, (Type)builder.clone(gettype)); desermeth.addParameter("text", "java.lang.String"); desermeth.setPublicStatic(); desermeth.addThrows("org.jibx.runtime.JiBXException"); block = desermeth.createBlock(); // build instance creation for anonymous inner class to handle deserialization NewInstanceBuilder newinst = builder.newInstance("org.jibx.runtime.IListItemDeserializer"); ClassBuilder anonclas = newinst.addAnonymousInnerClass(); MethodBuilder innermeth = anonclas.addMethod("deserialize", "java.lang.Object"); innermeth.addParameter("text", "java.lang.String"); innermeth.setPublic(); BlockBuilder innerblock = innermeth.createBlock(); if (valdesername == null) { innerblock.addReturnNamed("text"); } else { InvocationBuilder desercall = builder.createLocalStaticMethodCall(valdesername); desercall.addVariableOperand("text"); innerblock.addReturnExpression(desercall); } block.addLocalVariableDeclaration("org.jibx.runtime.IListItemDeserializer", "ldser", newinst); // build call using anonymous inner class to deserialize to untyped collection InvocationBuilder desercall = builder .createStaticMethodCall("org.jibx.runtime.Utility.deserializeList"); desercall.addVariableOperand("text"); desercall.addVariableOperand("ldser"); // handle the return as appropriate if (USE_GENERIC_TYPES) { CastBuilder castexpr = builder.buildCast((Type)builder.clone(gettype)); castexpr.addOperand(desercall); block.addReturnExpression(castexpr); } else if (USE_COLLECTIONS) { block.addReturnExpression(desercall); } else { // save deserialization result list to local variable block.addLocalVariableDeclaration("java.util.List", "list", desercall); // create null return block BlockBuilder ifnull = builder.newBlock(); ifnull.addReturnNull(); // create non-null return with conversion to array BlockBuilder ifnonnull = builder.newBlock(); InvocationBuilder toarraycall = builder.createNormalMethodCall("list", "toArray"); NewArrayBuilder newarray = builder.newArrayBuilder(type); newarray.addOperand(builder.createNormalMethodCall("list", "size")); toarraycall.addOperand(newarray); CastBuilder castexpr = builder.buildCast((Type)builder.clone(fieldtype)); castexpr.addOperand(toarraycall); ifnonnull.addReturnExpression(castexpr); // finish with the if statement that decides which to execute iftest = builder.buildNameOp("list", Operator.EQUALS); iftest.addNullOperand(); block.addIfElseStatement(iftest, ifnull, ifnonnull); } // handle list serialization and deserialization directly ValueElement valbind = buildValueBinding(value, propname); valbind.setSerializerName(getBindingName() + '.' + sername); valbind.setDeserializerName(getBindingName() + '.' + desername); binding.addChild(valbind); } if (EXTRA_COLLECTION_METHODS && (USE_GENERIC_TYPES || USE_COLLECTIONS)) { // add size method String sizename = "size" + propname; MethodBuilder sizemeth = builder.addMethod(sizename, "int"); sizemeth.setPublic(); InvocationBuilder expr = builder.createNormalMethodCall(fname, "size"); sizemeth.createBlock().addReturnExpression(expr); // add typed indexed get method (AKA binding load-method) String itemname = item.getEffectiveName(); String itemprop = NameConverter.toNameWord(itemname); String loadname = "get" + itemprop; MethodBuilder getitem = builder.addMethod(loadname, type); getitem.setPublic(); getitem.addParameter("index", "int"); expr = builder.createNormalMethodCall(fname, "get"); expr.addVariableOperand("index"); if (USE_GENERIC_TYPES) { getitem.createBlock().addReturnExpression(expr); } else { CastBuilder cast = builder.buildCast(type); cast.addOperand(expr); getitem.createBlock().addReturnExpression(cast); } // add typed add method String addname = "add" + itemprop; MethodBuilder additem = builder.addMethod(addname, "void"); additem.setPublic(); additem.addParameter("item", type); block = additem.createBlock(); expr = builder.createNormalMethodCall(fname, "add"); expr.addVariableOperand("item"); block.addExpressionStatement(expr); // add methods to binding description, if used (only if not using fields directly) // if (collect != null) { // collect.setSizeMethodName(sizename); // collect.setLoadMethodName(loadname); // collect.setAddMethodName(addname); // } } } else { // generate the field as a simple value FieldBuilder field = builder.addField(fname, type); field.setPrivate(); // add get method definition (unchecked, but result meaningless if not the selected group item) MethodBuilder getmeth = builder.addMethod(getname, type); getmeth.setPublic(); getmeth.createBlock().addReturnNamed(fname); // add the set method definition MethodBuilder setmeth = builder.addMethod(setname, "void"); setmeth.setPublic(); setmeth.addParameter(basename, type); BlockBuilder block = setmeth.createBlock(); generateSelectorSet(USE_SELECTION_SET_METHODS, value, block, builder); block.addAssignVariableToField(basename, fname); // build the appropriate binding representation StructureElement struct = null; if (item instanceof ReferenceItem) { // handle reference directly if a structure class, else just as value DefinitionItem def = ((ReferenceItem)item).getDefinition(); ClassHolder defclas = def.getGenerateClass(); if (defclas instanceof StructureClassHolder) { // add element for field, with type name if needed struct = new StructureElement(); if (def.getSchemaComponent().type() != SchemaBase.ELEMENT_TYPE) { QName qname = ((INamed)def.getSchemaComponent()).getQName(); holder.addDependency(qname.getUri()); struct.setMapAsQName(qname); } // fill in structure attributes setStructureName(value, struct); setStructureAttributes(value, struct); /* // special case: delete the name if already set on containing binding component if (struct.getName() != null) { boolean duplname = false; if (binding instanceof StructureElement) { duplname = struct.getName().equals(((StructureElement)binding).getName()); } else if (binding instanceof MappingElement) { duplname = struct.getName().equals(((MappingElement)binding).getName()); } if (duplname) { // same name reused, check if named wrapper relate to same schema component Wrapper ancestor = wrapper; while (!ancestor.isNamed() && ancestor.getValues().size() == 1) { ancestor = ancestor.getWrapper(); } if (ancestor.getSchemaComponent() == item.getSchemaComponent()) { // same name for same schema component, wipe it from this struct.setName(null); } } } */ } } else if (item instanceof GroupItem) { // handle group directly if a structure class, else just as value ClassHolder groupclas = ((GroupItem)item).getGenerateClass(); if (groupclas instanceof StructureClassHolder) { // add element to be filled in by inner class generation struct = new StructureElement(); setStructureName(value, struct); setStructureAttributes(value, struct); ((StructureClassHolder)groupclas).setBinding(struct); } } if (struct == null) { // add simple binding for field binding.addChild(buildValueBinding(value, propname)); } else { // common handling for structure if (wrapper.isSelectorNeeded()) { struct.setUsage(PropertyAttributes.OPTIONAL_USAGE); struct.setTestName("if" + propname); } // add to containing binding component binding.addChild(struct); } } } } } // finish with check that names have been used AnnotatedBase comp = findNamedComponent(wrapper); if (comp != null) { throw new IllegalStateException("Internal error - name '" + ((INamed)comp).getName() + "' not used in binding"); } } /** * Generate this class. * * @param builder class source file builder * @param holder binding holder */ public void generate(SourceBuilder builder, BindingHolder holder) { // setup the class builder String basename = getSuperClass() == null ? null : getSuperClass().getFullName(); ClassBuilder clasbuilder; String name = getName(); if (m_outerClass == null) { clasbuilder = builder.newMainClass(name, basename, false); } else { clasbuilder = builder.newInnerClass(name, basename, m_outerClass.getBuilder(), false); } // handle the common initialization setBuilder(clasbuilder); initClass(m_classGroup, holder); m_classGroup.setNameUsed(true); // add nested definitions to if (m_bindingElement instanceof MappingElement) { addInnerFormats((MappingElement)m_bindingElement); } // fix all the value names String fullname = getFullName(); if (s_logger.isInfoEnabled()) { s_logger.info("Generating class " + fullname + ":\n" + m_classGroup.describe(0)); } addFixedNames(m_classGroup); fixFlexibleNames(m_classGroup, false); if (s_logger.isInfoEnabled()) { s_logger.info("Class " + fullname + " after names fixed:\n" + m_classGroup.describe(0)); } // check for superclass handling needed if (basename != null) { StructureElement struct = new StructureElement(); AnnotatedBase comp = ((StructureClassHolder)getSuperClass()).m_classGroup.getSchemaComponent(); QName qname = ((INamed)comp).getQName(); holder.addDependency(qname.getUri()); struct.setMapAsQName(qname); m_bindingElement.addChild(struct); } buildDataModel(m_classGroup, clasbuilder, m_bindingElement, holder); // generate the binding code // m_classBuilder.addInterface("org.jibx.v2.MappedStructure"); // finish with subclass generation (which might be needed, if enumeration uses inlined type) generateInner(builder, holder); /* * // add the binding code for top-level classes if (m_generateCategory == TYPE_CLASS) { * m_classBuilder.addInterface(TYPE_INTERFACE); FieldBuilder field = m_classBuilder.addField(TYPE_NAME_VARIABLE, * QNAME_TYPE); field.setPrivateStaticFinal(); QName qname = ((INamed)comp).getQName(); * field.setInitializer(m_classBuilder.newInstanceFromStrings(QNAME_TYPE, qname.getUri(), qname.getName())); * MethodBuilder namemeth = m_classBuilder.addMethod(TYPE_NAME_METHOD, QNAME_TYPE); namemeth.setPublic(); * BlockBuilder block = namemeth.createBlock(); block.addReturnNamed(TYPE_NAME_VARIABLE); } else if * (m_generateCategory == ELEMENT_CLASS) { m_classBuilder.addInterface(ELEMENT_INTERFACE); FieldBuilder field = * m_classBuilder.addField(ELEMENT_NAME_VARIABLE, QNAME_TYPE); field.setPrivateStaticFinal(); QName qname = * ((INamed)comp).getQName(); field.setInitializer(m_classBuilder.newInstanceFromStrings(QNAME_TYPE, * qname.getUri(), qname.getName())); MethodBuilder namemeth = m_classBuilder.addMethod(ELEMENT_NAME_METHOD, * QNAME_TYPE); namemeth.setPublic(); BlockBuilder block = namemeth.createBlock(); * block.addReturnNamed(ELEMENT_NAME_VARIABLE); } else if (m_generateCategory == STRUCTURE_CLASS) { * m_classBuilder.addInterface(STRUCTURE_INTERFACE); } if (m_generateCategory != NONBOUND_CLASS) { * InfixExpressionBuilder ifexpr = m_generateCategory == TYPE_CLASS ? null : * m_classBuilder.buildInfix(Operator.OR); if (m_generateCategory != TYPE_CLASS) { // how to handle if present * unmarshalling method? the logic will look like: // if (rdr.isAt("a") || rdr.isAt("b") || ...) { // if (inst == * null || inst.getClass().getName() != "...") { // inst = new ...(); // } // inst._unmarshal(rdr); // } else { // * inst = null; // } // return inst; // so just pass an expression builder? } MethodBuilder marmeth = * m_classBuilder.addMethod(MARSHAL_METHOD, "void"); marmeth.setPublic(); marmeth.addParameter(WRITER_VARNAME, * WRITER_TYPE); MethodBuilder umarmeth = m_classBuilder.addMethod(UNMARSHAL_METHOD, "void"); * umarmeth.setPublic(); umarmeth.addParameter(READER_VARNAME, READER_TYPE); // bindGroup(m_classGroup, * marmeth.createBlock(), ifexpr, umarmeth.createBlock()); } */ } }