/*
 * 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.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.jibx.binding.classes.ClassItem;
import org.jibx.binding.generator.UniqueNameSet;
import org.jibx.binding.model.BindingHolder;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IMarshallable;
import org.jibx.runtime.IMarshallingContext;
import org.jibx.runtime.JiBXException;
import org.jibx.schema.INamed;
import org.jibx.schema.SchemaUtils;
import org.jibx.schema.elements.AnnotatedBase;
import org.jibx.schema.elements.SchemaBase;
import org.jibx.schema.elements.SchemaElement;
import org.jibx.schema.support.LazyList;

/**
 * Information for a class to be included in code generated from schema.
 * 
 * @author Dennis M. Sosnoski
 */
public abstract class ClassHolder
{
    // collection constants
    protected static final String COLLECTION_INSTANCE_TYPE = "java.util.ArrayList";
    protected static final String COLLECTION_VARIABLE_TYPE = "java.util.List";
    protected static final String COLLECTION_VARIABLE_NAME = "list";
    
    // general definitions used in binding code generation
//    protected static final String STATIC_UNMARSHAL_METHOD = "_unmarshal_if_present";
//    protected static final String STRUCTURE_INTERFACE = "org.jibx.v2.MappedStructure";
//    protected static final String MARSHAL_METHOD = "_marshal";
//    protected static final String WRITER_TYPE = "org.jibx.v2.XmlWriter";
//    protected static final String WRITER_VARNAME = "wrtr";
//    protected static final String UNMARSHAL_METHOD = "_unmarshal";
//    protected static final String QNAME_TYPE = "org.jibx.runtime.QName";
//    protected static final String TYPE_INTERFACE = "org.jibx.v2.MappedType";
//    protected static final String TYPE_NAME_METHOD = "_get_type_qname";
//    protected static final String TYPE_NAME_VARIABLE = "_type_name";
//    protected static final String ELEMENT_INTERFACE = "org.jibx.v2.MappedElement";
//    protected static final String ELEMENT_NAME_METHOD = "_get_element_qname";
//    protected static final String ELEMENT_NAME_VARIABLE = "_element_name";
//    protected static final String INSTANCE_VARNAME = "inst";
    
    // reader definitions
//    protected static final String READER_TYPE = "org.jibx.v2.XmlReader";
//    protected static final String READER_VARNAME = "rdr";
//    protected static final String READER_CHECK_START_TAG_METHOD = "checkStartTag";
    
    // TODO: make these customization flags
    protected static final boolean USE_COLLECTIONS = true;
    protected static final boolean USE_GENERIC_TYPES = false;
    protected static final boolean INCLUDE_SCHEMA_COMMENTS = true;
    protected static final boolean USE_JAVA5_ENUM = false;
    protected static final boolean IMPLEMENT_SERIALIZABLE = false;
    protected static final boolean EXTRA_COLLECTION_METHODS = true;
    protected static final boolean USE_SELECTION_SET_METHODS = true;
    public static final boolean FORCE_COLLECTION_WRAPPER = false;
    public static final boolean TRIM_COMMON_SUFFIXES = false;
    public static final boolean ADD_GLOBAL_ELEMENTS = false;
//    protected static final boolean USE_COLLECTIONS = true;
//    protected static final boolean USE_GENERIC_TYPES = true;
//    protected static final boolean INCLUDE_SCHEMA_COMMENTS = true;
//    protected static final boolean USE_JAVA5_ENUM = true;
//    protected static final boolean IMPLEMENT_SERIALIZABLE = true;
//    protected static final boolean EXTRA_COLLECTION_METHODS = true;
//    protected static final boolean USE_SELECTION_SET_METHODS = true;
//    public static final boolean FORCE_COLLECTION_WRAPPER = false;
//    public static final boolean TRIM_COMMON_SUFFIXES = false;
//    public static final boolean ADD_GLOBAL_ELEMENTS = true;
    
    // different categories of class generation
//    private static final int NONBOUND_CLASS = 0;
//    private static final int TYPE_CLASS = 1;
//    private static final int ELEMENT_CLASS = 2;
//    private static final int STRUCTURE_CLASS = 3;
    
    private static final IMarshallingContext s_schemaMarshaller;
    static {
        try {
            if (INCLUDE_SCHEMA_COMMENTS) {
                IBindingFactory factory = BindingDirectory.getFactory(SchemaElement.class);
                s_schemaMarshaller = factory.createMarshallingContext();
                s_schemaMarshaller.setIndent(2, "\n * ", ' ');
            } else {
                s_schemaMarshaller = null;
            }
        } catch (JiBXException e) {
            throw new IllegalStateException("Internal error - unable to access schema binding: " + e.getMessage());
        }
    }
    
    /** Logger for class. */
    private static final Logger s_logger = Logger.getLogger(ClassHolder.class.getName());
    
    //
    // Private instance data.
    
    /** Simple class name. */
    private final String m_name;
    
    /** Package containing class. */
    protected final PackageHolder m_package;
    
    /** Fully-qualified class name. */
    private final String m_fullName;
    
    /** Class name as used for binding (with '$' marker for inner class). */
    private final String m_bindingName;
    
    /** Set of imported classes. */
    private final TreeSet m_importedTypes;
    
    /** Map from simple names of imported types to full names. */
    private final Map m_unqualifiedTypes;
    
    /** Schema namespace for component associated with class. */
    private String m_namespaceUri;
    
    /** Superclass to be extended. */
    private ClassHolder m_superClass;
    
    /** Class generated flag. */
    private boolean m_generated;
    
    /** Builder for class. */
    private ClassBuilder m_classBuilder;
    
    //
    // Data shared with subclasses.
    
    /** Name conversion handler. */
    protected final NameConverter m_nameConverter;
    
    /** Base class name (for use when generating separate classes for nested structures). */
    protected final String m_baseName;
    
    /** Use inner classes for substructures flag. */
    protected final boolean m_useInnerClasses;
    
    /** Holders for inner classes defined within this class (<code>null</code> if an inner class). */
    protected final LazyList m_inners;
    
    /** Containing class (<code>null</code> if not an inner class). */
    protected final ClassHolder m_outerClass;
    
    /** Value names used in class. */
    protected UniqueNameSet m_nameSet;
    
    /**
     * 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 ClassHolder(String name, String base, PackageHolder pack, NameConverter nconv, boolean inner) {
        m_package = pack;
        m_nameConverter = nconv;
        m_name = name;
        m_baseName = base;
        StringBuffer buff = new StringBuffer();
        buff.append(pack.getName());
        if (buff.length() > 0) {
            buff.append('.');
        }
        buff.append(name);
        m_fullName = m_bindingName = buff.toString();
        m_useInnerClasses = inner;
        m_importedTypes = new TreeSet();
        m_unqualifiedTypes = new HashMap();
        m_outerClass = null;
        m_inners = new LazyList();
        m_nameSet = new UniqueNameSet();
        m_nameSet.add(name);
        addLocalType(name, m_fullName);
    }
    
    /**
     * Constructor for creating a child inner class definition.
     * 
     * @param name class name
     * @param context parent class
     */
    protected ClassHolder(String name, ClassHolder context) {
        m_package = context.m_package;
        m_nameConverter = context.m_nameConverter;
        m_name = name;
        m_baseName = null;
        m_fullName = context.m_fullName + '.' + name;
        m_bindingName = context.m_bindingName + '$' + name;
        m_useInnerClasses = true;
        m_importedTypes = context.m_importedTypes;
        m_unqualifiedTypes = context.m_unqualifiedTypes;
        m_outerClass = context;
        m_inners = new LazyList();
        m_nameSet = new UniqueNameSet(context.m_nameSet);
        addLocalType(name, m_fullName);
        m_package.addInnerClass(this);
    }
    
    /**
     * Derive group names from the containing group prefix and the simple name of the group.
     *
     * @param group
     * @param container (<code>null</code> 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;
//    }
    
    /**
     * Import the type associated with an item, if not directly accessible
     *
     * @param value
     */
    protected void importValueType(Value value) {
        String type = value.getType();
        if (type != null) {
            while (type.endsWith("[]")) {
                type = type.substring(0, type.length()-2);
            }
            if (!ClassItem.isPrimitive(type)) {
                ClassHolder outer = this;
                while (outer.m_outerClass != null) {
                    outer = outer.m_outerClass;
                }
                if (!type.startsWith(outer.getFullName())) {
                    addImport(type, false);
                }
            }
        }
    }
    
    /**
     * Convert an item structure to a class representation. This may include creating child classes, where necessary.
     *
     * @param group
     */
    public abstract void createStructure(GroupItem group);
    
    /**
     * Set the schema namespace for the component associated with this class.
     *
     * @param uri
     */
    protected void setNamespace(String uri) {
        m_namespaceUri = uri;
    }
    
    /**
     * Get the schema namespace for the component associated with this class.
     *
     * @return uri
     */
    protected String getNamespace() {
        return m_namespaceUri;
    }

    /**
     * Get containing package.
     * 
     * @return package
     */
    public PackageHolder getPackage() {
        return m_package;
    }
    
    /**
     * Get simple name.
     * 
     * @return name
     */
    public String getName() {
        return m_name;
    }
    
    /**
     * Get fully-qualified name.
     * 
     * @return name
     */
    public String getFullName() {
        return m_fullName;
    }
    
    /**
     * Get fully-qualified name as used in binding. This differs from the standard fully-qualified name in that it uses
     * '$' rather than '.' to delimit inner class names.
     * 
     * @return name
     */
    public String getBindingName() {
        return m_bindingName;
    }
    
    /**
     * Get base class to be extended.
     *
     * @return base (<code>null</code> if none)
     */
    public ClassHolder getSuperClass() {
        return m_superClass;
    }

    /**
     * Set superclass to be extended.
     *
     * @param sclas (<code>null</code> if none)
     */
    public void setSuperClass(ClassHolder sclas) {
        m_superClass = sclas;
        if (sclas != null) {
            boolean imported = addImport(sclas.getFullName(), false);
            if (s_logger.isDebugEnabled()) {
                s_logger.debug("Set superclass of " + getFullName() + " to " + sclas.getFullName() +
                    (imported ? " (imported)" : ""));
            }
        }
    }
    
    /**
     * Add local definition name to those visible in class. If the name conflicts with an import, the import is removed
     * to force fully-qualified references.
     * 
     * @param name simple class name
     * @param fqname fully qualified class name
     */
    protected void addLocalType(String name, String fqname) {
    
        // check for conflict with current import
        String prior = (String)m_unqualifiedTypes.get(name);
        if (prior != null) {
            if (!fqname.equals(prior)) {
                m_importedTypes.remove(prior);
            }
        }
        
        // add name to unqualified types
        m_unqualifiedTypes.put(name, fqname);
    }
    
    /**
     * Add import for class. If the requested import doesn't conflict with the current set it's added.
     * 
     * @param fqname fully qualified class name
     * @param force force replacement of current import
     * @return <code>true</code> if added as import
     */
    protected boolean addImport(String fqname, boolean force) {
        if (m_importedTypes.contains(fqname) || m_unqualifiedTypes.containsKey(fqname)) {
            return true;
        } else {
            
            // get simple class name
            boolean auto = false;
            String sname = fqname;
            int split = sname.lastIndexOf('.');
            if (split >= 0) {
                sname = sname.substring(split+1);
                auto = "java.lang".equals(fqname.substring(0, split));
            }
            
            // check for conflict with current import preventing addition
            if (m_unqualifiedTypes.containsKey(sname) && !(split < 0 || auto || force)) {
                return false;
            }
            
            // add import for type (overriding old import, if any)
            m_unqualifiedTypes.put(sname, fqname);
            if (split >= 0 && !fqname.substring(0, split).equals(m_package.getName())) {
                m_importedTypes.add(fqname);
            }
            return true;
            
        }
    }
    
    /**
     * Check if type needs qualified references.
     * 
     * @param fqname fully qualified class name
     * @return <code>true</code> if needs qualification
     */
    public boolean isQualified(String fqname) {
        return !m_importedTypes.contains(fqname);
    }
    
    /**
     * Get the imports defined for this class. In the case of inner classes, the set used (and returned by this method)
     * is that of the containing class.
     *
     * @return imports
     */
    public Set getImports() {
        return m_importedTypes;
    }
    
    /**
     * Get the map from unqualified type names to fully qualified names used by this class. In the case of inner
     * classes, the map used (and returned by this method) is that of the containing class.
     *
     * @return imports
     */
    public Map getUnqualifieds() {
        return m_unqualifiedTypes;
    }

    /**
     * Check if the class has been generated. This should always be called before calling {@link
     * #generate(SourceBuilder, BindingHolder)}, in order to prevent multiple generation passes over the same class.
     *
     * @return <code>true</code> if generated, <code>false</code> if not
     */
    public boolean isGenerated() {
        return m_generated;
    }
    
    /**
     * Set the builder for this class.
     *
     * @param builder
     */
    protected void setBuilder(ClassBuilder builder) {
        m_classBuilder = builder;
    }
    
    /**
     * Get the builder for this class.
     *
     * @return builder
     */
    protected ClassBuilder getBuilder() {
        return m_classBuilder;
    }
    
    /**
     * Initialize the class construction. This is a support method for use by subclasses, which handles common setup
     * including superclass generation. The {@link #setBuilder(ClassBuilder)} method must be called before this method
     * is called.
     *
     * @param claswrap wrapper for values
     * @param holder binding holder
     */
    protected void initClass(Wrapper claswrap, BindingHolder holder) {
        
        // make sure the builder has been set
        if (m_classBuilder == null) {
            throw new IllegalStateException("Internal error - need to set class builder before initializing class construction.");
        }
        
        // add serializable necessities if requested
        if (IMPLEMENT_SERIALIZABLE) {
            m_classBuilder.addInterface("java.io.Serializable");
            FieldBuilder serialfield = m_classBuilder.addField("serialVersionUID", "long");
            serialfield.setPrivateStaticFinal();
            serialfield.setNumberInitializer("1L");
        }
        
        // include schema in class comment if enabled and top-level class
        // TODO: include inlined component definitions
        AnnotatedBase comp = claswrap.getSchemaComponent();
        if (INCLUDE_SCHEMA_COMMENTS && m_outerClass == null) {
            StringWriter writer = new StringWriter();
            try {
                ArrayList decls = comp.getParent().getNamespaceDeclarations();
                for (int i = 0; i < decls.size(); i++) {
                    comp.addNamespaceDeclaration((String)decls.get(i), (String)decls.get(++i));
                }
                s_schemaMarshaller.setOutput(writer);
                ((IMarshallable)comp).marshal(s_schemaMarshaller);
                s_schemaMarshaller.endDocument();
                m_classBuilder.addSourceComment(writer.toString());
            } catch (JiBXException e) {
                s_logger.debug("Error marshalling component to create schema", e);
            }
        }
        
        // force generation of superclass, and include all names from that
        // TODO: maintain separate name list for those which are public/protected, vs. private
        if (m_superClass != null) {
            BindingHolder superhold = holder.getDirectory().getBinding(m_superClass.getNamespace());
            m_superClass.getPackage().generate(m_superClass, superhold, m_classBuilder.getAST());
            m_nameSet.addAll(m_superClass.m_nameSet);
        }
        
        // set flag to avoid re-generation
        m_generated = true;
    }
    
    /**
     * Generate any inner classes of this class.
     *
     * @param builder class source file builder
     * @param holder binding holder
     */
    protected void generateInner(SourceBuilder builder, BindingHolder holder) {
        for (int i = 0; i < m_inners.size(); i++) {
            ((ClassHolder)m_inners.get(i)).generate(builder, holder);
        }
    }

    /**
     * Generate this class. Subclasses must implement this method to first do the appropriate setup and then call
     * {@link #initClass(org.jibx.schema.codegen.ClassHolder.Wrapper, BindingHolder)} before doing their own code
     * generation.
     * 
     * @param builder class source file builder
     * @param holder binding holder
     */
    public abstract void generate(SourceBuilder builder, BindingHolder holder);
    
    /**
     * Information for a simple value item within the class definition. Group items use the {@link Wrapper} subclass for
     * extended behavior.
     */
    protected static class Value
    {
        /** Associated item. */
        private final Item m_item;
        
        /** Containing group (<code>null</code> if none defined, only allowed for item corresponding to class). */
        private final Wrapper m_container;
        
        /** Flag for an optional item. */
        private final boolean m_optional;
        
        /** Flag for a collection item. */
        private final boolean m_collection;
        
        /** Element or attribute name flag. */
        private final boolean m_named;
        
        /** Value type name. */
        private final String m_type;
        
        /** Flag for name value used in binding. There needs to be one and only one binding component for each element
         or attribute name, but that component can sometimes be at different levels of the binding. This tracks whether
         the name (if any) has been used yet. */
        private boolean m_nameUsed;
        
        /** Selection value name (only used with group selectors, <code>null</code> if no selector for group). */
        private String m_selectValue;
        
        /** Field name for value (<code>null</code> if no field). */
        private String m_fieldName;
        
        /** Get-method name for value (<code>null</code> if no get-method). */
        private String m_getMethodName;
        
        /** Set-method name for value (<code>null</code> if no set-method). */
        private String m_setMethodName;
        
        /**
         * Constructor. This automatically links to the containing group, and modifies the itme name to use the group
         * prefix (if any).
         * 
         * @param item
         * @param container
         */
        public Value(Item item, Wrapper container) {
            m_item = item;
//            boolean optional = item.isOptional();
//            boolean collection = item.isCollection();
            boolean optional = item.getComponentExtension().isOptional();
            boolean collection = item.getComponentExtension().isRepeated();
            m_container = container;
            AnnotatedBase comp = item.getSchemaComponent();
            if (container != null) {
                if (!optional && comp.type() == SchemaBase.ELEMENT_TYPE) {
                    container.forceDefinite();
                }
//                if (((GroupItem)container.getItem()).isInline()) {
//                    optional = optional || container.isOptional();
//                    collection = collection || container.isCollection();
//                }
            }
            m_optional = optional;
            m_collection = collection;
            int comptype = comp.type();
            m_named = (comptype == SchemaBase.ATTRIBUTE_TYPE || comptype == SchemaBase.ELEMENT_TYPE) &&
                ((INamed)comp).getName() != null;
            String type = null;
            if (item instanceof GroupItem) {
                if (!((GroupItem)item).isInline()) {
                    
                    // group item as value will always be a reference to the group class
                    type = ((GroupItem)item).getGenerateClass().getFullName();
                    
                }
            } else if (item instanceof ReferenceItem) {
                
                // reference item as value will always be a reference to the definition class
                type = ((ReferenceItem)item).getDefinition().getGenerateClass().getFullName();
                
            } else {
                
                // value item will always be a primitive or wrapper value
                JavaType jtype = ((ValueItem)item).getType();
                type = jtype.getPrimitiveName();
                if (type == null || optional || collection) {
                    type = jtype.getClassName();
                }
                
            }
            m_type = type;
            if (container != null) {
                container.addValue(this);
            }
        }
        
        /**
         * Adjust name based on group nesting.
         * TODO: needs switch to control
         */
//        public void adjustName() {
//            if (!m_item.isFixedName() && m_container != null) {
//                String prefix = m_container.getPrefix();
//                if (prefix != null) {
//                    m_item.setName(prefix + NameConverter.toNameWord(m_item.getEffectiveName()));
//                }
//            }
//        }

        /**
         * Get associated item.
         *
         * @return item
         */
        public Item getItem() {
            return m_item;
        }
        
        /**
         * Get the associated schema component.
         *
         * @return component
         */
        public AnnotatedBase getSchemaComponent() {
            return m_item.getSchemaComponent();
        }

        /**
         * Get containing wrapper.
         *
         * @return container
         */
        public Wrapper getWrapper() {
            return m_container;
        }

        /**
         * Check if value is optional.
         * 
         * @return optional
         */
        public boolean isOptional() {
            return m_optional;
        }

        /**
         * Check if a collection value.
         * 
         * @return <code>true</code> if collection
         */
        public boolean isCollection() {
            return m_collection;
        }
        
        /**
         * Check if a list value.
         *
         * @return <code>true</code> if list
         */
        public boolean isList() {
            return m_item.getSchemaComponent().type() == SchemaBase.LIST_TYPE;
        }
        
        /**
         * Check if a named (element or attribute) value.
         *
         * @return <code>true</code> if named
         */
        public boolean isNamed() {
            return m_named;
        }
        
        /**
         * Check if the name associated with the component has been represented in the binding.
         *
         * @return <code>true</code> if already used, <code>false</code> if not
         */
        public boolean isNameUsed() {
            return m_nameUsed;
        }

        /**
         * Set flag for name associated with the component has been represented in the binding.
         *
         * @param used <code>true</code> if already used, <code>false</code> if not
         */
        public void setNameUsed(boolean used) {
            m_nameUsed = used;
        }

        /**
         * Get the value type name.
         *
         * @return type (<code>null</code> if no type associated with value, only on group)
         */
        public String getType() {
            return m_type;
        }

        /**
         * Get selection value name. This is only used with group selectors, and is <code>null</code> if the containing
         * group does not use a selector.
         *
         * @return name (<code>null</code> if no selector for group)
         */
        public String getSelectValue() {
            return m_selectValue;
        }

        /**
         * Set selection value name. This is only used with group selectors.
         *
         * @param name (<code>null</code> if no selector for group)
         */
        public void setSelectValue(String name) {
            m_selectValue = name;
        }
        
        /**
         * Get field name used for value.
         *
         * @return name (<code>null</code> if no field)
         */
        public String getFieldName() {
            return m_fieldName;
        }
        
        /**
         * Set field name used for value.
         *
         * @param name (<code>null</code> if no field)
         */
        public void setFieldName(String name) {
            m_fieldName = name;
        }
        
        /**
         * Get get-method name used for value.
         *
         * @return name (<code>null</code> if no get-method)
         */
        public String getGetMethodName() {
            return m_getMethodName;
        }
        
        /**
         * Set get-method name used for value.
         *
         * @param name (<code>null</code> if no get-method)
         */
        public void setGetMethodName(String name) {
            m_getMethodName = name;
        }
        
        /**
         * Get set-method name used for value.
         *
         * @return name (<code>null</code> if no set-method)
         */
        public String getSetMethodName() {
            return m_setMethodName;
        }
        
        /**
         * Set set-method name used for value.
         *
         * @param name (<code>null</code> if no set-method)
         */
        public void setSetMethodName(String name) {
            m_setMethodName = name;
        }

        /**
         * Print the value description.
         *
         * @param depth current nesting depth
         * @return description
         */
        public String describe(int depth) {
            StringBuffer buff = new StringBuffer(depth + 40);
            buff.append(SchemaUtils.getIndentation(depth));
            if (isOptional()) {
                buff.append("optional ");
            }
            if (isCollection()) {
                buff.append("collection ");
            }
            buff.append("value ");
            buff.append(m_item.getName());
            buff.append(" of type ");
            buff.append(m_type);
            buff.append(" for schema component ");
            buff.append(SchemaUtils.describeComponent(m_item.getSchemaComponent()));
            if (m_selectValue != null) {
                buff.append(" (selection ");
                buff.append(m_selectValue);
                buff.append(')');
            }
            return buff.toString();
        }
    }
    
    /**
     * Information for a wrapper around one or more values within the class definition. Depending on the type of
     * wrapper a selector field may be used to track which of a set of alternatives is actually present.
     * TODO: should probably really be three separate classes, SimpleValue, CollectionValue, and WrapperValue
     */
    protected static class Wrapper extends Value
    {
        /** Selector field needed for group flag. Selector fields are used with mutually exclusive alternatives. */
        private final boolean m_selectorNeeded;
        
        /** Definite group flag. A definite group is one which always represents at least one element in a document. */
        private boolean m_definite;
        // TODO: is this the right place for the above? this should be used by the code generation to check when an
        //  alternative within a <choice> can be inlined, which means it should go into GroupItem
        
        /** Prefix for all contained value names (<code>null</code> if none used). */
        private final String m_prefix;
        
        /** Values in this group. */
        private final ArrayList m_values;
        
        /** Nameset for prefixes used by child groups (lazy create, <code>null</code> if not used). */
        private UniqueNameSet m_nameset;
        
        /** Field name for selector  */
        private String m_selectField;
        
        /** Method name for selection set method. */
        private String m_selectMethod;
        
        /**
         * Constructor. This derives the prefix used for all contained value names by appending the class name set for
         * this group to the prefix used for the containing group.
         * 
         * @param group
         * @param container
         */
        public Wrapper(GroupItem group, Wrapper container) {
            super(group, container);
            String prefix = NameConverter.toNameWord(group.getEffectiveClassName());
            // TODO: this really should be a separate pass to handle fixed class names
            m_prefix = prefix;
            int type = group.getSchemaComponent().type();
            boolean select = type == SchemaBase.CHOICE_TYPE || type == SchemaBase.UNION_TYPE;
            if (select) {
                select = container == null || container.getSchemaComponent() != group.getSchemaComponent();
                if (group.getChildCount() <= 1) {
                    select = false;
                }
            }
            m_selectorNeeded = select;
            m_values = new ArrayList();
        }

        /**
         * Check if a selector field is required for this group.
         *
         * @return selector
         */
        public boolean isSelectorNeeded() {
            return m_selectorNeeded;
        }
        
        /**
         * Adjust name based on group nesting. This has special handling for the case of &lt;sequence> compositors,
         * substituting the name of the first value in the sequence for the value name if a fixed name has not been
         * assigned to the sequence.
         */
        public void adjustName() {
            Item item = getItem();
            if (!item.isFixedName()) {
                if (item.getSchemaComponent().type() == SchemaBase.SEQUENCE_TYPE) {
                    if (m_values.size() > 0) {
                        item.setName(((Value)m_values.get(0)).getItem().getEffectiveName());
                    }
                }
                // TODO: make the inheritance of names controlled by a flag
//                Group container = getContainer();
//                if (container != null) {
//                    String prefix = container.getPrefix();
//                    if (prefix != null) {
//                        name = prefix + NameConverter.toNameWord(item.getName());
//                    }
//                }
            }
        }
        
        /**
         * Get prefix for value names in group.
         *
         * @return prefix (<code>null</code> if none used)
         */
        public String getPrefix() {
            return m_prefix;
        }

        /**
         * Add a value (which may be another group) to this group. This method is normally only used by the superclass,
         * when creating a new instance. The instance must be fully initialized before it is added.
         *
         * @param value
         */
        protected void addValue(Value value) {
            m_values.add(value);
/*            if (value.isNamed()) {
                AnnotatedBase match = value.getSchemaComponent();
                Wrapper wrapper = this;
                while (wrapper != null && !wrapper.m_nestedName && wrapper.getSchemaComponent() != match) {
                    wrapper.m_nestedName = true;
                    if (wrapper.isNamed()) {
                        match = wrapper.getSchemaComponent();
                    }
                    wrapper = wrapper.getWrapper();
                }
            }
*/        }

        /**
         * Get values in group. The returned list is "live", but should never be modified.
         *
         * @return values
         */
        public ArrayList getValues() {
            return m_values;
        }

        /**
         * Get selector field name.
         *
         * @return name (<code>null</code> if no selector for group)
         */
        public String getSelectField() {
            return m_selectField;
        }

        /**
         * Set selector field name.
         *
         * @param name (<code>null</code> if no selector for group)
         */
        public void setSelectField(String name) {
            m_selectField = name;
        }

        /**
         * Get selector set method name.
         *
         * @return name (<code>null</code> if no selector set method for group)
         */
        public String getSelectMethod() {
            return m_selectMethod;
        }

        /**
         * Set selector set method name.
         *
         * @param name (<code>null</code> if no selector set method for group)
         */
        public void setSelectMethod(String name) {
            m_selectMethod = name;
        }
        
        /**
         * Check if this is a "definite" group, meaning it contains at least one required element and hence will always
         * be present in instance documents.
         *
         * @return <code>true</code> if definite, <code>false</code> if not
         */
        public boolean isDefinite() {
            return m_definite;
        }
        
        /**
         * Set the indication for a "definite" group.
         *
         */
        protected void forceDefinite() {
            m_definite = true;
        }
        
        /**
         * Check if this is a collection wrapper. This is an ugly sort of test, but necessary so that the binding
         * generation can determine whether to create a &lt;structure> element or a &lt;collection> element to represent
         * the values in this wrapper.
         *
         * @return <code>true</code> if a collection wrapper, <code>false</code> if not
         */
        public boolean isCollectionWrapper() {
            
            // a collection wrapper has a single child which does not share the same schema component, and either that
            //  child is a simple collection value or is itself a collection wrapper
            if (m_values.size() == 1) {
                Value value = (Value)m_values.get(0);
                if (value.getSchemaComponent() != getSchemaComponent()) {
                    if (value instanceof Wrapper) {
                        return ((Wrapper)value).isCollectionWrapper();
                    } else {
                        return value.isCollection();
                    }
                }
            }
            return false;
        }
        
        /**
         * Generate the group description.
         *
         * @param depth current nesting depth
         * @return description
         */
        public String describe(int depth) {
            StringBuffer buff = new StringBuffer(depth + 40);
            buff.append(SchemaUtils.getIndentation(depth));
            if (isOptional()) {
                buff.append("optional ");
            }
            if (isCollection()) {
                buff.append("collection ");
            }
            buff.append("wrapper ");
            buff.append(getItem().getName());
            if (m_selectorNeeded) {
                buff.append(" (with selector)");
            }
            buff.append(" for schema component ");
            buff.append(SchemaUtils.describeComponent(getItem().getSchemaComponent()));
            for (int i = 0; i < m_values.size(); i++) {
                buff.append('\n');
                Value value = (Value)m_values.get(i);
                buff.append(value.describe(depth+1));
            }
            return buff.toString();
        }
    }
    
    /**
     * Builder for an unmarshalling presence test method. This is based around the structure of a schema sequence, which
     * may have any number of mixed required and optional elements. The presence test checks if the current element name
     * matches one of those in the sequence up to and including the first <i>required</i> element name.
     */
//    private class UnmarshalPresenceTestBuilder
//    {
//        /** Builder for the logical or expression. */
//        private final InfixExpressionBuilder m_expression;
//        
//        /** Implicit namespace in use at point of evaluation. */
//        private final String m_implicitNamespace;
//        
//        /** Expression completed flag (set when a required element is added to expression). */
//        private boolean m_complete;
//        
//        /**
//         * Constructor.
//         * 
//         * @param implns implicit namespace in use at point of evaluation
//         */
//        public UnmarshalPresenceTestBuilder(String implns) {
//            m_expression = m_classBuilder.buildInfix(Operator.OR);
//            m_implicitNamespace = implns;
//        }
//        
//        /**
//         * Add an element to the presence test expression. If the expression is already complete (with a required
//         * element found), the call is ignored.
//         *
//         * @param ns
//         * @param name
//         * @param required
//         */
//        public void addElement(String ns, String name, boolean required) {
//            if (!m_complete) {
//                InvocationBuilder call = m_classBuilder.createNormalMethodCall(READER_VARNAME,
//                    READER_CHECK_START_TAG_METHOD);
//                if ((m_implicitNamespace == null && ns != null) ||
//                    (m_implicitNamespace != null && !m_implicitNamespace.equals(ns))) {
//                    call.addStringLiteralOperand(ns);
//                }
//                call.addStringLiteralOperand(name);
//                m_expression.addOperand(call);
//                if (required) {
//                    m_complete = true;
//                }
//            }
//        }
//        
//        /**
//         * Get the expression.
//         *
//         * @return expression
//         */
//        public InfixExpressionBuilder getExpression() {
//            return m_expression;
//        }
//    }
}