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