/*
* Copyright (c) 2007-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 org.jibx.schema.SchemaUtils;
import org.jibx.schema.codegen.custom.ComponentExtension;
import org.jibx.schema.elements.AnnotatedBase;
import org.jibx.schema.elements.ElementElement;
import org.jibx.schema.elements.SchemaBase;
/**
* Base class for code generation items. Each instance corresponds to a particular schema component, and this base class
* tracks that schema component (by way of the extension information), along with related details and linkage
* information. The linkage uses embedded list links, which allows replacing one instance with another with minimal
* overhead.
*
* @author Dennis M. Sosnoski
*/
public abstract class Item
{
/** Corresponding schema component extension. */
private final ComponentExtension m_componentExtension;
/** Flag for an optional item. */
private final boolean m_optional;
/** Flag for a collection item. */
private final boolean m_collection;
/** Flag for a nillable item. */
private final boolean m_nillable;
/** Containing group item. */
private GroupItem m_parent;
/** Attribute data present flag. */
private boolean m_attributePresent;
/** Element data present flag. */
private boolean m_elementPresent;
/** Character data content data present flag. */
private boolean m_contentPresent;
/** Next item in list (null
if none). */
protected Item m_next;
/** Preceding item in list (null
if none). */
protected Item m_last;
/** Actual name to be used for item (null
if to be inherited). */
private String m_name;
/**
* Basic constructor. This uses the schema component to determine all information other than the parent item group,
* including the optional/nillable/collection flags. As a special case, if the parent group is associated with the
* same component this sets all three of these flags false
to avoid redundant handling.
*
* @param comp schema component
* @param parent containing group (null
if a top-level group)
*/
protected Item(AnnotatedBase comp, GroupItem parent) {
// save basic values
m_componentExtension = (ComponentExtension)comp.getExtension();
m_parent = parent;
// set other values based on extension and component information
if (parent == null || parent.getSchemaComponent() != comp) {
m_optional = m_componentExtension.isOptional();
m_collection = m_componentExtension.isRepeated();
m_nillable = comp.type() == SchemaBase.ELEMENT_TYPE && ((ElementElement)comp).isNillable();
} else {
m_optional = m_collection = m_nillable = false;
}
m_name = m_componentExtension.getBaseName();
}
/**
* Copy constructor. This creates a copy with a new parent.
*
* @param original
* @param ref reference (for name override; null
if none)
* @param parent
*/
protected Item(Item original, Item ref, GroupItem parent) {
m_componentExtension = original.m_componentExtension;
m_parent = parent;
m_optional = original.m_optional;
m_collection = original.m_collection;
m_nillable = original.m_nillable;
m_attributePresent = original.m_attributePresent;
m_elementPresent = original.m_elementPresent;
m_contentPresent = original.m_contentPresent;
if (ref == null || original.isFixedName()) {
m_name = original.m_name;
} else {
m_name = ref.m_name;
}
}
/**
* Replace the parent for this item.
*
* @param parent
*/
protected void reparent(GroupItem parent) {
m_parent = parent;
}
/**
* Get schema component corresponding to this item.
*
* @return schema component
*/
public AnnotatedBase getSchemaComponent() {
return (AnnotatedBase)m_componentExtension.getComponent();
}
/**
* Get schema component annotation corresponding to this item.
*
* @return schema component
*/
public ComponentExtension getComponentExtension() {
return m_componentExtension;
}
/**
* Get containing group item.
*
* @return group (null
if a top-level group)
*/
public GroupItem getParent() {
return m_parent;
}
/**
* Check if the name is fixed by configuration.
*
* @return true
if fixed, false
if not
*/
public boolean isFixedName() {
return m_componentExtension.getBaseName() != null;
}
/**
* Get effective item name, applying inheritance if necessary.
*
* @return name
*/
public String getEffectiveName() {
Item item = this;
while (item.m_name == null) {
item = item.m_parent;
if (item == null) {
throw new IllegalStateException("Inherited name with nothing to inherit");
}
}
return item.m_name;
}
/**
* Get name set directly for this item.
*
* @return name (null
if to be inherited)
*/
public String getName() {
return m_name;
}
/**
* Set name directly for this item. It is an error to call this method if the name is fixed.
*
* @param name (null
if to be inherited)
*/
public void setName(String name) {
if (isFixedName()) {
throw new IllegalStateException("Internal error - attempt to change configured name");
} else {
m_name = name;
}
}
/**
* Get next item in list.
*
* @return next
*/
public Item getNext() {
return m_next;
}
/**
* Check if item is optional.
*
* @return optional
*/
public boolean isOptional() {
return m_optional;
}
/**
* Check if a collection item.
*
* @return true
if collection
*/
public boolean isCollection() {
return m_collection;
}
/**
* Check if an attribute is part of this item. This is only true
for items corresponding to attribute
* definitions, and groupings including these items which do not define an element name.
*
* @return true
if attribute
*/
public boolean isAttributePresent() {
return m_attributePresent;
}
/**
* Check if a child elements is part of this item. This is true
for all items corresponding to element
* definitions, and all groupings which include such an item.
*
* @return true
if content
*/
public boolean isElementPresent() {
return m_elementPresent;
}
/**
* Check if character data content is part of this item. This is true
for all items corresponding to
* simpleContent definitions, and all groupings which include such an item.
*
* @return true
if content
*/
public boolean isContentPresent() {
return m_contentPresent;
}
/**
* Set attribute present in group. This cascades the attribute present flag upward through containing groups until
* one is found which defines an element name.
*/
protected void forceAttributePresent() {
if (!m_attributePresent && m_componentExtension.getComponent().type() != SchemaBase.ELEMENT_TYPE) {
if (m_parent != null) {
((Item)m_parent).forceAttributePresent();
}
}
m_attributePresent = true;
}
/**
* Set element present in group. This cascades the element present flag upward through containing groups until
* one is found which defines an element name.
*/
protected void forceElementPresent() {
if (!m_elementPresent && m_componentExtension.getComponent().type() != SchemaBase.ELEMENT_TYPE) {
if (m_parent != null) {
((Item)m_parent).forceElementPresent();
}
}
m_elementPresent = true;
}
/**
* Set character data content present in group. This cascades the content present flag upward through all containing
* groups until one is found which defines an element name.
*/
protected void forceContentPresent() {
if (!m_contentPresent && m_componentExtension.getComponent().type() != SchemaBase.ELEMENT_TYPE) {
if (m_parent != null) {
((Item)m_parent).forceContentPresent();
}
}
m_contentPresent = true;
}
/**
* Copy the item under a different parent. This is intended for replacing a reference with a copy, and allows the
* reference to override settings from the original.
*
* @param ref reference (for overrides to copy; null
if none)
* @param parent
* @return copy
*/
protected abstract Item copy(Item ref, GroupItem parent);
/**
* Classify the content of this item as attribute, element, and/or character data content. This needs to be done as
* a separate step after construction in order to handle references, which must assume the content of the
* definition, and also to work after inlining. This base class implementation does the classification based solely
* on the schema component type. Any subclasses which override this method should call the base class implementation
* before doing their own classification handling.
*/
protected void classifyContent() {
// find the parent group with a different schema component
GroupItem parent = getParent();
AnnotatedBase comp = getSchemaComponent();
while (parent != null && parent.getSchemaComponent() == comp) {
parent = parent.getParent();
}
if (parent != null) {
// flag content type for that parent group
switch (comp.type()) {
case SchemaBase.ANY_TYPE:
case SchemaBase.ELEMENT_TYPE:
case SchemaBase.GROUP_TYPE:
parent.forceElementPresent();
break;
case SchemaBase.SIMPLECONTENT_TYPE:
parent.forceContentPresent();
break;
case SchemaBase.ANYATTRIBUTE_TYPE:
case SchemaBase.ATTRIBUTE_TYPE:
case SchemaBase.ATTRIBUTEGROUP_TYPE:
parent.forceAttributePresent();
break;
}
}
}
/**
* Generate a description of the item. For items with nested items this will show the complete structure.
*
* @param depth current nesting depth
* @return description
*/
public abstract String describe(int depth);
/**
* Generate the standard leading text for description of the item.
*
* @param depth current nesting depth
* @return leading text for description
*/
public String leadString(int depth) {
StringBuffer buff = new StringBuffer(SchemaUtils.getIndentation(depth));
if (isOptional()) {
buff.append("optional ");
}
if (isCollection()) {
buff.append("repeating ");
}
if (m_attributePresent) {
if (m_contentPresent) {
buff.append("attribute+content ");
} else {
buff.append("attribute ");
}
} else if (m_contentPresent) {
buff.append("content ");
}
return buff.toString();
}
}