/**
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. 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.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED 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 INTALIO, INC. OR ITS
 * 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.
 *
 * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id$
 */

package org.exolab.castor.xml.schema.writer;

import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.types.AnyNode;
import org.exolab.castor.xml.Namespaces;
import org.exolab.castor.xml.Serializer;
import org.exolab.castor.xml.schema.Annotated;
import org.exolab.castor.xml.schema.Annotation;
import org.exolab.castor.xml.schema.AppInfo;
import org.exolab.castor.xml.schema.AttributeDecl;
import org.exolab.castor.xml.schema.AttributeGroup;
import org.exolab.castor.xml.schema.AttributeGroupDecl;
import org.exolab.castor.xml.schema.AttributeGroupReference;
import org.exolab.castor.xml.schema.BlockList;
import org.exolab.castor.xml.schema.ComplexType;
import org.exolab.castor.xml.schema.ContentModelGroup;
import org.exolab.castor.xml.schema.ContentType;
import org.exolab.castor.xml.schema.Documentation;
import org.exolab.castor.xml.schema.ElementDecl;
import org.exolab.castor.xml.schema.Facet;
import org.exolab.castor.xml.schema.FinalList;
import org.exolab.castor.xml.schema.Form;
import org.exolab.castor.xml.schema.Group;
import org.exolab.castor.xml.schema.IdentityConstraint;
import org.exolab.castor.xml.schema.IdentityField;
import org.exolab.castor.xml.schema.IdentitySelector;
import org.exolab.castor.xml.schema.KeyRef;
import org.exolab.castor.xml.schema.ModelGroup;
import org.exolab.castor.xml.schema.RedefineSchema;
import org.exolab.castor.xml.schema.Schema;
import org.exolab.castor.xml.schema.SchemaContext;
import org.exolab.castor.xml.schema.SchemaContextImpl;
import org.exolab.castor.xml.schema.SchemaNames;
import org.exolab.castor.xml.schema.SimpleContent;
import org.exolab.castor.xml.schema.SimpleType;
import org.exolab.castor.xml.schema.Structure;
import org.exolab.castor.xml.schema.Union;
import org.exolab.castor.xml.schema.Wildcard;
import org.exolab.castor.xml.schema.XMLType;
import org.exolab.castor.xml.schema.simpletypes.ListType;
import org.exolab.castor.xml.util.AnyNode2SAX;
import org.xml.sax.DocumentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributeListImpl;

/**
 * A class for serializing Schema models.
 * 
 * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
 * @version $Revision$ $Date: 2006-04-05 13:16:42 -0600 (Wed, 05 Apr 2006) $
 **/
public class SchemaWriter {

  /**
   * The {@link Log} instance to use.
   */
  private static final Log LOG = LogFactory.getLog(SchemaWriter.class);
  // ------------------------/
  // - Schema element names -/
  // ------------------------/

  /**
   * Annotation element name.
   */
  private static final String ANNOTATION = "annotation";

  /**
   * AppInfo element name.
   */
  private static final String APPINFO = "appinfo";

  /**
   * Attribute element name.
   */
  private static final String ATTRIBUTE = "attribute";

  /**
   * AttributeGroup element name.
   */
  private static final String ATTRIBUTE_GROUP = "attributeGroup";

  /**
   * ComplexType element name.
   */
  private static final String COMPLEX_TYPE = "complexType";

  /**
   * Documentation element name.
   */
  private static final String DOCUMENTATION = "documentation";

  /**
   * Element element name.
   */
  private static final String ELEMENT = "element";

  /**
   * ModelGroup element name.
   */
  private static final String GROUP = "group";

  /**
   * Restriction element name.
   */
  private static final String RESTRICTION = "restriction";

  /**
   * Schema element name.
   */
  private static final String SCHEMA = "schema";

  /**
   * SimpleType element name
   */
  private static final String SIMPLE_TYPE = "simpleType";

  // -------------------/
  // - Attribute names -/
  // -------------------/

  private static final String ATTR_NAME = "name";
  private static final String ATTR_TYPE = "type";

  private static final String VALUE_TRUE = "true";


  /**
   * For use with SAX AttributeList
   */
  private static final String CDATA = "CDATA";
  private static final String XMLNS_PREFIX = "xmlns:";
  private static final String XMLNS_DEFAULT = "xmlns";
  private static final String DEFAULT_PREFIX = "xsd";

  /**
   * The DocumentHandler to send events to
   */
  private DocumentHandler _handler = null;

  /**
   * The AttributeList to send events to
   */
  private AttributeListImpl _atts = new AttributeListImpl();

  /**
   * This field is no longer used and only here for backward compatibility.
   * 
   * @deprecated
   **/
  public static boolean enable = false;

  /** Castor XML context - mother of all dwelling. */
  private SchemaContext _schemaContext = null;

  /**
   * Creates a new SchemaWriter for the given Writer.
   *
   * @param writer the Writer to serialize to
   * @throws IOException in case taht wrapping the Writer fails
   **/
  public SchemaWriter(final Writer writer) throws IOException {
    this();
    Serializer serializer = _schemaContext.getSerializer();

    if (serializer == null) {
      throw new IOException("Unable to obtain serailizer");
    }

    serializer.setOutputCharStream(writer);

    DocumentHandler handler = serializer.asDocumentHandler();

    if (handler == null) {
      String err = "The following serializer is not SAX capable: ";
      err += serializer.getClass().getName();
      err += "; cannot proceed.";
      throw new IOException(err);
    }

    _handler = handler;

  } // -- SchemaWriter

  /**
   * Creates a new SchemaWriter for the given DocumentHandler.
   *
   * @param handler the DocumentHandler to send events to
   **/
  public SchemaWriter(final DocumentHandler handler) {
    this();

    if (handler == null) {
      throw new IllegalArgumentException("DocumentHandler must not be null.");
    }

    _handler = handler;
  } // -- SchemaWriter

  /**
   * A constructor to create an empty uninitialized SchemaWriter via XMLContext.
   */
  public SchemaWriter() {
    super();
    _schemaContext = new SchemaContextImpl();
  }

  /**
   * To set the {@link SchemaContext} to be used for the {@link SchemaWriter}.
   * 
   * @param schemaContext the {@link SchemaContext} to be used
   */
  public void setSchemaContext(final SchemaContext schemaContext) {
    _schemaContext = schemaContext;
  }

  /**
   * To set the DocumentHandler to a Writer - which is wrapped by a serializer.
   * 
   * @param writer the Writer to use for output
   * @throws IOException in case the Writer cannot be used for DocumentHandler
   */
  public void setDocumentHandler(final Writer writer) throws IOException {
    Serializer serializer = _schemaContext.getSerializer();

    if (serializer == null) {
      String message = "Unable to obtain serailizer";
      LOG.warn(message);
      throw new IOException(message);
    }

    serializer.setOutputCharStream(writer);

    DocumentHandler handler = serializer.asDocumentHandler();

    if (handler == null) {
      String err = "The following serializer is not SAX capable: ";
      err += serializer.getClass().getName();
      err += "; cannot proceed.";
      LOG.warn(err);
      throw new IOException(err);
    }

    _handler = handler;
  }

  /**
   * To directly set a DocumentHandler.
   * 
   * @param documentHandler set the DocumentHandler
   */
  public void setDocumentHandler(final DocumentHandler documentHandler) {
    if (documentHandler == null) {
      String message = "DocumentHandler must not be null.";
      LOG.warn(message);
      throw new IllegalArgumentException(message);
    }
    _handler = documentHandler;
  }

  public void write(final Schema schema) throws SAXException {
    if (schema == null)
      throw new IllegalArgumentException("Schema must not be null.");

    processSchema(schema);

  } // -- write


  /**
   * Processes the given annotated structure into events
   *
   * @param annotated the annotated structure to process into events
   **/
  private void processAnnotated(Annotated annotated, String schemaPrefix) throws SAXException {
    Enumeration enumeration = annotated.getAnnotations();
    while (enumeration.hasMoreElements())
      processAnnotation((Annotation) enumeration.nextElement(), schemaPrefix);

  } // -- processAnnotated

  /**
   * Processes the given annotation into events
   *
   * @param annotation the annotation to process into events
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processAnnotation(Annotation annotation, String schemaPrefix) throws SAXException {

    _atts.clear();

    String ELEM_ANNOTATION = schemaPrefix + ANNOTATION;

    _handler.startElement(ELEM_ANNOTATION, _atts);


    // --process appinfo elements
    Enumeration enumeration = annotation.getAppInfo();
    String ELEM_APPINFO = schemaPrefix + APPINFO;
    while (enumeration.hasMoreElements()) {
      AppInfo app = (AppInfo) enumeration.nextElement();

      String source = app.getSource();
      String sourceName = _atts.getName(0);
      boolean isSourceIsNull = (sourceName == null);
      boolean isSourceExists = false;

      if (!isSourceIsNull) {
        isSourceExists = sourceName.equals(SchemaNames.SOURCE_ATTR);
      }
      if (source != null && !isSourceExists)
        _atts.addAttribute(SchemaNames.SOURCE_ATTR, CDATA, source);

      _handler.startElement(ELEM_APPINFO, _atts);
      Enumeration anyNodes = app.getObjects();
      while (anyNodes.hasMoreElements()) {
        Object obj = anyNodes.nextElement();
        if (obj instanceof AnyNode) {
          AnyNode2SAX anyNode2SAX = new AnyNode2SAX((AnyNode) obj);
          anyNode2SAX.setDocumentHandler(_handler);
          anyNode2SAX.start();
        } else {
          char[] chars = obj.toString().toCharArray();
          _handler.characters(chars, 0, chars.length);

        }
      }
      _handler.endElement(ELEM_APPINFO);
    }

    // -- process documentation elements
    enumeration = annotation.getDocumentation();
    String ELEM_DOCUMENTATION = schemaPrefix + DOCUMENTATION;
    while (enumeration.hasMoreElements()) {
      _atts.clear();
      Documentation doc = (Documentation) enumeration.nextElement();
      String source = doc.getSource();
      String sourceName = _atts.getName(0);
      boolean isSourceIsNull = (sourceName == null);
      boolean isSourceExists = false;

      if (!isSourceIsNull) {
        isSourceExists = sourceName.equals(SchemaNames.SOURCE_ATTR);
      }
      if (source != null && !isSourceExists) {
        _atts.addAttribute(SchemaNames.SOURCE_ATTR, CDATA, source);
      }

      _handler.startElement(ELEM_DOCUMENTATION, _atts);
      Enumeration anyNodes = doc.getObjects();
      while (anyNodes.hasMoreElements()) {
        Object obj = anyNodes.nextElement();
        if (obj instanceof AnyNode) {
          AnyNode2SAX anyNode2SAX = new AnyNode2SAX((AnyNode) obj);
          anyNode2SAX.setDocumentHandler(_handler);
          anyNode2SAX.start();
        } else {
          char[] chars = obj.toString().toCharArray();
          _handler.characters(chars, 0, chars.length);

        }
      }
      _handler.endElement(ELEM_DOCUMENTATION);
    }


    _handler.endElement(ELEM_ANNOTATION);

  } // -- processAnnotations

  /**
   * Processes the given attribute declaration
   *
   * @param attribute the attribute declaration to process into events
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processAttribute(AttributeDecl attribute, String schemaPrefix) throws SAXException {
    String ELEM_ATTRIBUTE = schemaPrefix + ATTRIBUTE;

    _atts.clear();

    boolean isReference = attribute.isReference();

    // -- name
    if (!isReference) {
      _atts.addAttribute(SchemaNames.NAME_ATTR, CDATA, attribute.getName());
    } else {
      _atts.addAttribute(SchemaNames.REF_ATTR, CDATA, attribute.getReferenceName());
    }


    // -- type attribute
    boolean hasAnonymousType = false;
    SimpleType type = attribute.getSimpleType();
    if ((!isReference) && (type != null)) {

      if (type.getName() != null) {

        String typeName = type.getName();

        // -- add prefix if necessary
        if (typeName.indexOf(':') < 0) {
          if (type.isBuiltInType()) {
            typeName = schemaPrefix + typeName; // xsd prefix
          } else {
            // resolve prefix
            String namespace = type.getSchema().getTargetNamespace();
            if (namespace == null)
              namespace = "";
            String prefix = getNSPrefix(attribute.getSchema(), namespace);
            if ((prefix != null) && (prefix.length() > 0))
              typeName = prefix + ":" + typeName;
          }
        }
        _atts.addAttribute(ATTR_TYPE, CDATA, typeName);
      } else
        hasAnonymousType = true;
    }

    // default or fixed values?
    // -- @default
    if (attribute.isDefault()) {
      _atts.addAttribute(SchemaNames.DEFAULT_ATTR, CDATA, attribute.getDefaultValue());
    }
    // -- @fixed
    else if (attribute.isFixed()) {
      _atts.addAttribute(SchemaNames.FIXED_ATTR, CDATA, attribute.getFixedValue());
    }

    // -- @form
    if (attribute.getForm() != null) {
      _atts.addAttribute(SchemaNames.FORM, CDATA, attribute.getForm().toString());
    }

    // -- @id (optional)
    if (attribute.getId() != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, attribute.getId());
    }

    // -- use : required
    if (attribute.isRequired()) {
      _atts.addAttribute(SchemaNames.USE_ATTR, CDATA, AttributeDecl.USE_REQUIRED);
    }
    // -- use : prohibited
    else if (attribute.isProhibited()) {
      _atts.addAttribute(SchemaNames.USE_ATTR, CDATA, AttributeDecl.USE_PROHIBITED);
    }

    _handler.startElement(ELEM_ATTRIBUTE, _atts);

    // -- process annotations
    processAnnotated(attribute, schemaPrefix);

    // -- process anonymous type if necessary
    if (hasAnonymousType) {
      processSimpleType(type, schemaPrefix);
    }

    _handler.endElement(ELEM_ATTRIBUTE);


  } // -- processAttribute

  /**
   * Processes the given attributeGroup declaration
   *
   * @param attGroup the attributeGroup declaration to process into events
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processAttributeGroup(AttributeGroup attGroup, String schemaPrefix)
      throws SAXException {
    String ELEM_ATTRIBUTE_GROUP = schemaPrefix + ATTRIBUTE_GROUP;

    _atts.clear();

    boolean isReference = (attGroup instanceof AttributeGroupReference);

    // -- name
    if (!isReference) {
      _atts.addAttribute(SchemaNames.NAME_ATTR, CDATA, ((AttributeGroupDecl) attGroup).getName());
    } else {
      _atts.addAttribute(SchemaNames.REF_ATTR, CDATA,
          ((AttributeGroupReference) attGroup).getReference());
    }

    // -- @id (optional)
    if (attGroup.getId() != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, attGroup.getId());
    }

    _handler.startElement(ELEM_ATTRIBUTE_GROUP, _atts);

    // -- process annotations
    processAnnotated(attGroup, schemaPrefix);

    if (!isReference) {
      AttributeGroupDecl group = (AttributeGroupDecl) attGroup;
      Enumeration enumeration = group.getLocalAttributes();
      while (enumeration.hasMoreElements()) {
        processAttribute((AttributeDecl) enumeration.nextElement(), schemaPrefix);
      }
      enumeration = group.getLocalAttributeGroupReferences();
      while (enumeration.hasMoreElements()) {
        processAttributeGroup((AttributeGroup) enumeration.nextElement(), schemaPrefix);
      }

      if (group.getAnyAttribute() != null) {
        processWildcard(group.getAnyAttribute(), schemaPrefix);
      }
    }

    _handler.endElement(ELEM_ATTRIBUTE_GROUP);


  } // -- processAttributeGroup

  /**
   * Processes the given complex type definition
   *
   * @param complexType the complex type definition to process into events
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processComplexType(ComplexType complexType, String schemaPrefix)
      throws SAXException {
    String ELEMENT_NAME = schemaPrefix + COMPLEX_TYPE;

    _atts.clear();

    // -- handle top-level only attributes
    if (complexType.isTopLevel()) {

      // -- @name
      _atts.addAttribute(SchemaNames.NAME_ATTR, CDATA, complexType.getName());

      // -- @abstract
      if (complexType.isAbstract()) {
        _atts.addAttribute(SchemaNames.ABSTRACT, CDATA, VALUE_TRUE);
      }

      // -- @block
      if (complexType.getBlock() != null) {
        _atts.addAttribute(SchemaNames.BLOCK_ATTR, CDATA, complexType.getBlock().toString());
      }

      // -- @final
      if (complexType.getFinal() != null) {
        _atts.addAttribute(SchemaNames.FINAL_ATTR, CDATA, complexType.getFinal().toString());
      }
    } // -- isTopLevel

    // -- @id
    if (complexType.getId() != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, complexType.getId());
    }

    // -- @mixed
    if (complexType.getContentType() == ContentType.mixed) {
      _atts.addAttribute(SchemaNames.MIXED, CDATA, VALUE_TRUE);
    }

    _handler.startElement(ELEMENT_NAME, _atts);

    // -- process annotations
    processAnnotated(complexType, schemaPrefix);


    // -- handle simpleContent/complexContent if we have a baseType.
    String ELEM_CONTENT = null;
    String ELEM_DERIVATION = null;
    XMLType baseType = complexType.getBaseType();
    if (baseType != null) {
      if (complexType.isSimpleContent())
        ELEM_CONTENT = schemaPrefix + SchemaNames.SIMPLE_CONTENT;
      else
        ELEM_CONTENT = schemaPrefix + SchemaNames.COMPLEX_CONTENT;

      _atts.clear();
      if (complexType.isComplexContent()) {
        if (complexType.getContentType() == ContentType.mixed) {
          _atts.addAttribute(SchemaNames.MIXED, CDATA, VALUE_TRUE);
        }

      }
      _handler.startElement(ELEM_CONTENT, _atts);

      ELEM_DERIVATION = schemaPrefix + complexType.getDerivationMethod();

      String baseTypeName = baseType.getName();

      // -- add "xsd" prefix if necessary
      if (complexType.isSimpleContent()) {
        // the base type can be a complexType in extension
        if (baseType.isSimpleType()) {
          SimpleType simpleType = (SimpleType) baseType;
          if (baseTypeName.indexOf(':') < 0) {
            if (simpleType.isBuiltInType()) {
              baseTypeName = schemaPrefix + baseTypeName;
            } else {
              String targetNamespace = simpleType.getSchema().getTargetNamespace();
              String prefix = getNSPrefix(complexType.getSchema(), targetNamespace);
              if ((prefix != null) && (prefix.length() > 0)) {
                baseTypeName = prefix + ":" + baseTypeName;
              }
            }
          }
        }
      } else if (complexType.isComplexContent()) {
        // --complexType: add 'xsd' only for anyType
        if (baseType.isAnyType()) {
          if (baseTypeName.indexOf(':') < 0) {
            baseTypeName = schemaPrefix + baseTypeName;
          }

        }
      } // --end of 'xsd' appending
      // add the targetNamespace prefix if necessary
      if (baseType.isComplexType()) {
        String targetNamespace = baseType.getSchema().getTargetNamespace();
        // -- targetNamespace is null
        if (targetNamespace == null) {
          if (complexType.isRedefined()) {
            targetNamespace = complexType.getSchema().getTargetNamespace();
          }
        }

        else {
          String nsPrefix = getNSPrefix(complexType.getSchema(), targetNamespace);
          if ((nsPrefix != null) && (nsPrefix.length() != 0))
            baseTypeName = nsPrefix + ':' + baseTypeName;
          targetNamespace = null;
          nsPrefix = null;
        }

      }
      _atts.clear();
      _atts.addAttribute(SchemaNames.BASE_ATTR, CDATA, baseTypeName);
      _handler.startElement(ELEM_DERIVATION, _atts);
      // --Any Facets to process?
      // --only relevant for the simpleContent with restriction
      if (complexType.isSimpleContent() && complexType.isRestricted()) {
        if (complexType.getContentType().getType() == ContentType.SIMPLE) {
          SimpleContent simpleContent = (SimpleContent) complexType.getContentType();
          SimpleType simpleType = simpleContent.getSimpleType();
          // -- process facets
          Enumeration enumeration = simpleType.getLocalFacets();
          while (enumeration.hasMoreElements()) {
            Facet facet = (Facet) enumeration.nextElement();
            _atts.clear();
            _atts.addAttribute(SchemaNames.VALUE_ATTR, CDATA, facet.getValue());
            String facetName = schemaPrefix + facet.getName();
            _handler.startElement(facetName, _atts);
            Enumeration annotations = facet.getAnnotations();
            while (annotations.hasMoreElements()) {
              Annotation annotation = (Annotation) annotations.nextElement();
              processAnnotation(annotation, schemaPrefix);
            }
            _handler.endElement(facetName);
          } // --facets
          enumeration = null;
          simpleType = null;
        }
      } // --<simpleContent><restriction>

    }

    // -- process content model group
    processContentModelGroup(complexType, schemaPrefix);

    // -- process Attributes, must appear last in a complex type
    Enumeration enumeration = complexType.getLocalAttributeDecls();
    while (enumeration.hasMoreElements()) {
      processAttribute((AttributeDecl) enumeration.nextElement(), schemaPrefix);
    }
    enumeration = complexType.getAttributeGroupReferences();
    while (enumeration.hasMoreElements()) {
      processAttributeGroup((AttributeGroup) enumeration.nextElement(), schemaPrefix);
    }

    if (baseType != null) {
      _handler.endElement(ELEM_DERIVATION);
      _handler.endElement(ELEM_CONTENT);
    }

    if (complexType.getAnyAttribute() != null) {
      processWildcard(complexType.getAnyAttribute(), schemaPrefix);
    }

    _handler.endElement(ELEMENT_NAME);

  } // -- processComplexType

  /**
   * Processes the given ContentModelGroup
   *
   * @param contentModel the content model group to process into events
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processContentModelGroup(ContentModelGroup contentModel, String schemaPrefix)
      throws SAXException {
    Enumeration enumeration = contentModel.enumerate();
    while (enumeration.hasMoreElements()) {
      Structure structure = (Structure) enumeration.nextElement();
      switch (structure.getStructureType()) {
        case Structure.ELEMENT:
          processElement((ElementDecl) structure, schemaPrefix);
          break;
        case Structure.MODELGROUP:
        case Structure.GROUP:
          processGroup((Group) structure, schemaPrefix);
          break;
        case Structure.WILDCARD:
          processWildcard((Wildcard) structure, schemaPrefix);
          break;
        default:
          break;
      }
    }
  } // -- contentModel

  /**
   * Processes the given element declaration
   *
   * @param element the element declaration to process into events
   **/
  private void processElement(ElementDecl element, String schemaPrefix) throws SAXException {
    String ELEMENT_NAME = schemaPrefix + ELEMENT;

    _atts.clear();


    // -- name or reference
    String value = element.getName();
    if (value != null) {
      if (element.isReference()) {
        String targetNamespace = element.getReference().getSchema().getTargetNamespace();
        String nsPrefix = getNSPrefix(element.getSchema(), targetNamespace);
        if ((nsPrefix != null) && (nsPrefix.length() != 0))
          value = nsPrefix + ':' + value;
        targetNamespace = null;
        nsPrefix = null;
        _atts.addAttribute(SchemaNames.REF_ATTR, CDATA, value);

      } else
        _atts.addAttribute(ATTR_NAME, CDATA, value);
    }

    // -- minOccurs/maxOccurs
    int max = element.getMaxOccurs();
    int min = element.getMinOccurs();

    if (min != 1) {
      _atts.addAttribute(SchemaNames.MIN_OCCURS_ATTR, CDATA, Integer.toString(min));
    }

    if (max < 0) {
      _atts.addAttribute(SchemaNames.MAX_OCCURS_ATTR, CDATA, "unbounded");
    } else if (max != 1) {
      _atts.addAttribute(SchemaNames.MAX_OCCURS_ATTR, CDATA, Integer.toString(max));
    }

    // -- type attribute
    boolean hasAnonymousType = false;
    if (!element.isReference()) {
      XMLType type = element.getType();

      // -- no type?
      if (type == null) {
        // -- do nothing
      }
      // -- anonymous (in-lined) type
      else if (type.getName() == null) {
        hasAnonymousType = true;
      }
      // -- built-in simpleType
      else if (type.isSimpleType() && ((SimpleType) type).isBuiltInType()) {
        _atts.addAttribute(ATTR_TYPE, CDATA, schemaPrefix + type.getName());
      } else if (type.getStructureType() == Structure.ANYTYPE) {
        _atts.addAttribute(ATTR_TYPE, CDATA, schemaPrefix + type.getName());
      }
      // -- type imported from another schema
      else if (isImportedType(type, element)) {
        String namespace = type.getSchema().getTargetNamespace();
        String prefix = getNSPrefix(element.getSchema(), namespace);
        if (prefix == null) {
          // -- declare a temporary prefix
          prefix = schemaPrefix + '2';
          _atts.addAttribute("xmlns:" + prefix, CDATA, namespace);
        }
        _atts.addAttribute(ATTR_TYPE, CDATA, prefix + ':' + type.getName());
        // -- otherwise...user defined type.
      } else {
        String typeName = type.getName();
        // add the targetNamespace prefix if necessary
        String targetNamespace = element.getSchema().getTargetNamespace();
        if (targetNamespace != null) {
          String nsPrefix = getNSPrefix(element.getSchema(), targetNamespace);
          if ((nsPrefix != null) && (nsPrefix.length() != 0))
            typeName = nsPrefix + ':' + typeName;
          targetNamespace = null;
          nsPrefix = null;
        }

        _atts.addAttribute(ATTR_TYPE, CDATA, typeName);
      }
    }

    // add various attributes if we are looking at a local element definition; iow,
    // for an element reference, this values should be specified on the
    // referenced (global) element definition only.
    if (!element.isReference()) {

      // -- @abstract
      if (element.isAbstract()) {
        _atts.addAttribute(SchemaNames.ABSTRACT, CDATA, VALUE_TRUE);
      }

      // -- @block
      if (element.getBlock() != null) {
        _atts.addAttribute(SchemaNames.BLOCK_ATTR, CDATA, element.getBlock().toString());
      }

      // -- @default
      String defaultValue = element.getDefaultValue();
      if (defaultValue != null) {
        _atts.addAttribute(SchemaNames.DEFAULT_ATTR, CDATA, defaultValue);
      }

      // -- @fixed
      String fixedValue = element.getFixedValue();
      if (fixedValue != null) {
        _atts.addAttribute(SchemaNames.FIXED_ATTR, CDATA, fixedValue);
      }

      // -- @final
      FinalList finalValue = element.getFinal();
      if (finalValue != null) {
        _atts.addAttribute(SchemaNames.FINAL_ATTR, CDATA, finalValue.toString());
      }

      // -- @substitutionGroup
      String substitutionGroup = element.getSubstitutionGroup();
      if (substitutionGroup != null) {
        _atts.addAttribute(SchemaNames.SUBSTITUTION_GROUP_ATTR, CDATA, substitutionGroup);
      }
    }

    // -- @form
    Form form = element.getForm();
    if (form != null) {
      _atts.addAttribute(SchemaNames.FORM, CDATA, form.toString());
    }

    // -- @id
    String id = element.getId();
    if (id != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, id);
    }

    // -- @nillable
    if (element.isNillable()) {
      _atts.addAttribute(SchemaNames.NILLABLE_ATTR, CDATA, VALUE_TRUE);
    }


    _handler.startElement(ELEMENT_NAME, _atts);

    // -- process annotations
    processAnnotated(element, schemaPrefix);

    // -- process anonymous type if necessary
    if (hasAnonymousType) {
      XMLType type = element.getType();
      if (type.isComplexType())
        processComplexType((ComplexType) type, schemaPrefix);
      else if (type.isSimpleType())
        processSimpleType((SimpleType) type, schemaPrefix);
    }

    // -- process any identity-constraints
    Enumeration enumeration = element.getIdentityConstraints();
    while (enumeration.hasMoreElements()) {
      processIdentityConstraint((IdentityConstraint) enumeration.nextElement(), schemaPrefix);
    }

    _handler.endElement(ELEMENT_NAME);

  } // -- processElement

  /**
   * Processes the given group definition into SAX events
   *
   * @param group the group definition to process into SAX events
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processGroup(Group group, String schemaPrefix) throws SAXException {
    String ELEMENT_NAME = schemaPrefix;

    // -- ModelGroup
    String reference = null;
    if (group instanceof ModelGroup) {
      ELEMENT_NAME += GROUP;
      ModelGroup mGroup = (ModelGroup) group;
      if (mGroup.hasReference()) {
        ModelGroup refGroup = mGroup.getReference();
        if (refGroup != null) {
          reference = refGroup.getName();
          // -- prefix
          String namespace = refGroup.getSchema().getTargetNamespace();
          if (namespace == null)
            namespace = "";
          String prefix = getNSPrefix(mGroup.getSchema(), namespace);
          if ((prefix != null) && (prefix.length() > 0))
            reference = prefix + ':' + reference;

        }
      }
    }
    // -- Group
    else {
      ELEMENT_NAME += group.getOrder().toString();
    }

    _atts.clear();

    // -- @id
    if (group.getId() != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, group.getId());
    }

    // -- reference
    if (reference != null) {
      _atts.addAttribute("ref", CDATA, reference);
    } else if (group.getName() != null) {
      _atts.addAttribute(ATTR_NAME, CDATA, group.getName());
    }

    // -- minOccurs/maxOccurs
    int max = group.getMaxOccurs();
    int min = group.getMinOccurs();

    if (min != 1) {
      _atts.addAttribute(SchemaNames.MIN_OCCURS_ATTR, CDATA, Integer.toString(min));
    }

    if (max < 0) {
      _atts.addAttribute(SchemaNames.MAX_OCCURS_ATTR, CDATA, "unbounded");
    } else if (max != 1) {
      _atts.addAttribute(SchemaNames.MAX_OCCURS_ATTR, CDATA, Integer.toString(max));
    }


    _handler.startElement(ELEMENT_NAME, _atts);

    // -- process annotations
    processAnnotated(group, schemaPrefix);

    // -- process content model
    if (reference == null) {
      processContentModelGroup(group, schemaPrefix);
    }

    _handler.endElement(ELEMENT_NAME);

  } // -- processGroup

  /**
   * Processes the given IdentityConstraint
   *
   * @param constraint the IdentityConstraint to process
   **/
  private void processIdentityConstraint(IdentityConstraint constraint, String schemaPrefix)
      throws SAXException {

    if (constraint == null)
      return;

    String ELEMENT_NAME = schemaPrefix;

    String id = null;
    String refer = null;

    switch (constraint.getStructureType()) {
      case Structure.KEYREF:
        ELEMENT_NAME += SchemaNames.KEYREF;
        refer = ((KeyRef) constraint).getRefer();
        break;
      case Structure.UNIQUE:
        ELEMENT_NAME += SchemaNames.UNIQUE;
        break;
      default:
        ELEMENT_NAME += SchemaNames.KEY;
        break;
    }

    id = constraint.getId();

    _atts.clear();

    // -- name
    _atts.addAttribute(SchemaNames.NAME_ATTR, CDATA, constraint.getName());

    // -- id
    if (id != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, id);
    }

    // -- refer
    if (refer != null) {
      _atts.addAttribute(SchemaNames.REFER_ATTR, CDATA, refer);
    }

    _handler.startElement(ELEMENT_NAME, _atts);

    // -- process annotations
    processAnnotated(constraint, schemaPrefix);

    // -- process selector
    String ELEM_SELECTOR = schemaPrefix + SchemaNames.SELECTOR;
    String xpath = null;

    IdentitySelector selector = constraint.getSelector();
    xpath = selector.getXPath();
    id = selector.getId();
    _atts.clear();
    _atts.addAttribute(SchemaNames.XPATH_ATTR, CDATA, xpath);
    if (id != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, id);
    }
    _handler.startElement(ELEM_SELECTOR, _atts);
    processAnnotated(selector, schemaPrefix);
    _handler.endElement(ELEM_SELECTOR);

    // -- process field(s)
    String ELEM_FIELD = schemaPrefix + SchemaNames.FIELD;
    Enumeration enumeration = constraint.getFields();
    while (enumeration.hasMoreElements()) {
      IdentityField field = (IdentityField) enumeration.nextElement();
      _atts.clear();
      id = field.getId();
      xpath = field.getXPath();
      _atts.addAttribute(SchemaNames.XPATH_ATTR, CDATA, xpath);
      if (id != null) {
        _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, id);
      }
      _handler.startElement(ELEM_FIELD, _atts);
      processAnnotated(field, schemaPrefix);
      _handler.endElement(ELEM_FIELD);
    }
    _handler.endElement(ELEMENT_NAME);

  } // -- processIdentityConstraint

  private void processSchema(Schema schema) throws SAXException {
    // -- calculate schema prefix
    String schemaPrefix = getNSPrefix(schema, schema.getSchemaNamespace());
    if (schemaPrefix == null) {
      schemaPrefix = DEFAULT_PREFIX;
    }

    // -- namespace declaration for xsd
    _atts.clear();
    if (schemaPrefix.length() == 0) {
      // -- declared as default namespace
      _atts.addAttribute(XMLNS_DEFAULT, CDATA, schema.getSchemaNamespace());
    } else {
      // -- declare namespace + prefix
      _atts.addAttribute(XMLNS_PREFIX + schemaPrefix, CDATA, schema.getSchemaNamespace());
    }



    // -- namespace declarations
    Namespaces namespaces = schema.getNamespaces();
    Enumeration keys = namespaces.getLocalNamespacePrefixes();
    while (keys.hasMoreElements()) {
      String nsPrefix = (String) keys.nextElement();
      if (!nsPrefix.equals(schemaPrefix)) {
        String ns = namespaces.getNamespaceURI(nsPrefix);
        if (nsPrefix.length() > 0) {
          _atts.addAttribute(XMLNS_PREFIX + nsPrefix, CDATA, ns);
        } else {
          _atts.addAttribute(XMLNS_DEFAULT, CDATA, ns);
        }
      }
    }

    // -- targetNS
    String value = schema.getTargetNamespace();
    if (value != null)
      _atts.addAttribute(SchemaNames.TARGET_NS_ATTR, CDATA, value);

    // -- attributeFormDefault
    Form form = schema.getAttributeFormDefault();
    if (form != null) {
      _atts.addAttribute(SchemaNames.ATTR_FORM_DEFAULT_ATTR, CDATA, form.toString());
    }
    // -- elementFormDefault
    form = schema.getElementFormDefault();
    if (form != null) {
      _atts.addAttribute(SchemaNames.ELEM_FORM_DEFAULT_ATTR, CDATA, form.toString());
    }

    // -- blockDefault
    BlockList blockList = schema.getBlockDefault();
    if (blockList != null) {
      _atts.addAttribute(SchemaNames.BLOCK_DEFAULT_ATTR, CDATA, blockList.toString());
    }

    // -- finalDefault
    FinalList finalList = schema.getFinalDefault();
    if (finalList != null) {
      _atts.addAttribute(SchemaNames.FINAL_DEFAULT_ATTR, CDATA, finalList.toString());
    }

    // --@version
    if (schema.getVersion() != null) {
      _atts.addAttribute(SchemaNames.VERSION_ATTR, CDATA, schema.getVersion());
    }


    // -- modify schemaPrefix to include ':'
    if (schemaPrefix.length() > 0) {
      schemaPrefix += ':';
    }

    _handler.startDocument();

    String ELEM_SCHEMA = schemaPrefix + SCHEMA;

    _handler.startElement(ELEM_SCHEMA, _atts);

    // -- process annotations
    processAnnotated(schema, schemaPrefix);

    // -- process all imported schemas
    Collection<Schema> importedSchemas = schema.getImportedSchema();
    for (Schema importedSchema : importedSchemas) {
      processImport(importedSchema, schemaPrefix);
    }

    // -- process all cached included schemas
    Collection<Schema> enumeration = schema.getCachedIncludedSchemas();
    for (Schema includedSchema : enumeration) {
      processIncludedSchema(includedSchema, schemaPrefix);
    }

    // -- process all redefinitions
    Collection<RedefineSchema> redefinedSchemas = schema.getRedefineSchema();
    for (RedefineSchema redefinedSchema : redefinedSchemas) {
      processRedefinition(redefinedSchema, schema, schemaPrefix);
    }

    // -- process all top level attributeGroup declarations
    Iterator<AttributeGroup> attributeGroups = schema.getAttributeGroups().iterator();
    while (attributeGroups.hasNext()) {
      boolean found = false;
      AttributeGroup temp = (AttributeGroup) attributeGroups.next();
      // -- check if this attributeGroup is not
      // -- part of a redefinition
      if (temp instanceof AttributeGroupDecl) {
        if (((AttributeGroupDecl) temp).isRedefined()) {
          found = true;
        }
      }

      // --check if this attributeGroup is not already included
      Collection<Schema> includedSchemas = schema.getCachedIncludedSchemas();
      for (Schema includedSchema : includedSchemas) {
        if (temp instanceof AttributeGroupDecl) {
          String name = ((AttributeGroupDecl) temp).getName();
          found = (includedSchema.getAttributeGroup(name) != null);
        }
      }

      if (!found) {
        processAttributeGroup(temp, schemaPrefix);
      }
    }

    // -- process all top level attribute declarations
    Collection<AttributeDecl> attributeDeclarations = schema.getAttributes();
    for (AttributeDecl attribute : attributeDeclarations) {
      boolean alreadyIncluded = false;
      // --check if this attribute is not already included
      Iterator<Schema> includedSchemas = schema.getCachedIncludedSchemas().iterator();
      while (includedSchemas.hasNext()) {
        Schema tempSchema = includedSchemas.next();
        // TODO[Java 5.0]: check whether algorithm is wrong !!!
        alreadyIncluded = (tempSchema.getAttribute(attribute.getName()) != null);
      }

      if (!alreadyIncluded) {
        processAttribute(attribute, schemaPrefix);
      }
    }

    // -- process all top level element declarations
    Collection<ElementDecl> elementDeclarations = schema.getElementDecls();
    for (ElementDecl element : elementDeclarations) {
      boolean alreadyIncluded = false;
      // --check if this element is not already included
      Iterator<Schema> includedSchemas = schema.getCachedIncludedSchemas().iterator();
      while (includedSchemas.hasNext()) {
        Schema tempSchema = includedSchemas.next();
        // TODO[Java 5.0]: check whether algorithm is wrong !!!
        alreadyIncluded = (tempSchema.getElementDecl(element.getName()) != null);
      }

      if (!alreadyIncluded) {
        processElement(element, schemaPrefix);
      }
    }

    // -- process all top level complex types
    Collection<ComplexType> complexTypes = schema.getComplexTypes();
    for (ComplexType complexType : complexTypes) {
      boolean alreadyIncluded = false;
      // --check if this complex type is not already included
      Iterator<Schema> includedSchemas = schema.getCachedIncludedSchemas().iterator();
      while (includedSchemas.hasNext()) {
        Schema tempSchema = includedSchemas.next();
        // TODO[Java 5.0]: check whether algorithm is wrong !!!
        alreadyIncluded = (tempSchema.getComplexType(complexType.getName()) != null);
      }
      if (!complexType.isRedefined() && !alreadyIncluded) {
        processComplexType(complexType, schemaPrefix);
      }
    }

    // -- process all top level groups
    Collection<ModelGroup> modelGroups = schema.getModelGroups();
    for (ModelGroup modelGroup : modelGroups) {
      boolean alreadyIncluded = false;
      // --check if this Group is not already included
      Iterator<Schema> includedSchemas = schema.getCachedIncludedSchemas().iterator();
      while (includedSchemas.hasNext()) {
        Schema tempSchema = includedSchemas.next();
        // TODO[Java 5.0]: check whether algorithm is wrong !!!
        alreadyIncluded = (tempSchema.getModelGroup(modelGroup.getName()) != null);
      }

      if (!modelGroup.isRedefined() && !alreadyIncluded) {
        processGroup(modelGroup, schemaPrefix);
      }
    }

    // -- process all top level simple types
    Collection<SimpleType> simpleTypes = schema.getSimpleTypes();
    for (SimpleType simpleType : simpleTypes) {
      boolean found = false;
      // --check if this attributeGroup is not
      // -- included
      Iterator<Schema> includedSchemas = schema.getCachedIncludedSchemas().iterator();
      while (includedSchemas.hasNext()) {
        Schema tempSchema = includedSchemas.next();
        found = (tempSchema.getSimpleType(simpleType.getName()) != null);
      }
      if (!simpleType.isRedefined() && !found) {
        processSimpleType(simpleType, schemaPrefix);
      }
    }

    _handler.endElement(ELEM_SCHEMA);

    _handler.endDocument();

  } // -- processSchema

  /**
   * Process a Wildcard (xsd:any) component
   *
   * @param wildcard the Wildcard to process
   * @param schemaPrefix the namespace prefix to use for schema elements
   */
  private void processWildcard(Wildcard wildcard, String schemaPrefix) throws SAXException {

    String ELEMENT_NAME = null;
    if (wildcard.isAttributeWildcard())
      ELEMENT_NAME = schemaPrefix + SchemaNames.ANY_ATTRIBUTE;
    else
      ELEMENT_NAME = schemaPrefix + SchemaNames.ANY;

    _atts.clear();

    // -- @namespace
    StringBuffer namespace = new StringBuffer();
    Enumeration enumeration = wildcard.getNamespaces();
    while (enumeration.hasMoreElements()) {
      if (namespace.length() > 0)
        namespace.append(' ');
      namespace.append(enumeration.nextElement().toString());
    }
    if (namespace.length() > 0) {
      _atts.addAttribute(SchemaNames.NAMESPACE, CDATA, namespace.toString());
    }

    // -- minOccurs/maxOccurs
    int max = wildcard.getMaxOccurs();
    int min = wildcard.getMinOccurs();

    if (min != 1) {
      _atts.addAttribute(SchemaNames.MIN_OCCURS_ATTR, CDATA, Integer.toString(min));
    }

    if (max < 0) {
      _atts.addAttribute(SchemaNames.MAX_OCCURS_ATTR, CDATA, "unbounded");
    } else if (max != 1) {
      _atts.addAttribute(SchemaNames.MAX_OCCURS_ATTR, CDATA, Integer.toString(max));
    }

    // -- @processContents
    String value = wildcard.getProcessContent();
    if (value != null) {
      _atts.addAttribute(SchemaNames.PROCESS_CONTENTS, CDATA, value);
    }
    _handler.startElement(ELEMENT_NAME, _atts);
    processAnnotated(wildcard, schemaPrefix);
    _handler.endElement(ELEMENT_NAME);

  } // -- processWildcard

  /**
   * Process an imported schema
   *
   * @param schema the imported Schema to process
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processImport(Schema schema, String schemaPrefix) throws SAXException {
    String ELEMENT_NAME = schemaPrefix + SchemaNames.IMPORT;
    _atts.clear();

    String namespace = schema.getTargetNamespace();
    String schemaLoc = schema.getSchemaLocation();

    _atts.addAttribute("namespace", null, namespace);
    _atts.addAttribute("schemaLocation", null, schemaLoc);
    _handler.startElement(ELEMENT_NAME, _atts);
    _handler.endElement(ELEMENT_NAME);
  } // -- processImport

  /**
   * Process an included schema
   *
   * @param schema the imported Schema to process
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processIncludedSchema(Schema schema, String schemaPrefix) throws SAXException {
    String ELEMENT_NAME = schemaPrefix + SchemaNames.INCLUDE;
    _atts.clear();

    String schemaLoc = schema.getSchemaLocation();

    _atts.addAttribute("schemaLocation", null, schemaLoc);
    _handler.startElement(ELEMENT_NAME, _atts);
    _handler.endElement(ELEMENT_NAME);
  } // -- processImport

  /**
   * Process a set of redefinitions
   *
   * @param schema the redefined Schema to process
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processRedefinition(RedefineSchema schema, Schema parentSchema, String schemaPrefix)
      throws SAXException {
    String ELEMENT_NAME = schemaPrefix + SchemaNames.REDEFINE;
    _atts.clear();

    String schemaLoc = schema.getSchemaLocation();
    if (schemaLoc != "") {
      _atts.addAttribute("schemaLocation", null, schemaLoc);
    }

    _handler.startElement(ELEMENT_NAME, _atts);

    // -- process annotations
    processAnnotated(schema, schemaPrefix);

    if (schemaLoc != "") {
      // --process complexTypes
      for (ComplexType complexType : schema.enumerateComplexTypes()) {
        processComplexType(complexType, schemaPrefix);
      }
      // --process simpleTypes
      for (SimpleType simpleType : schema.enumerateSimpleTypes()) {
        processSimpleType(simpleType, schemaPrefix);
      }
      // --process groups
      for (ModelGroup modelGroup : schema.enumerateGroups()) {
        processGroup(modelGroup, schemaPrefix);
      }
      // --process AttributeGroups
      for (AttributeGroup attributeGroup : schema.enumerateAttributeGroups()) {
        processAttributeGroup(attributeGroup, schemaPrefix);
      }
    }
    _handler.endElement(ELEMENT_NAME);
  } // -- processImport

  /**
   * Processes the given simple type definition
   *
   * @param simpleType the simple type definition to process into events
   * @param schemaPrefix the namespace prefix to use for schema elements
   **/
  private void processSimpleType(SimpleType simpleType, String schemaPrefix) throws SAXException {

    if (simpleType.isBuiltInType())
      return;

    String ELEMENT_NAME = schemaPrefix + SIMPLE_TYPE;

    _atts.clear();

    String name = simpleType.getName();

    // -- top-level simple type
    if (name != null) {
      _atts.addAttribute(SchemaNames.NAME_ATTR, CDATA, name);
    }

    // -- @final
    if (simpleType.getFinal() != null) {
      _atts.addAttribute(SchemaNames.FINAL_ATTR, CDATA, simpleType.getFinal());
    }

    // -- @id
    if (simpleType.getId() != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, simpleType.getId());
    }

    _handler.startElement(ELEMENT_NAME, _atts);

    // -- process annotations
    processAnnotated(simpleType, schemaPrefix);

    // -- handle restriction
    SimpleType base = (SimpleType) simpleType.getBaseType();

    /*
     * In case we don't have a direct reference on the base type, check whether its definition can
     * be obtained from one of the imported schemas.
     */
    String typeName = simpleType.getBaseTypeName();
    if (base == null && typeName != null) {
      Schema schema = simpleType.getSchema();
      base = getSimpleTypeFromSchema(schema, typeName);
    }

    boolean isRestriction = false;
    if (base != null) {
      if (simpleType instanceof ListType) {
        isRestriction = (base instanceof ListType);
      } else
        isRestriction = true;
    }

    if (isRestriction) {

      String ELEM_RESTRICTION = schemaPrefix + RESTRICTION;

      _atts.clear();

      typeName = base.getName();
      // -- add "xsd" prefix if necessary
      if (typeName.indexOf(':') < 0) {
        if (base.isBuiltInType()) {
          typeName = schemaPrefix + typeName;
        } else {
          String targetNamespace = base.getSchema().getTargetNamespace();
          String prefix = getNSPrefix(simpleType.getSchema(), targetNamespace);
          if ((prefix != null) && (prefix.length() > 0)) {
            typeName = prefix + ":" + typeName;
          }
        }
      }
      _atts.addAttribute(SchemaNames.BASE_ATTR, CDATA, typeName);

      _handler.startElement(ELEM_RESTRICTION, _atts);

      // -- process facets
      Enumeration enumeration = simpleType.getLocalFacets();
      while (enumeration.hasMoreElements()) {
        Facet facet = (Facet) enumeration.nextElement();
        _atts.clear();
        _atts.addAttribute(SchemaNames.VALUE_ATTR, CDATA, facet.getValue());
        String facetName = schemaPrefix + facet.getName();
        _handler.startElement(facetName, _atts);
        Enumeration annotations = facet.getAnnotations();
        while (annotations.hasMoreElements()) {
          Annotation annotation = (Annotation) annotations.nextElement();
          processAnnotation(annotation, schemaPrefix);
        }
        _handler.endElement(facetName);
      }

      _handler.endElement(ELEM_RESTRICTION);
    } else if (simpleType instanceof Union) {
      processUnion((Union) simpleType, schemaPrefix);
    }
    // -- handle List
    else {

      String ELEM_LIST = schemaPrefix + SchemaNames.LIST;

      _atts.clear();

      SimpleType itemType = ((ListType) simpleType).getItemType();

      boolean topLevel = (itemType.getParent() == itemType.getSchema());
      if (itemType.isBuiltInType() || topLevel) {
        typeName = itemType.getName();
        // -- add "xsd" prefix if necessary
        if ((typeName.indexOf(':') < 0) && itemType.isBuiltInType()) {
          typeName = schemaPrefix + typeName;
        }
        _atts.addAttribute("itemType", CDATA, typeName);
      }
      _handler.startElement(ELEM_LIST, _atts);

      // -- processAnnotations
      Annotation ann = ((ListType) simpleType).getLocalAnnotation();
      if (ann != null) {
        processAnnotation(ann, schemaPrefix);
      }
      // -- process simpleType if necessary
      if ((!topLevel) && (!itemType.isBuiltInType())) {
        processSimpleType(itemType, schemaPrefix);
      }
      _handler.endElement(ELEM_LIST);
    }

    _handler.endElement(ELEMENT_NAME);

  } // -- processSimpleType

  /**
   * Processes the given simpleType Union definition
   *
   * @param union the simpleType Union definition to process into events
   * @param schemaPrefix the namespace prefix to use for schema elements
   * @throws SAXException Indicates a problem in SAX processing.
   **/
  private void processUnion(final Union union, final String schemaPrefix) throws SAXException {

    String elementName = schemaPrefix + SchemaNames.UNION;

    _atts.clear();

    if (union.getId() != null) {
      _atts.addAttribute(SchemaNames.ID_ATTR, CDATA, union.getId());
    }

    // -- process local simpleType references
    StringBuffer memberTypesBuffer = new StringBuffer();
    Enumeration memberTypes = union.getMemberTypes();
    while (memberTypes.hasMoreElements()) {
      SimpleType simpleType = (SimpleType) memberTypes.nextElement();
      // -- ignore local simpleTypes;
      if (!simpleType.isBuiltInType() && simpleType.getParent() != union.getSchema()) {
        continue;
      }
      // -- process top-level references
      if (memberTypesBuffer.length() > 0) {
        memberTypesBuffer.append(' ');
      }
      if (simpleType.isBuiltInType()) {
        memberTypesBuffer.append(schemaPrefix);
      } else {
        String targetNamespace = simpleType.getSchema().getTargetNamespace();
        String prefix = getNSPrefix(simpleType.getSchema(), targetNamespace);
        if ((prefix != null) && (prefix.length() > 0)) {
          memberTypesBuffer.append(prefix + ":");
        }
      }
      memberTypesBuffer.append(simpleType.getName());
    }
    if (memberTypesBuffer.length() > 0) {
      _atts.addAttribute(SchemaNames.MEMBER_TYPES_ATTR, CDATA, memberTypesBuffer.toString());
    }

    _handler.startElement(elementName, _atts);

    // -- process local annotation
    Annotation annotation = union.getLocalAnnotation();
    if (annotation != null) {
      processAnnotation(annotation, schemaPrefix);
    }

    // -- process local simpleType definitions
    Enumeration localMemberTypeDefinitions = union.getMemberTypes();
    while (localMemberTypeDefinitions.hasMoreElements()) {
      SimpleType localMemberTypeDefinition = (SimpleType) localMemberTypeDefinitions.nextElement();
      // -- ignore top-level simpleTypes;
      if (localMemberTypeDefinition.getParent() == union.getSchema()) {
        continue;
      }
      processSimpleType(localMemberTypeDefinition, schemaPrefix);
    }

    _handler.endElement(elementName);

  } // -- processUnion

  /**
   * Determines if a given XMLType is imported by the schema containing the element that refers to
   * it.
   *
   * @param type the type to be checked to see if it is imported
   * @param element the schema element that references the type
   **/
  private boolean isImportedType(XMLType type, ElementDecl element) {
    String targetNS = type.getSchema().getTargetNamespace();
    if (targetNS != null) {
      return (element.getSchema().getImportedSchema(targetNS) != null);
    }
    return false;
  } // -- isImportedType

  /**
   * Determines the proper namespace prefix for a namespace by scanning all declared namespaces for
   * the schema.
   *
   * @param schema the schema in which the namespace is declared
   * @param namespace the namespace for which a prefix will be returned
   **/
  private String getNSPrefix(Schema schema, String namespace) {
    if (namespace == null)
      namespace = "";
    return schema.getNamespaces().getNamespacePrefix(namespace);
  } // -- getNSPrefix


  /**
   * Walks over provided schema and its hierarchy looking for the named type.
   * 
   * @param schema Schema instance where to start searching from.
   * @param typeName Name of the type we search.
   * 
   * @return Definition of the simple type is found, null otherwise.
   */
  private SimpleType getSimpleTypeFromSchema(Schema schema, String typeName) {
    SimpleType base = schema.getSimpleType(typeName);
    if (base == null) {
      Iterator<Schema> imports = schema.getImportedSchema().iterator();
      while (imports.hasNext() && base == null) {
        Schema importedSchema = imports.next();
        base = getSimpleTypeFromSchema(importedSchema, typeName);
      }
    }
    return base;
  }
} // -- SchemaWriter
