/*
* 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()); }
*/
}
}