/*
* 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.apache.log4j.Logger;
import org.jibx.runtime.QName;
import org.jibx.schema.SchemaUtils;
import org.jibx.schema.codegen.custom.ComponentCustom;
import org.jibx.schema.codegen.custom.ComponentExtension;
import org.jibx.schema.codegen.custom.CustomBase;
import org.jibx.schema.elements.AnnotatedBase;
import org.jibx.schema.elements.SchemaBase;
/**
* Information for a grouping of components (attributes, elements, compositors, and/or wildcards). This is used both
* directly for local groupings, and by way of the {@link DefinitionItem} subclass for global definitions.
*/
public class GroupItem extends Item
{
/** Logger for class. */
private static final Logger s_logger = Logger.getLogger(GroupItem.class.getName());
/** Flag for enumeration value. */
private boolean m_enumeration;
/** Inline references to this structure. */
private boolean m_inline;
/** Name to be used for generated class (null
if inherited). */
private String m_className;
/** Number of child items in group. */
private int m_size;
/** First child (null
if none). */
private Item m_head;
/** Last child (null
if none). */
private Item m_tail;
/** Generated class information (null
if inlined). */
private ClassHolder m_generateClass;
/**
* Internal constructor. This is used both for creating a new child group and by the {@link DefinitionItem}
* subclass, so allows for specifying both an optional parent and the actual type of the item.
*
* @param comp schema component (should be the simpleType component in the case of an enumeration)
* @param parent (null
if none)
*/
protected GroupItem(AnnotatedBase comp, GroupItem parent) {
super(comp, parent);
ComponentExtension exten = getComponentExtension();
m_className = exten.getClassName();
ComponentCustom custom = exten.getCustom();
if (custom != null) {
m_inline = custom.getGeneration() == CustomBase.GENERATE_INLINE;
}
}
/**
* Copy constructor. This creates a deep copy with a new parent.
*
* @param original
* @param ref reference (for overrides to copy; null
if none)
* @param parent
*/
/*package*/ GroupItem(GroupItem original, Item ref, GroupItem parent) {
super(original, ref, parent);
m_enumeration = original.m_enumeration;
for (Item child = original.getFirstChild(); child != null; child = child.getNext()) {
appendChild(child.copy(null, this));
}
m_inline = original.m_inline;
m_className = original.m_className;
}
/**
* Constructor from a reference. This is only used for inlining a referenced definition. It merges usage information
* from the reference with a deep copy of the item structure of the definition.
*
* @param reference
*/
/*package*/ GroupItem(ReferenceItem reference) {
super(reference, reference, reference.getParent());
m_inline = true;
DefinitionItem definition = reference.getDefinition();
appendChild(new GroupItem(definition, reference, this));
}
/**
* Check if this value represents an enumeration.
*
* @return enumeration
*/
public boolean isEnumeration() {
return m_enumeration;
}
/**
* Set value represents an enumeration flag.
*
* @param enumeration
*/
public void setEnumeration(boolean enumeration) {
m_enumeration = enumeration;
}
/**
* Append an item to the list of children.
*
* @param item
*/
private void appendChild(Item item) {
if (m_head == null) {
m_head = m_tail = item;
} else {
m_tail.m_next = item;
item.m_last = m_tail;
m_tail = item;
}
m_size++;
}
/**
* Add a child grouping structure.
*
* @param comp schema component
* @return structure
*/
public GroupItem addGroup(AnnotatedBase comp) {
GroupItem group = new GroupItem(comp, this);
appendChild(group);
return group;
}
/**
* Add a child reference structure.
*
* @param comp schema component
* @param ref referenced definition item
* @return reference
*/
public ReferenceItem addReference(AnnotatedBase comp, DefinitionItem ref) {
ReferenceItem reference = new ReferenceItem(comp, this, ref);
appendChild(reference);
return reference;
}
/**
* Add a child value.
*
* @param comp schema component extension
* @param type schema type name
* @param ref schema type equivalent (null
if not appropriate)
* @return value
*/
public ValueItem addValue(AnnotatedBase comp, QName type, JavaType ref) {
ValueItem item = new ValueItem(comp, type, ref, this);
appendChild(item);
return item;
}
/**
* Replace an item in this group with another item.
*
* @param current
* @param replace
*/
/*package*/ void replaceChild(Item current, Item replace) {
Item last = current.m_last;
Item next = current.m_next;
if (last == null) {
m_head = replace;
} else {
last.m_next = replace;
}
replace.m_last = last;
if (next == null) {
m_tail = replace;
} else {
next.m_last = replace;
}
replace.m_next = next;
}
/**
* Adopt the child items from another group as the child items of this group.
*
* @param group
*/
void adoptChildren(GroupItem group) {
m_size = group.m_size;
m_head = group.m_head;
m_tail = group.m_tail;
for (Item item = m_head; item != null; item = item.m_next) {
item.reparent(this);
}
}
/**
* Check if structure to be inlined.
*
* @return inline
*/
public boolean isInline() {
return m_inline;
}
/**
* Set structure to be inlined flag.
*
* @param inline
*/
public void setInline(boolean inline) {
m_inline = inline;
}
/**
* Get effective item name, applying inheritance if necessary.
*
* @return name
*/
public String getEffectiveClassName() {
GroupItem item = this;
while (item.getClassName() == null) {
item = item.getParent();
if (item == null) {
throw new IllegalStateException("Inherited class name with nothing to inherit");
}
}
return item.getClassName();
}
/**
* Get class name set directly for this group.
*
* @return name (null
if to be inherited)
*/
public String getClassName() {
return m_className;
}
/**
* Check if the class name is fixed by configuration.
*
* @return true
if fixed, false
if not
*/
public boolean isFixedClassName() {
return getComponentExtension().getClassName() != null;
}
/**
* Set class name directly for this group. It is an error to call this method if the class name is fixed.
*
* @param name (null
if to be inherited)
*/
public void setClassName(String name) {
if (isFixedClassName()) {
throw new IllegalStateException("Internal error - attempt to change configured class name");
} else {
m_className = name;
}
}
/**
* Get the number of items present in the group.
*
* @return count
*/
public int getChildCount() {
return m_size;
}
/**
* Get head item in list grouped by this structure.
*
* @return item (null
if none)
*/
public Item getFirstChild() {
return m_head;
}
/**
* Get information for class to be generated.
*
* @return class
*/
public ClassHolder getGenerateClass() {
return m_generateClass;
}
/**
* Set information for class to be generated. If this group is a complexType extension and the base type is not
* being inlined, this sets the generated class to extend the base type class.
*
* @param clas
*/
public void setGenerateClass(ClassHolder clas) {
m_generateClass = clas;
}
/**
* Check if this group represents an extension reference.
*
* @return true
if extension reference, false
if not
*/
public boolean isExtensionReference() {
return m_head instanceof ReferenceItem && m_head.getSchemaComponent().type() == SchemaBase.EXTENSION_TYPE;
}
/**
* Handle an extension reference to a non-inlined type by subclassing the corresponding class. This removes the
* extension reference, if found, from the data structure.
*/
public void convertExtensionReference() {
if (isExtensionReference()) {
ClassHolder base = ((ReferenceItem)m_head).getDefinition().getGenerateClass();
if (s_logger.isDebugEnabled()) {
s_logger.debug("Setting base class for " + m_generateClass.getFullName() + " to " +
base.getFullName());
}
m_generateClass.setSuperClass(base);
m_head = m_head.m_next;
if (m_head == null) {
m_tail = null;
} else {
m_head.m_last = null;
}
}
}
/**
* Copy the item under a different parent.
*
* @param ref reference (for overrides to copy; null
if none)
* @param parent
* @return copy
*/
protected Item copy(Item ref, GroupItem parent) {
return new GroupItem(this, ref, parent);
}
/**
* Classify the content of this item as attribute, element, and/or character data content. For a group item, this
* just needs to call the corresponding method for each child item.
*/
protected void classifyContent() {
// handle basic classification for this component
super.classifyContent();
// classify each child component
for (Item item = m_head; item != null; item = item.getNext()) {
item.classifyContent();
}
}
/**
* Convert an embedded group to a freestanding definition. This creates a definition using a cloned copy of the
* structure of this group, then replaces this group with a reference to the definition.
* TODO: just adopt the child items, rather than cloning? minor performance gain.
*
* @return definition
*/
public DefinitionItem convertToDefinition() {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Converting " + SchemaUtils.describeComponent(getSchemaComponent()) +
" to freestanding definition");
}
DefinitionItem def = new DefinitionItem(this);
if (def.getClassName() == null) {
def.setClassName(getEffectiveClassName());
}
if (def.getName() == null) {
def.setName(getEffectiveName());
}
ReferenceItem ref = new ReferenceItem(this, def);
getParent().replaceChild(this, ref);
return def;
}
/**
* Build description of nested items.
*
* @param depth current nesting depth
* @return description
*/
public String nestedString(int depth) {
depth++;
Item child = getFirstChild();
StringBuffer buff = new StringBuffer(400);
while (child != null) {
buff.append(child.describe(depth));
child = child.getNext();
}
return buff.toString();
}
/**
* Generate a description of the item, including all nested items.
*
* @param depth current nesting depth
* @return description
*/
public String describe(int depth) {
StringBuffer buff = new StringBuffer(depth + 50);
buff.append(leadString(depth));
if (isInline()) {
buff.append("inlined ");
}
if (m_enumeration) {
buff.append("enumeration ");
}
buff.append("group with class name ");
buff.append(getClassName());
buff.append(" and value name ");
buff.append(getName());
buff.append(": ");
buff.append(SchemaUtils.describeComponent(getSchemaComponent()));
buff.append('\n');
buff.append(nestedString(depth));
return buff.toString();
}
}