/*
 * 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-2004 (C) Intalio Inc. All Rights Reserved.
 *
 * This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
 * Portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
 * Visco. All Rights Reserverd.
 *
 * $Id$
 */
package org.exolab.castor.builder.factory;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.castor.core.util.StringUtil;
import org.exolab.castor.builder.AnnotationBuilder;
import org.exolab.castor.builder.BuilderConfiguration;
import org.exolab.castor.builder.FactoryState;
import org.exolab.castor.builder.GroupNaming;
import org.exolab.castor.builder.SGStateInfo;
import org.exolab.castor.builder.SGTypes;
import org.exolab.castor.builder.SourceGenerator;
import org.exolab.castor.builder.SourceGeneratorConstants;
import org.exolab.castor.builder.TypeConversion;
import org.exolab.castor.builder.binding.ExtendedBinding;
import org.exolab.castor.builder.binding.XMLBindingComponent;
import org.exolab.castor.builder.info.ClassInfo;
import org.exolab.castor.builder.info.FieldInfo;
import org.exolab.castor.builder.info.GroupInfo;
import org.exolab.castor.builder.info.XMLInfo;
import org.exolab.castor.builder.info.nature.JDOClassInfoNature;
import org.exolab.castor.builder.info.nature.JDOFieldInfoNature;
import org.exolab.castor.builder.info.nature.SolrjFieldInfoNature;
import org.exolab.castor.builder.info.nature.XMLInfoNature;
import org.exolab.castor.builder.info.nature.relation.JDOOneToManyNature;
import org.exolab.castor.builder.info.nature.relation.JDOOneToOneNature;
import org.exolab.castor.builder.types.XSClass;
import org.exolab.castor.builder.types.XSString;
import org.exolab.castor.builder.types.XSType;
import org.exolab.castor.mapping.AccessMode;
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.AppInfoJpaNature;
import org.exolab.castor.xml.schema.AppInfoSolrjNature;
import org.exolab.castor.xml.schema.AttributeDecl;
import org.exolab.castor.xml.schema.AttributeGroupDecl;
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.Group;
import org.exolab.castor.xml.schema.ModelGroup;
import org.exolab.castor.xml.schema.Order;
import org.exolab.castor.xml.schema.Particle;
import org.exolab.castor.xml.schema.Schema;
import org.exolab.castor.xml.schema.SimpleContent;
import org.exolab.castor.xml.schema.SimpleType;
import org.exolab.castor.xml.schema.SimpleTypesFactory;
import org.exolab.castor.xml.schema.Structure;
import org.exolab.castor.xml.schema.Wildcard;
import org.exolab.castor.xml.schema.XMLType;
import org.exolab.castor.xml.schema.annotations.jdo.Column;
import org.exolab.castor.xml.schema.annotations.jdo.OneToMany;
import org.exolab.castor.xml.schema.annotations.jdo.OneToOne;
import org.exolab.castor.xml.schema.annotations.jdo.PrimaryKey;
import org.exolab.castor.xml.schema.annotations.jdo.Table;
import org.exolab.castor.xml.schema.annotations.solrj.Field;
// import org.exolab.castor.xml.schema.annotations.solrj.Id;
import org.exolab.javasource.JAnnotation;
import org.exolab.javasource.JAnnotationType;
import org.exolab.javasource.JClass;
import org.exolab.javasource.JCollectionType;
import org.exolab.javasource.JConstructor;
import org.exolab.javasource.JDocComment;
import org.exolab.javasource.JDocDescriptor;
import org.exolab.javasource.JEnum;
import org.exolab.javasource.JField;
import org.exolab.javasource.JMethod;
import org.exolab.javasource.JParameter;
import org.exolab.javasource.JSourceCode;
import org.exolab.javasource.JType;

/**
 * Creates the Java Source classes for Schema components.
 *
 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
 * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 */
public final class SourceFactory extends BaseFactory {
  private static final String ENUM_ACCESS_INTERFACE =
      "org.exolab.castor.types.EnumeratedTypeAccess";

  private static final short BASE_TYPE_ENUMERATION = 0;
  private static final short OBJECT_TYPE_ENUMERATION = 1;

  private static final String CLASS_METHOD_SUFFIX = "Class";
  private static final String CLASS_KEYWORD = "class";
  private static final String ITEM_NAME = "Item";

  /** The current Binding for which we are creating classes. */
  private ExtendedBinding _binding = null;
  /** The member factory. */
  private MemberFactory _memberFactory = null;

  private short _enumerationType = OBJECT_TYPE_ENUMERATION;

  /**
   * A flag indicating whether or not to generate XML marshaling framework specific methods.
   */
  private boolean _createMarshalMethods = true;

  /**
   * A flag indicating whether or not to implement CastorTestable (used by the Castor Testing
   * Framework).
   */
  private boolean _testable = false;
  /** A flag indicating that SAX1 should be used when generating the source. */
  private boolean _sax1 = false;
  /** The TypeConversion instance to use for mapping SimpleTypes into XSTypes. */
  private TypeConversion _typeConversion = null;
  /** Enumeration factory used to create code for enumerations. */
  private final EnumerationFactory _enumerationFactory;

  /**
   * Creates a new SourceFactory with the given FieldInfoFactory.
   *
   * @param config the BuilderConfiguration instance (must not be null).
   * @param infoFactory the FieldInfoFactory to use
   * @param groupNaming Group naming scheme to be used.
   * @param sourceGenerator the calling source generator.
   */
  public SourceFactory(final BuilderConfiguration config, final FieldInfoFactory infoFactory,
      final GroupNaming groupNaming, final SourceGenerator sourceGenerator) {
    super(config, infoFactory, groupNaming, sourceGenerator);

    // set the config into the info factory (CASTOR-1346)
    infoFactory.setBoundProperties(config.boundPropertiesEnabled());

    this._memberFactory = new MemberFactory(config, infoFactory, getGroupNaming(), sourceGenerator);
    this._typeConversion = new TypeConversion(getConfig());
    this._enumerationFactory =
        new EnumerationFactory(getConfig(), getGroupNaming(), sourceGenerator);
  } // -- SourceFactory

  /**
   * Sets whether or not to create the XML marshaling framework specific methods (marshal,
   * unmarshal, validate) in the generated classes. By default, these methods are generated.
   *
   * @param createMarshalMethods a boolean, when true indicates to generated the marshaling
   *        framework methods
   *
   */
  public void setCreateMarshalMethods(final boolean createMarshalMethods) {
    _createMarshalMethods = createMarshalMethods;
  } // -- setCreateMarshalMethpds

  /**
   * Sets whether or not to create extra collection methods for accessing the actual collection.
   *
   * @param extraMethods a boolean that when true indicates that extra collection accessor methods
   *        should be created. False by default.
   * @see org.exolab.castor.builder.SourceFactory#setReferenceMethodSuffix
   */
  public void setCreateExtraMethods(final boolean extraMethods) {
    getInfoFactory().setCreateExtraMethods(extraMethods);
  } // -- setCreateExtraMethods

  /**
   * Sets the method suffix (ending) to use when creating the extra collection methods.
   *
   * @param suffix the method suffix to use when creating the extra collection methods. If null or
   *        emtpty the default value, as specified in CollectionInfo will be used.
   * @see org.exolab.castor.builder.SourceFactory#setCreateExtraMethods
   */
  public void setReferenceMethodSuffix(final String suffix) {
    getInfoFactory().setReferenceMethodSuffix(suffix);
  } // -- setReferenceMethodSuffix

  /**
   * Sets whether or not to implement CastorTestable.
   *
   * @param testable if true, indicates to implement CastorTestable
   */
  public void setTestable(final boolean testable) {
    _testable = testable;
  }

  /**
   * Sets to true if SAX1 should be used in the marshall method.
   *
   * @param sax1 true if SAX1 should be used.
   */
  public void setSAX1(final boolean sax1) {
    _sax1 = sax1;
  }

  /**
   * Set to true if enumerated type lookups should be performed in a case insensitive manner.
   *
   * @param caseInsensitive when true
   */
  public void setCaseInsensitive(final boolean caseInsensitive) {
    _enumerationFactory.setCaseInsensitive(caseInsensitive);
  }

  // ------------------/
  // - Public Methods -/
  // ------------------/

  /**
   * Creates a new ClassInfo for the given XMLBindingComponent.
   *
   * @param component the XMLBindingComponent that abstracts all XML Schema definition for a XML
   *        Schema component.
   * @param sgState The given state of the SourceGenerator.
   * @return an array of JClasses reflecting the given XMLBindingComponent.
   */
  public JClass[] createSourceCode(final XMLBindingComponent component, final SGStateInfo sgState) {
    if (component == null) {
      throw new IllegalStateException("XMLBindingComponent may not be null.");
    }
    if (sgState == null) {
      throw new IllegalStateException("SGStateInfo may not be null.");
    }

    // -- check for previous JClass bindings
    JClass[] classes = sgState.getSourceCode(component.getAnnotated());
    if (classes != null) {
      return classes;
    }

    _binding = component.getBinding();

    if (sgState.verbose()) {
      String name = component.getXMLName();
      if (name == null) {
        name = component.getJavaClassName();
      }
      String msg = "Creating classes for: " + name;
      sgState.getDialog().notify(msg);
    }

    // 0-- set the packageName
    String packageName = component.getJavaPackage();
    if (packageName == null || packageName.length() == 0) {
      packageName = sgState.getPackageName();
    }

    // 1-- get the name
    // --if no package used then try to append the default package
    // --used in the SourceGenerator
    String className = component.getQualifiedName();
    if (className.indexOf('.') == -1) {
      // --be sure it is a valid className
      className = getJavaNaming().toJavaClassName(className);
      className = resolveClassName(className, packageName);
    }

    // 2-- check if we have to create an Item class
    boolean createGroupItem = component.createGroupItem();
    if (createGroupItem) {
      className += ITEM_NAME;
      classes = new JClass[2];
    } else {
      classes = new JClass[1];
    }

    // 3-- Create factoryState and chain it to sgState to prevent endless loop
    FactoryState state = new FactoryState(className, sgState, packageName, component);
    state.setCreateGroupItem(createGroupItem);
    if (sgState.getCurrentFactoryState() != null) {
      state.setParent(sgState.getCurrentFactoryState());
    }
    sgState.setCurrentFactoryState(state);

    // --Prevent endless loop
    if (state.processed(component.getAnnotated())) {
      return new JClass[0];
    }

    // -- Mark the enclosed annotated structure as processed in the
    // -- current FactoryState for preventing endless loop.
    state.markAsProcessed(component.getAnnotated());

    //////////////////////////////////////////////////////
    // NOTE: check that the component is not referring to
    // an imported schema to prevent class creation
    //////////////////////////////////////////////////////

    // 4-- intialization of the JClass
    ClassInfo classInfo = state.getClassInfo();
    JClass jClass = state.getJClass();
    initialize(jClass);

    if (classInfo.hasNature(XMLInfoNature.class.getName())) {
      final XMLInfoNature xmlNature = new XMLInfoNature(classInfo);

      // -- name information
      xmlNature.setNodeName(component.getXMLName());

      // -- namespace information
      xmlNature.setNamespaceURI(component.getTargetNamespace());

      // 5--processing the type
      XMLType type = component.getXMLType();
      boolean createForSingleGroup = false;
      boolean creatingForAnElement =
          (component.getAnnotated().getStructureType() == Structure.ELEMENT);

      // -- created from element definition information
      xmlNature.setElementDefinition(creatingForAnElement);

      // deal with substitution groups
      if (creatingForAnElement) {
        ElementDecl elementDeclaration = (ElementDecl) component.getAnnotated();
        Enumeration<ElementDecl> possibleSubstitutes =
            elementDeclaration.getSubstitutionGroupMembers();
        if (possibleSubstitutes.hasMoreElements()) {
          List<String> substitutionGroupMembers = new ArrayList<String>();
          while (possibleSubstitutes.hasMoreElements()) {
            ElementDecl substitute = possibleSubstitutes.nextElement();
            substitutionGroupMembers.add(substitute.getName());
          }
          xmlNature.setSubstitutionGroups(substitutionGroupMembers);
        }
      }

      if (type != null) {
        if (type.isComplexType()) {
          processComplexType(component, sgState, state);
        } else if (type.isSimpleType()) {
          SimpleType simpleType = (SimpleType) type;
          // -- handle our special case for enumerated types
          if (simpleType.hasFacet(Facet.ENUMERATION)) {
            processSimpleTypeEnumeration(component, sgState, classInfo, simpleType);
          } else {
            //////////////////////////////////////////////////////////
            // NOTE: generate sources if the flag for generating sources
            // from imported schemas is on
            //////////////////////////////////////////////////////////
            return new JClass[0];
          }
        } else if (type.isAnyType()) {
          // -- Do not create classes for AnyType
          xmlNature.setSchemaType(new XSClass(SGTypes.OBJECT));
          return new JClass[0];
        }
      } else {
        // --no type we must be facing an XML schema group
        // --MODEL GROUP OR GROUP
        createForSingleGroup = processSchemaGroup(component, state, classInfo);
      }

      // 6--createGroupItem
      if (createGroupItem) {
        // -- create Bound Properties code
        if (component.hasBoundProperties()) {
          createPropertyChangeMethods(jClass);
        }

        sgState.bindReference(jClass, classInfo);

        classes[1] = jClass;

        // -- create main group class
        String fname = component.getJavaClassName() + ITEM_NAME;
        fname = getJavaNaming().toJavaMemberName(fname, false);

        FieldInfo fInfo = null;
        if (createForSingleGroup) {
          // By default a nested group Item can occur only once
          fInfo = getInfoFactory().createFieldInfo(new XSClass(jClass), fname);
        } else {
          fInfo = getInfoFactory().createCollection(new XSClass(jClass), "_items", fname,
              getJavaNaming(), getConfig().useJava50());
        }
        fInfo.setContainer(true);
        String newClassName = className.substring(0, className.length() - 4);
        state = new FactoryState(newClassName, sgState, packageName, component);
        classInfo = state.getClassInfo();
        jClass = state.getJClass();
        initialize(jClass);
        if (type != null && type.isComplexType()) {
          ComplexType complexType = (ComplexType) type;
          if (complexType.isTopLevel() ^ creatingForAnElement) {
            // process attributes and content type since it has not be performed before
            Annotated saved = component.getAnnotated();
            processAttributes(component.getBinding(), complexType, state);
            component.setView(saved);
            if (complexType.getContentType() == ContentType.mixed) {
              FieldInfo fieldInfo = _memberFactory.createFieldInfoForContent(component,
                  new XSString(), getConfig().useJava50());
              handleField(fieldInfo, state, component);
            } else if (complexType.getContentType().getType() == ContentType.SIMPLE) {
              SimpleContent simpleContent = (SimpleContent) complexType.getContentType();
              SimpleType temp = simpleContent.getSimpleType();
              XSType xsType =
                  _typeConversion.convertType(temp, packageName, getConfig().useJava50());
              FieldInfo fieldInfo = _memberFactory.createFieldInfoForContent(component, xsType,
                  getConfig().useJava50());
              handleField(fieldInfo, state, component);
              temp = null;
            } else {
              // handle multi-valued choice group
              xmlNature.setSchemaType(new XSClass(jClass));
            }
          }
        }

        classInfo.addFieldInfo(fInfo);
        fInfo.getMemberAndAccessorFactory().createJavaField(fInfo, jClass);
        fInfo.getMemberAndAccessorFactory().createAccessMethods(fInfo, jClass,
            getConfig().useJava50(), getConfig().getAnnotationBuilders());
        fInfo.getMemberAndAccessorFactory().generateInitializerCode(fInfo,
            jClass.getConstructor(0).getSourceCode());

        // -- name information
        XMLInfoNature xmlInfoNature = new XMLInfoNature(classInfo);
        xmlInfoNature.setNodeName(component.getXMLName());

        // -- mark as a container
        xmlInfoNature.setContainer(true);

        // -- if we have a superclass, make sure that the actual type extends it, not the
        // xxxItem holder class.
        String actSuperClass = classes[1].getSuperClassQualifiedName();
        jClass.setSuperClass(actSuperClass);
        classes[1].setSuperClass(null);
      }

    }

    classes[0] = jClass;

    // 7--set the class information given the component information
    // --base class
    String baseClass = component.getExtends();
    if (baseClass != null && baseClass.length() > 0) {
      // -- at this point if a base class has been set
      // -- it means that it is a class generated for an element
      // -- that extends a class generated for a complexType. Thus
      // -- no change is possible
      if (jClass.getSuperClassQualifiedName() == null) {
        jClass.setSuperClass(baseClass);
      }
    }

    // --interface implemented
    String[] implemented = component.getImplements();
    if (implemented != null) {
      for (String interfaceName : implemented) {
        if ((interfaceName != null) && (interfaceName.length() > 0)) {
          jClass.addInterface(interfaceName);
        }
      }
    }

    // --final
    jClass.getModifiers().setFinal(component.isFinal());

    // --abstract
    if (component.isAbstract()) {
      jClass.getModifiers().setAbstract(true);
      classInfo.setAbstract(true);
    }

    processAppInfo(component.getAnnotated(), classInfo);
    extractAnnotations(component.getAnnotated(), jClass);

    createContructorForDefaultValueForSimpleContent(component.getAnnotated(), classInfo, sgState);
    makeMethods(component, sgState, state, jClass, baseClass);

    if (classInfo.hasNature(JDOClassInfoNature.class.getName())) {
      JDOClassInfoNature jdoNature = new JDOClassInfoNature(classInfo);
      if (jdoNature.getDetachable()) {
        createJdoTimestampImplementations(jClass);
      }
    }

    sgState.bindReference(jClass, classInfo);
    sgState.bindReference(component.getAnnotated(), classInfo);

    // -- Save source code bindings to prevent duplicate code generation
    sgState.bindSourceCode(component.getAnnotated(), classes);

    // custom annotations
    AnnotationBuilder[] annotationBuilders = getConfig().getAnnotationBuilders();
    for (AnnotationBuilder annotationBuilder : annotationBuilders) {
      annotationBuilder.addClassAnnotations(classInfo, jClass);
    }

    return classes;
  }

  /**
   * This method adds a contructor with a string parameter to set the default value of a simple
   * content. If the provided class is not a Simple Content class, nothing is done.
   *
   * @param annotated type information for the class to potentially create a constructor for
   * @param classInfo the ClassInfo for the class to potentially create a constructor for
   */
  private void createContructorForDefaultValueForSimpleContent(final Annotated annotated,
      final ClassInfo classInfo, final SGStateInfo sgStateInfo) {
    FieldInfo textFieldInfo = classInfo.getTextField();

    boolean generate = false;
    boolean inherited = false;

    // check if there is some field inherited from a type
    if (annotated instanceof ElementDecl) {
      XMLType type = ((ElementDecl) annotated).getType();
      ClassInfo typeInfo = sgStateInfo.resolve(type);
      if (typeInfo != null && typeInfo.getTextField() != null) {
        textFieldInfo = typeInfo.getTextField();
        inherited = true;
      }
      generate = (type.isComplexType() && ((ComplexType) type).isSimpleContent());
    }

    // check if we are a complexType ourself
    else if (annotated instanceof ComplexType && ((ComplexType) annotated).isSimpleContent()) {
      generate = true;
    }


    // discard primitiv types and collections
    if (textFieldInfo != null) {
      XSType textFieldType = new XMLInfoNature(textFieldInfo).getSchemaType();
      if (textFieldType != null && textFieldType.getJType().isArray()) {
        generate = false;
      }
    }

    if (!generate) {
      return;
    }

    XMLInfoNature xmlNature = new XMLInfoNature(textFieldInfo);

    // create constructor
    JClass jClass = classInfo.getJClass();
    JParameter parameter = new JParameter(new JClass("java.lang.String"), "defaultValue");
    JConstructor constructor = jClass.createConstructor(new JParameter[] {parameter});
    JSourceCode sourceCode = new JSourceCode();

    if (inherited) {
      sourceCode.add("super(defaultValue);");
    } else {
      sourceCode.add("try {");
      String defaultValue = xmlNature.getSchemaType().createDefaultValueWithString("defaultValue");
      sourceCode.addIndented("setContent(" + defaultValue + ");");
      sourceCode.add(" } catch(Exception e) {");
      sourceCode.addIndented(
          "throw new RuntimeException(\"Unable to cast default value for simple content!\");");
      sourceCode.add(" } ");
    }

    constructor.setSourceCode(sourceCode);
    jClass.addConstructor(constructor);
  }

  /**
   * Extract 'documentation' annotations from the {@link Annotated} instance given.
   * 
   * @param annotated {@link Annotated} instance to extract annotations from.
   * @param jClass {@link JClass} instance to inject annotations into.
   */
  private void extractAnnotations(final Annotated annotated, final JClass jClass) {
    // -- process annotation
    String comment = extractCommentsFromAnnotations(annotated);
    if (comment != null) {
      jClass.getJDocComment().setComment(comment);

      if (getConfig().generateExtraDocumentationMethods()) {
        generateExtraDocumentationMethods(annotated, jClass);
      }
    }
  }

  /**
   * Creates the #getXmlSchemaDocumentation methods for the given JClass.
   * 
   * @param annotated The {@link Annotation} instance to extract the XML schema documentation
   *        instances from.
   * @param parent the JClass to create the #getXmlSchemaDocumentation() methods for
   */
  private void generateExtraDocumentationMethods(final Annotated annotated, final JClass jClass) {
    JField documentationsField =
        new JField(new JClass("java.util.Map"), "_xmlSchemaDocumentations");
    documentationsField.setComment("The content of the <xsd:documentation> elements");
    documentationsField.setInitString("new java.util.HashMap()");
    jClass.addMember(documentationsField);

    Enumeration<Annotation> annotations = annotated.getAnnotations();
    while (annotations.hasMoreElements()) {
      Annotation annotation = annotations.nextElement();
      Enumeration<Documentation> documentations = annotation.getDocumentation();
      while (documentations.hasMoreElements()) {
        Documentation documentation = documentations.nextElement();
        JConstructor defaultConstructor = jClass.getConstructor(0);
        String documentationContent = normalize(documentation.getContent());
        documentationContent = StringUtil.replaceAll(documentationContent, "\n", "\"\n+ \" ");
        defaultConstructor.getSourceCode().add("_xmlSchemaDocumentations.put(\""
            + documentation.getSource() + "\", \"" + documentationContent + "\");");
      }
    }

    JMethod aMethod = new JMethod("getXmlSchemaDocumentations", new JClass("java.util.Map"),
        " A collection of documentation elements.");
    JSourceCode sourceCode = aMethod.getSourceCode();
    sourceCode.add("return _xmlSchemaDocumentations;");
    jClass.addMethod(aMethod);

    JMethod anotherMethod = new JMethod("getXmlSchemaDocumentation", new JClass("java.lang.String"),
        " A specific XML schema documentation element.");
    JParameter parameter = new JParameter(new JClass("java.lang.String"), "source");
    anotherMethod.addParameter(parameter);
    sourceCode = anotherMethod.getSourceCode();
    sourceCode.add("return (java.lang.String) _xmlSchemaDocumentations.get(source);");
    jClass.addMethod(anotherMethod);
  }

  /**
   * Generate methods for our class.
   *
   * @param component
   * @param sgState
   * @param state
   * @param jClass
   * @param baseClass
   */
  private void makeMethods(final XMLBindingComponent component, final SGStateInfo sgState,
      final FactoryState state, final JClass jClass, final String baseClass) {
    // NOTE: be careful with the derivation stuff when generating bounds properties

    if (_createMarshalMethods) {
      // -- #validate()
      createValidateMethods(jClass);
      // --don't generate marshal/unmarshal methods
      // --for abstract classes
      if (!component.isAbstract()) {
        // -- #marshal()
        createMarshalMethods(jClass);
        // -- #unmarshal()
        createUnmarshalMethods(jClass, sgState);
      }
    }

    // create equals() method?
    if (component.hasEquals()) {
      createEqualsMethod(jClass);
      createHashCodeMethod(jClass);
    }

    // implements CastorTestable?
    if (_testable) {
      createTestableMethods(jClass, state);
    }

    // -- This boolean is set to create bound properties
    // -- even if the user has set the SUPER CLASS property
    String superclassQualifiedName = jClass.getSuperClassQualifiedName();
    if (superclassQualifiedName == null || superclassQualifiedName.equals(baseClass)) {
      // -- create Bound Properties code
      if (component.hasBoundProperties()) {
        createPropertyChangeMethods(jClass);
      }
    }
  }

  private boolean processSchemaGroup(final XMLBindingComponent component, final FactoryState state,
      final ClassInfo classInfo) {
    try {
      Group group = (Group) component.getAnnotated();
      processContentModel(group, state);
      component.setView(group);

      // -- Check Group Type
      Order order = group.getOrder();
      GroupInfo groupInfo = new XMLInfoNature(classInfo).getGroupInfo();
      if (order == Order.choice) {
        groupInfo.setAsChoice();
      } else if (order == Order.sequence) {
        groupInfo.setAsSequence();
      } else {
        groupInfo.setAsAll();
      }

      return group.getMaxOccurs() == 1;
    } catch (ClassCastException ce) {
      // --Should not happen
      throw new IllegalArgumentException("Illegal binding component: " + ce.getMessage());
    }
  }

  private void processSimpleTypeEnumeration(final XMLBindingComponent component,
      final SGStateInfo sgState, final ClassInfo classInfo, final SimpleType simpleType) {
    // -- Don't create source code for simple types that
    // -- don't belong in the elements target namespace
    String tns = simpleType.getSchema().getTargetNamespace();
    boolean create = false;
    if (tns == null) {
      create = (component.getTargetNamespace() == null);
    } else {
      create = tns.equals(component.getTargetNamespace());
    }

    if (create) {
      ClassInfo tmpInfo = sgState.resolve(simpleType);
      JClass tmpClass = null;
      if (tmpInfo != null) {
        tmpClass = tmpInfo.getJClass();
      } else {
        tmpClass = createSourceCode(component.getBinding(), simpleType, sgState);
      }
      XMLInfoNature xmlNature = new XMLInfoNature(classInfo);
      xmlNature.setSchemaType(new XSClass(tmpClass));
    }
  }

  private void processComplexType(final XMLBindingComponent component, final SGStateInfo sgState,
      final FactoryState state) {
    XMLType type = component.getXMLType();
    ClassInfo classInfo = state.getClassInfo();
    JClass jClass = state.getJClass();
    boolean creatingForAnElement =
        (component.getAnnotated().getStructureType() == Structure.ELEMENT);

    ComplexType complexType = (ComplexType) type;
    if (complexType.isTopLevel() && creatingForAnElement) {
      // --move the view and keep the structure
      Annotated saved = component.getAnnotated();
      String previousPackage = component.getJavaPackage();
      XMLBindingComponent baseComponent = new XMLBindingComponent(getConfig(), getGroupNaming());
      baseComponent.setBinding(component.getBinding());
      baseComponent.setView(complexType);
      // -- call createSourceCode to pre-process the complexType
      createSourceCode(baseComponent, sgState);
      String baseClassName = null;
      String basePackage = baseComponent.getJavaPackage();
      // --if the base class is not in the same package
      // --of the current class then we have to qualify the base
      // --class
      if (basePackage != null && !basePackage.equals(previousPackage)) {
        baseClassName = baseComponent.getQualifiedName();
        if (baseClassName.indexOf('.') == -1) {
          // --be sure it is a valid className
          baseClassName = getJavaNaming().toJavaClassName(baseClassName);
        }
      } else {
        baseClassName = baseComponent.getJavaClassName();
      }
      jClass.setSuperClass(baseClassName);
      basePackage = null;
      baseClassName = null;
      component.setView(saved);
      saved = null;
    } else if (complexType.isTopLevel() || creatingForAnElement) {
      // generate class if the complexType is anonymous and we create classes
      // for an element OR if the complexType is top-level and we create
      // classes for it.

      // -- check Group type
      if (complexType.getParticleCount() == 1) {
        Particle particle = complexType.getParticle(0);
        if (particle.getStructureType() == Structure.GROUP) {
          Group group = (Group) particle;
          if (group.getOrder() == Order.choice) {
            new XMLInfoNature(classInfo).getGroupInfo().setAsChoice();
          }
        }
      }
      Annotated saved = component.getAnnotated();
      processComplexType(complexType, state);
      component.setView(saved);
      saved = null;
    }
  }

  /**
   * Creates the Java source code to support the given Simpletype.
   *
   * @param binding Current XML binding
   * @param simpleType the Simpletype to create the Java source for
   * @param sgState the current SGStateInfo (cannot be null).
   * @return the JClass representation of the given Simpletype
   */
  public JClass createSourceCode(final ExtendedBinding binding, final SimpleType simpleType,
      final SGStateInfo sgState) {
    if (SimpleTypesFactory.isBuiltInType(simpleType.getTypeCode())) {
      String err = "You cannot construct a ClassInfo for a built-in SimpleType.";
      throw new IllegalArgumentException(err);
    }
    if (sgState == null) {
      throw new IllegalArgumentException("SGStateInfo cannot be null.");
    }

    // -- Unions are currently processed as the built-in
    // -- basetype for the member types of the Union, so
    // -- do nothing for now...however we can warn
    // -- user that no validation will be peformed on the
    // -- union
    if (simpleType.getStructureType() == Structure.UNION) {
      if (!sgState.getSuppressNonFatalWarnings()) {
        String message = "warning: support for unions is incomplete.";
        sgState.getDialog().notify(message);
      }
      return null;
    }

    ClassInfo cInfo = sgState.resolve(simpleType);
    if (cInfo != null) {
      return cInfo.getJClass();
    }

    boolean enumeration = false;

    // -- class name information
    String typeName = simpleType.getName();
    if (typeName == null) {
      Structure struct = simpleType.getParent();
      FactoryState fstate = null;
      switch (struct.getStructureType()) {
        case Structure.ATTRIBUTE:
          typeName = ((AttributeDecl) struct).getName();
          fstate = sgState.getCurrentFactoryState();
          break;
        case Structure.ELEMENT:
          typeName = ((ElementDecl) struct).getName();
          break;
        default:
          // Nothing to do
          break;
      }
      // -- In case of naming collision we append current class name
      if (fstate != null) {
        typeName = getJavaNaming().toJavaClassName(typeName);
        Structure attrDeclParent = ((AttributeDecl) struct).getParent();
        if (attrDeclParent != null
            && attrDeclParent.getStructureType() == Structure.ATTRIBUTE_GROUP) {
          typeName = getJavaNaming()
              .toJavaClassName(((AttributeGroupDecl) attrDeclParent).getName() + typeName);
        } else {
          typeName = fstate.getJClass().getLocalName() + typeName;
        }
      }
      // -- otherwise (???) just append "Type"
      typeName += "Type";
    }

    String className = getJavaNaming().toJavaClassName(typeName);

    // --XMLBindingComponent is only used to retrieve the java package
    // -- we need to optimize it by enabling the binding of simpleTypes.
    XMLBindingComponent comp = new XMLBindingComponent(getConfig(), getGroupNaming());
    if (binding != null) {
      comp.setBinding(binding);
    }

    // set component view for anonymous simple types to parent
    if (simpleType.getName() == null) {
      Annotated annotated = (Annotated) simpleType.getParent();
      comp.setView(annotated);
    } else {
      comp.setView(simpleType);
    }

    String packageName = comp.getJavaPackage();
    if ((packageName == null) || (packageName.length() == 0)) {
      packageName = sgState.getPackageName();
    }

    // reset component view for anonymous simple types
    if (simpleType.getName() == null) {
      comp.setView(simpleType);
    }

    if (simpleType.hasFacet(Facet.ENUMERATION)) {
      enumeration = true;
      // Fix packageName
      // TODO this is a hack I know, we should change this
      if ((packageName != null) && (packageName.length() > 0)) {
        packageName = packageName + "." + SourceGeneratorConstants.TYPES_PACKAGE;
      } else {
        packageName = SourceGeneratorConstants.TYPES_PACKAGE;
      }
    }

    String boundClassName = comp.getJavaClassName();
    if ((boundClassName != null) && (boundClassName.length() > 0)) {
      className = boundClassName;
      typeName = boundClassName;
    }

    className = resolveClassName(className, packageName);

    FactoryState state = new FactoryState(className, sgState, packageName, comp,
        (enumeration && getConfig().useJava5Enums()));

    state.setParent(sgState.getCurrentFactoryState());

    ClassInfo classInfo = state.getClassInfo();
    JClass jClass = state.getJClass();

    initialize(jClass);

    // -- XML information
    Schema schema = simpleType.getSchema();
    XMLInfoNature xmlNature = new XMLInfoNature(classInfo);
    xmlNature.setNamespaceURI(schema.getTargetNamespace());
    xmlNature.setNodeName(typeName);

    extractAnnotations(simpleType, jClass);

    XSClass xsClass = new XSClass(jClass, typeName);

    xmlNature.setSchemaType(xsClass);

    // -- handle enumerated types
    if (enumeration) {
      xsClass.setAsEnumerated(true);
      processEnumeration(binding, simpleType, state);
    }

    // -- create Bound Properties code
    if (state.hasBoundProperties() && !enumeration) {
      createPropertyChangeMethods(jClass);
    }

    if (classInfo.hasNature(JDOClassInfoNature.class.getName())) {
      JDOClassInfoNature jdoNature = new JDOClassInfoNature(classInfo);
      if (jdoNature.getDetachable()) {
        createJdoTimestampImplementations(jClass);
      }
    }

    sgState.bindReference(jClass, classInfo);
    sgState.bindReference(simpleType, classInfo);

    return jClass;
  }

  private void createJdoTimestampImplementations(final JClass jClass) {

    jClass.addInterface("org.exolab.castor.jdo.TimeStampable");

    JField jdoTimestamp = new JField(JType.LONG, "_jdoTimeStamp");
    jClass.addField(jdoTimestamp);

    JMethod getTSMethod =
        new JMethod("jdoGetTimeStamp", JType.LONG, "returns the current time stamp");
    JSourceCode getSourceCode = getTSMethod.getSourceCode();
    getSourceCode.addIndented("return _jdoTimeStamp;");
    jClass.addMethod(getTSMethod);

    JMethod setTSMethod = new JMethod("jdoSetTimeStamp");
    JParameter parameter = new JParameter(JType.LONG, "jdoTimeStamp");
    setTSMethod.addParameter(parameter);
    JSourceCode setSourceCode = setTSMethod.getSourceCode();
    setSourceCode.addIndented("this._jdoTimeStamp = jdoTimeStamp;");
    jClass.addMethod(setTSMethod);
  }


  // -------------------/
  // - Private Methods -/
  // -------------------/

  /**
   * Initializes the given JClass.
   * 
   * @param jClass the JClass to initialize
   */
  private void initialize(final JClass jClass) {
    jClass.addInterface("java.io.Serializable");

    if (getConfig().useJava50()) {
      JAnnotation serial = new JAnnotation(new JAnnotationType("SuppressWarnings"));
      serial.setValue(new String[] {"\"serial\""});
      jClass.addAnnotation(serial);
    }

    // -- add default constructor
    JConstructor con = jClass.createConstructor();
    jClass.addConstructor(con);
    con.getSourceCode().add("super();");
  } // -- initialize

  /**
   * Creates the #marshal methods for the given JClass.
   * 
   * @param parent the JClass to create the #marshal methods for
   */
  private void createPropertyChangeMethods(final JClass parent) {
    // -- add vector to hold listeners
    String vName = "propertyChangeSupport";
    JField field = new JField(SGTypes.PROPERTY_CHANGE_SUPPORT, vName);
    field.getModifiers().makePrivate();
    parent.addField(field);

    // ---------------------------------/
    // - notifyPropertyChangeListeners -/
    // ---------------------------------/

    JMethod jMethod = new JMethod("notifyPropertyChangeListeners");
    jMethod.getModifiers().makeProtected();

    JDocComment jdc = jMethod.getJDocComment();
    JDocDescriptor jdDesc = null;
    String desc = null;

    desc = "Notifies all registered PropertyChangeListeners "
        + "when a bound property's value changes.";
    jdc.appendComment(desc);

    jMethod.addParameter(new JParameter(SGTypes.STRING, "fieldName"));
    jdDesc = jdc.getParamDescriptor("fieldName");
    jdDesc.setDescription("the name of the property that has changed.");

    jMethod.addParameter(new JParameter(SGTypes.OBJECT, "oldValue"));
    jdDesc = jdc.getParamDescriptor("oldValue");
    jdDesc.setDescription("the old value of the property.");

    jMethod.addParameter(new JParameter(SGTypes.OBJECT, "newValue"));
    jdDesc = jdc.getParamDescriptor("newValue");
    jdDesc.setDescription("the new value of the property.");

    parent.addMethod(jMethod);
    JSourceCode jsc = jMethod.getSourceCode();
    // --fix for bug 1026
    jsc.add("if (");
    jsc.append(vName);
    jsc.append(" == null) return;");

    jsc.add(vName);
    jsc.append(".firePropertyChange(fieldName,oldValue,newValue);");

    // -----------------------------/
    // - addPropertyChangeListener -/
    // -----------------------------/

    JType jType = new JClass("java.beans.PropertyChangeListener");
    jMethod = new JMethod("addPropertyChangeListener");

    desc = "Registers a PropertyChangeListener with this class.";
    jdc = jMethod.getJDocComment();
    jdc.appendComment(desc);

    jMethod.addParameter(new JParameter(jType, "pcl"));
    desc = "The PropertyChangeListener to register.";
    jdDesc = jdc.getParamDescriptor("pcl");
    jdDesc.setDescription(desc);

    parent.addMethod(jMethod);

    jsc = jMethod.getSourceCode();

    jsc.add("if (");
    jsc.append(vName);
    jsc.append(" == null) {");
    jsc.addIndented(vName + " = new java.beans.PropertyChangeSupport(this);");
    jsc.add("}");
    jsc.add(vName);
    jsc.append(".addPropertyChangeListener(pcl);");

    // --------------------------------/
    // - removePropertyChangeListener -/
    // --------------------------------/

    jMethod = new JMethod("removePropertyChangeListener", JType.BOOLEAN,
        "always returns true if pcl != null");

    desc = "Removes the given PropertyChangeListener "
        + "from this classes list of ProperyChangeListeners.";
    jdc = jMethod.getJDocComment();
    jdc.appendComment(desc);

    jMethod.addParameter(new JParameter(jType, "pcl"));
    desc = "The PropertyChangeListener to remove.";
    jdDesc = jdc.getParamDescriptor("pcl");
    jdDesc.setDescription(desc);

    parent.addMethod(jMethod);

    jsc = jMethod.getSourceCode();
    jsc.add("if (");
    jsc.append(vName);
    jsc.append(" == null) return false;");

    jsc.add(vName);
    jsc.append(".removePropertyChangeListener(pcl);");
    jsc.add("return true;");
  } // -- createPropertyChangeMethods

  /**
   * Creates the #marshal methods for the given JClass.
   * 
   * @param parent the JClass to create the #marshal methods for
   */
  private void createMarshalMethods(final JClass parent) {
    createMarshalMethods(parent, false);
  } // -- createMarshalMethods

  /**
   * Creates the #marshal methods for the given JClass.
   * 
   * @param parent the JClass to create the #marshal methods for
   * @param isAbstract true if the generated Class should be marked abstract
   */
  private void createMarshalMethods(final JClass parent, final boolean isAbstract) {
    // -- create main marshal method
    JMethod jMethod = new JMethod("marshal");
    jMethod.addException(SGTypes.MARSHAL_EXCEPTION,
        "if object is null or if any SAXException is thrown during marshaling");
    jMethod.addException(SGTypes.VALIDATION_EXCEPTION,
        "if this object is an invalid instance according to the schema");
    jMethod.addParameter(new JParameter(SGTypes.WRITER, "out"));

    // if (_config.useJava50()) {
    // jMethod.addAnnotation(new JAnnotation(new JAnnotationType("Override")));
    // }

    parent.addMethod(jMethod);

    if (isAbstract) {
      jMethod.getModifiers().setAbstract(true);
    } else {
      JSourceCode jsc = jMethod.getSourceCode();
      jsc.add("org.exolab.castor.xml.Marshaller.marshal(this, out);");
    }

    // -- create helper marshal method
    // -- start helper marshal method, this method will
    // -- be built up as we process the given ElementDecl
    jMethod = new JMethod("marshal");
    JClass jc = null;
    if (_sax1) {
      jc = new JClass("org.xml.sax.DocumentHandler");
    } else {
      jc = new JClass("org.xml.sax.ContentHandler");
      jMethod.addException(SGTypes.IO_EXCEPTION, "if an IOException occurs during marshaling");
    }
    jMethod.addException(SGTypes.MARSHAL_EXCEPTION,
        "if object is null or if any SAXException is thrown during marshaling");
    jMethod.addException(SGTypes.VALIDATION_EXCEPTION,
        "if this object is an invalid instance according to the schema");
    jMethod.addParameter(new JParameter(jc, "handler"));
    parent.addMethod(jMethod);

    if (isAbstract) {
      jMethod.getModifiers().setAbstract(true);
    } else {
      JSourceCode jsc = jMethod.getSourceCode();
      jsc.add("org.exolab.castor.xml.Marshaller.marshal(this, handler);");
    }
  } // -- createMarshalMethods

  private void createUnmarshalMethods(final JClass parent, final SGStateInfo sgState) {
    // -- mangle method name to avoid compiler errors when this class is extended
    String methodName = "unmarshal";
    if (sgState.getSourceGenerator().mappingSchemaType2Java()) {
      methodName += parent.getLocalName();
    }

    // -- create main unmarshal method

    // -- search for proper base class
    // TODO[WG]: java 5.0 allows different types for unmarshal method in extension hierarchy
    JClass returnType;
    if (!getConfig().useJava50()) {
      returnType = findBaseClass(parent, sgState);
    } else {
      returnType = parent;
    }

    JMethod jMethod = new JMethod(methodName, returnType, "the unmarshaled " + returnType);
    jMethod.getModifiers().setStatic(true);
    jMethod.addException(SGTypes.MARSHAL_EXCEPTION,
        "if object is null or if any SAXException is thrown during marshaling");
    jMethod.addException(SGTypes.VALIDATION_EXCEPTION,
        "if this object is an invalid instance according to the schema");
    jMethod.addParameter(new JParameter(SGTypes.READER, "reader"));
    parent.addMethod(jMethod);

    JSourceCode jsc = jMethod.getSourceCode();
    jsc.add("return (");
    jsc.append(returnType.getName());
    jsc.append(") org.exolab.castor.xml.Unmarshaller.unmarshal(");
    jsc.append(parent.getName());
    jsc.append(".class, reader);");
  } // -- createUnmarshalMethods

  /**
   * Returns the base class (as found in the schema) of the provided class. Climbs the inheritence
   * tree of the provided class to find and return the base class of the provided class.
   *
   * @param jClass class to find the base class of
   * @param sgState current state of source generation
   * @return the base class of the provided class.
   */
  private JClass findBaseClass(final JClass jClass, final SGStateInfo sgState) {
    JClass returnType = jClass;

    List<JClass> classes = new LinkedList<JClass>();
    classes.add(returnType);

    while (returnType.getSuperClassQualifiedName() != null) {
      String superClassName = returnType.getSuperClassQualifiedName();
      JClass superClass = sgState.getSourceCode(superClassName);
      if (superClass == null) {
        superClass = sgState.getImportedSourceCode(superClassName);
      }

      // A binding can cause us to have to look for the superclass class in
      // the package of the current class
      if (superClass == null && superClassName.indexOf('.') < 0) {
        String pkgName = returnType.getPackageName();
        if (pkgName != null && pkgName.length() > 0) {
          superClassName = pkgName + "." + superClassName;
          superClass = sgState.getSourceCode(superClassName);
        }
      }

      // If returnClass has no superclass then it is the base class
      if (superClass == null) {
        break;
      }

      // Prevent inheritance loops from causing infinite loops
      if (classes.contains(superClass)) {
        StringBuilder buffer = new StringBuilder(64);
        buffer.append("Loop found in class hierarchy: ");
        for (Iterator<JClass> i = classes.iterator(); i.hasNext();) {
          JClass element = i.next();
          // If JClass told us the source of the class (ComplexType, Element, ...
          // then we could report that to and make name conflicts more obvious.
          buffer.append(element.getName());
          buffer.append(" -> ");
        }
        buffer.append(superClass.getName());
        sgState.getDialog().notify(buffer.toString());
        // FIXME We should probably throw an exception here
        break;
      }

      classes.add(superClass);
      returnType = superClass;
    }

    classes.clear();
    return returnType;
  }

  /**
   * Create an "hashCode" method on the given JClass.
   *
   * @param jclass the JClass in wich we create the hashCode method.
   */
  public void createHashCodeMethod(final JClass jclass) {
    if (jclass == null) {
      throw new IllegalArgumentException("JClass must not be null");
    }

    // The argument is not null
    JField[] fields = jclass.getFields();

    // Creates the method signature
    JMethod jMethod = new JMethod("hashCode", JType.INT, "a hash code value for the object.");
    jMethod.setComment("Overrides the java.lang.Object.hashCode method.\n" + "<p>\n"
        + "The following steps came from " + "<b>Effective Java Programming Language Guide</b> "
        + "by Joshua Bloch, Chapter 3");

    // The hashCode method has no arguments
    jclass.addMethod(jMethod);

    JSourceCode jsc = jMethod.getSourceCode();
    if (jclass.getSuperClassQualifiedName() == null) {
      jsc.add("int resultHc = 17;");
    } else {
      jsc.add("int resultHc = super.hashCode();");
    }
    jsc.add("");
    jsc.add("long tmp;");

    for (JField temp : fields) {
      // If the field is an object the hashCode method is called recursively

      JType type = temp.getType();
      String name = temp.getName();
      if (type.isPrimitive()) {
        if (type == JType.BOOLEAN) {
          // Skip the _has_* variables only if they represent
          // a primitive that may or may not be present
          if (!name.startsWith("_has_") || jclass.getField(name.substring(5)) != null) {
            jsc.add("resultHc = 37 * resultHc + (" + name + "?0:1);");
          }
        } else if (type == JType.BYTE || type == JType.INT || type == JType.SHORT) {
          jsc.add("resultHc = 37 * resultHc + " + name + ";");
        } else if (type == JType.LONG) {
          jsc.add("resultHc = 37 * resultHc + (int)(" + name + "^(" + name + ">>>32));");
        } else if (type == JType.FLOAT) {
          jsc.add("resultHc = 37 * resultHc + java.lang.Float.floatToIntBits(" + name + ");");
        } else if (type == JType.DOUBLE) {
          jsc.add("tmp = java.lang.Double.doubleToLongBits(" + name + ");");
          jsc.add("resultHc = 37 * resultHc + (int)(tmp^(tmp>>>32));");
        }
      } else {
        if (getConfig().useCycleBreaker()) {
          // Calculates hashCode in an acyclic recursive manner
          jsc.add("if (" + name + " != null");
          jsc.add("       && !org.castor.core.util.CycleBreaker.startingToCycle(" + name + ")) {");
        } else {
          // Calculates hashCode in a recursive manner
          jsc.add("if (" + name + " != null) {");
        }
        jsc.add("   resultHc = 37 * resultHc + " + name + ".hashCode();");

        if (getConfig().useCycleBreaker()) {
          // Calculates hashCode in an acyclic recursive manner
          jsc.add("   org.castor.core.util.CycleBreaker.releaseCycleHandle(" + name + ");");
        }
        jsc.add("}");
      }
    }
    jsc.add("");
    jsc.add("return resultHc;");
  } // createHashCodeMethod

  /**
   * Create an 'equals' method on the given JClass.
   *
   * @param jclass the Jclass in which we create the equals method
   */
  public void createEqualsMethod(final JClass jclass) {
    if (jclass == null) {
      throw new IllegalArgumentException("JClass must not be null");
    }

    JField[] fields = jclass.getFields();
    JMethod jMethod = new JMethod("equals", JType.BOOLEAN, "true if the objects are equal.");
    jMethod.setComment("Overrides the java.lang.Object.equals method.");
    jMethod.addParameter(new JParameter(SGTypes.OBJECT, "obj"));

    if (getConfig().useJava50()) {
      jMethod.addAnnotation(new JAnnotation(new JAnnotationType("Override")));
    }

    jclass.addMethod(jMethod);
    JSourceCode jsc = jMethod.getSourceCode();
    jsc.add("if ( this == obj )");
    jsc.indent();
    jsc.add("return true;");
    jsc.unindent();
    if (jclass.getSuperClassQualifiedName() != null) {
      jsc.add("");
      jsc.add("if (super.equals(obj)==false)");
      jsc.indent();
      jsc.add("return false;");
      jsc.unindent();
    }
    jsc.add("");
    jsc.add("if (obj instanceof ");
    jsc.append(jclass.getLocalName());
    jsc.append(") {");
    jsc.add("");
    if (fields.length > 0) {
      jsc.indent();
      jsc.add(jclass.getLocalName());
      jsc.append(" temp = (");
      jsc.append(jclass.getLocalName());
      jsc.append(")obj;");

      if (getConfig().useCycleBreaker()) {
        jsc.add("boolean thcycle;");
        jsc.add("boolean tmcycle;");
      }
    }
    for (JField temp : fields) {
      // Be careful to arrayList....

      String name = temp.getName();
      if (temp.getType().isPrimitive()) {
        jsc.add("if (this.");
        jsc.append(name);
        jsc.append(" != temp.");
        jsc.append(name);
        jsc.append(")");
      } else {
        // -- Check first if the field is not null. This can occur while comparing
        // -- two objects that contains non-mandatory fields. We only have to check
        // -- one field since x.equals(null) should return false when equals() is
        // -- correctly implemented.
        jsc.add("if (this.");
        jsc.append(name);
        jsc.append(" != null) {");
        jsc.indent();
        jsc.add("if (temp.");
        jsc.append(name);
        jsc.append(" == null) ");
        jsc.indent();
        jsc.append("return false;");
        jsc.unindent();

        if (getConfig().useCycleBreaker()) {
          jsc.add("if (this.");
          jsc.append(name);
          jsc.append(" != temp.");
          jsc.append(name);
          jsc.append(") {");
          // This prevents string constants and improper DOM subtree self comparisons
          // (where Q(A(B)) and Q'(C(B)) are compared) screwing up cycle detection
          jsc.indent();
          jsc.add("thcycle=org.castor.core.util.CycleBreaker.startingToCycle(this." + name + ");");
          jsc.add("tmcycle=org.castor.core.util.CycleBreaker.startingToCycle(temp." + name + ");");
          // equivalent objects *will* cycle at the same time
          jsc.add("if (thcycle!=tmcycle) {");
          jsc.indent();
          jsc.add("if (!thcycle) { org.castor.core.util.CycleBreaker.releaseCycleHandle(this."
              + name + "); };");
          jsc.add("if (!tmcycle) { org.castor.core.util.CycleBreaker.releaseCycleHandle(temp."
              + name + "); };");
          jsc.add("return false;");
          jsc.unindent();
          jsc.add("}"); // end of unequal cycle point test
          jsc.add("if (!thcycle) {");
          jsc.indent();
        }
        jsc.add("if (!");

        // Special handling for comparing arrays
        if (temp.getType().isArray()) {
          jsc.append("java.util.Arrays.equals(this.");
          jsc.append(name);
          jsc.append(", temp.");
          jsc.append(name);
          jsc.append(")");
        } else {
          jsc.append("this.");
          jsc.append(name);
          jsc.append(".equals(temp.");
          jsc.append(name);
          jsc.append(")");
        }

        if (getConfig().useCycleBreaker()) {
          jsc.append(") {");
        } else {
          jsc.append(") ");
        }
        jsc.indent();

        if (getConfig().useCycleBreaker()) {
          jsc.add("org.castor.core.util.CycleBreaker.releaseCycleHandle(this." + name + ");");
          jsc.add("org.castor.core.util.CycleBreaker.releaseCycleHandle(temp." + name + ");");
        }
        jsc.add("return false;");
        jsc.unindent();

        if (getConfig().useCycleBreaker()) {
          jsc.add("}");

          jsc.add("org.castor.core.util.CycleBreaker.releaseCycleHandle(this." + name + ");");
          jsc.add("org.castor.core.util.CycleBreaker.releaseCycleHandle(temp." + name + ");");
        }

        jsc.unindent();

        if (getConfig().useCycleBreaker()) {
          jsc.add("}"); // end of !thcycle
          jsc.unindent();
          jsc.add("}"); // end of this.name != that.name object constant check
          jsc.unindent();
        }
        jsc.add("} else if (temp."); // end of != null
        jsc.append(name);
        jsc.append(" != null)");
      }
      jsc.indent();
      jsc.add("return false;");
      jsc.unindent();
    }
    jsc.add("return true;");
    jsc.unindent();
    jsc.add("}");
    jsc.add("return false;");
  } // CreateEqualsMethod

  /**
   * Implement org.castor.xmlctf.CastorTestable im the given JClass.
   *
   * @param jclass The JClass which will implement the CastorTestable Interface.
   * @param state our state, e.g., state of this Factory instance.
   */
  public void createTestableMethods(final JClass jclass, final FactoryState state) {
    if (jclass == null) {
      throw new IllegalArgumentException("JClass must not be null");
    }

    jclass.addInterface("org.castor.xmlctf.CastorTestable");
    jclass.addImport("org.castor.xmlctf.CastorTestable");
    jclass.addImport("org.castor.xmlctf.RandomHelper");

    createRandomizeFields(jclass, state); // implementation of randomizeFields
    createDumpFields(jclass); // implementation of dumpFields
  } // CreateTestableMethods

  /**
   * Creates the randomizeFields method for a class that implements the interface
   * org.castor.xmlctf.CastorTestable.
   *
   * @param jclass The JClass which will implement the CastorTestable Interface.
   * @param state
   */
  private void createRandomizeFields(final JClass jclass, final FactoryState state) {
    JMethod jMethod = new JMethod("randomizeFields");
    jMethod.addException(new JClass("InstantiationException"),
        "if we try to instantiate an abstract class or interface");
    jMethod.addException(new JClass("IllegalAccessException"),
        "if we do not have access to the field, for example if it is private");
    jMethod.setComment("implementation of org.castor.xmlctf.CastorTestable");
    jclass.addMethod(jMethod);
    JSourceCode jsc = jMethod.getSourceCode();
    JField[] fields = jclass.getFields();

    for (JField temp : fields) {
      JType type = temp.getType();
      String name = temp.getName();

      if (state.getFieldInfoForChoice() != null
          && name.equals(state.getFieldInfoForChoice().getName())) {
        continue;
      }

      if (name.startsWith("enumConstants")) {
        continue;
      }

      if (name.startsWith("_")) {
        name = getJavaNaming().toJavaClassName(name.substring(1));
      } else {
        name = getJavaNaming().toJavaClassName(name);
      }

      String setName = "set" + name;
      if (name.indexOf("Has") == -1) {
        if (type instanceof JCollectionType) {
          // Collection needs a specific handling
          int listLocat = name.lastIndexOf("List");
          String tempName = name;
          if (listLocat != -1) {
            tempName = tempName.substring(0, listLocat);
          }
          String methodName = getJavaNaming().toJavaClassName(tempName);
          methodName = "get" + methodName;
          JMethod method = jclass.getMethod(methodName, 0);
          // TODO handle the Item introduced in with the group handling
          if (method == null) {
            continue;
          }

          String componentName = method.getReturnType().getName();

          jsc.add(temp.getName());
          jsc.append(" = RandomHelper.getRandom(");
          jsc.append(temp.getName());
          jsc.append(", ");
          jsc.append(componentName);
          jsc.append(".class);");
        } else if (type.isPrimitive()) {
          // Primitive
          jsc.add(setName);
          jsc.append("(RandomHelper.getRandom(");
          jsc.append(temp.getName());
          jsc.append("));");
        } else if (type.isArray()) {
          // Array
          jsc.add(setName);
          jsc.append("((");
          jsc.append(type.toString());
          jsc.append(")RandomHelper.getRandom(");
          jsc.append(temp.getName());
          // Any Class will do, but Array.class seems appropriate
          jsc.append(", java.lang.reflect.Array.class));");
        } else {
          // Object
          jsc.add(setName);
          jsc.append("((");
          jsc.append(type.getName());
          jsc.append(")RandomHelper.getRandom(");
          jsc.append(temp.getName());
          jsc.append(", ");
          jsc.append(type.getName());
          jsc.append(".class));");
        }
        jsc.add("");
      }
    }
  }

  /**
   * Creates the dumpFields method for a class that implements the interface
   * org.castor.xmlctf.CastorTestable.
   *
   * @param jclass The JClass which will implement the CastorTestable Interface.
   */
  private void createDumpFields(final JClass jclass) {
    JMethod jMethod = new JMethod("dumpFields", SGTypes.STRING,
        "a String representation of all of the fields for " + jclass.getName());
    jMethod.setComment("implementation of org.castor.xmlctf.CastorTestable");
    jclass.addMethod(jMethod);
    JSourceCode jsc = jMethod.getSourceCode();
    jsc.add("StringBuffer stringBuffer = new StringBuffer(\"DumpFields() for element: ");
    jsc.append(jclass.getName());
    jsc.append("\\n\");");

    for (JField temp : jclass.getFields()) {
      String name = temp.getName();
      if ((temp.getType().isPrimitive()) || temp.getType().getName().startsWith("java.lang.")) {
        // hack when using the option 'primitivetowrapper'
        // this should not interfere with other cases
        jsc.add("stringBuffer.append(\"Field ");
        jsc.append(name);
        jsc.append(":\" +");
        jsc.append(name);
        jsc.append("+\"\\n\");");
      } else if (temp.getType().isArray()) {
        jsc.add("if (");
        jsc.append(name);
        jsc.append(" != null) {");
        jsc.indent();
        jsc.add("stringBuffer.append(\"[\");");
        jsc.add("for (int i = 0; i < ");
        jsc.append(name);
        jsc.append(".length; i++) {");
        jsc.indent();
        jsc.add("stringBuffer.append(");
        jsc.append(name);
        jsc.append("[i] + \" \");");
        jsc.unindent();
        jsc.add("}");
        jsc.add("stringBuffer.append(\"]\");");
        jsc.unindent();
        jsc.add("}");
      } else {
        jsc.add("if ( (");
        jsc.append(name);
        jsc.append(" != null) && (");
        jsc.append(name);
        jsc.append(".getClass().isAssignableFrom(CastorTestable.class)))");
        jsc.indent();
        jsc.add("stringBuffer.append(((CastorTestable)");
        jsc.append(name);
        jsc.append(").dumpFields());");
        jsc.unindent();
        jsc.add("else stringBuffer.append(\"Field ");
        jsc.append(name);
        jsc.append(":\" +");
        jsc.append(name);
        jsc.append("+\"\\n\");");
      }
      jsc.add("");
    }
    jsc.add("");
    jsc.add("return stringBuffer.toString();");
  }

  /**
   * Creates the Validate methods for the given JClass.
   * 
   * @param jClass the JClass to create the Validate methods for
   */
  private void createValidateMethods(final JClass jClass) {
    JMethod jMethod = null;
    JSourceCode jsc = null;

    // -- #validate
    jMethod = new JMethod("validate");
    jMethod.addException(SGTypes.VALIDATION_EXCEPTION,
        "if this object is an invalid instance according to the schema");

    jClass.addMethod(jMethod);
    jsc = jMethod.getSourceCode();
    jsc.add("org.exolab.castor.xml.Validator validator = new ");
    jsc.append("org.exolab.castor.xml.Validator();");
    jsc.add("validator.validate(this);");

    // -- #isValid
    jMethod = new JMethod("isValid", JType.BOOLEAN,
        "true if this object is valid according to the schema");
    jsc = jMethod.getSourceCode();
    jsc.add("try {");
    jsc.indent();
    jsc.add("validate();");
    jsc.unindent();
    jsc.add("} catch (org.exolab.castor.xml.ValidationException vex) {");
    jsc.indent();
    jsc.add("return false;");
    jsc.unindent();
    jsc.add("}");
    jsc.add("return true;");
    jClass.addMethod(jMethod);
  } // -- createValidateMethods

  // -------------------/
  // - Private Methods -/
  // -------------------/

  /**
   * Resolves the className out of the given name and the packageName.
   *
   * @param name the class name
   * @param packageName the package name
   * @return the full qualified class name.
   */
  private String resolveClassName(final String name, final String packageName) {
    if ((packageName != null) && (packageName.length() > 0)) {
      return packageName + "." + name;
    }
    return name;
  } // -- resolveClassName

  //////////////////////////////////////////////
  // Process XML Schema structures
  // Note: This code is XML specific, it has to be moved somehow in XMLBindingComponent.
  // The aim of the SourceFactory is to generate code from a BindingComponent.
  ///////////////////////////////////////////////

  /**
   * This method handles the processing of AppInfo elements from the input schema and adds the
   * information found therein to the specified {@link ClassInfo}.
   * 
   * @param annotated the {@link org.exolab.castor.xml.schema.Structure} holding the {@link AppInfo}
   *        annotations.
   * @param cInfo the {@link ClassInfo} to store the information found in the AppInfos in.
   * @see ClassInfo
   * @see AppInfo
   * @see Annotated
   */
  private void processAppInfo(final Annotated annotated, final ClassInfo cInfo) {
    Enumeration<Annotation> annotations = annotated.getAnnotations();
    while (annotations.hasMoreElements()) {
      Annotation ann = annotations.nextElement();
      Enumeration<AppInfo> appInfos = ann.getAppInfo();
      while (appInfos.hasMoreElements()) {
        AppInfo appInfo = appInfos.nextElement();
        if (appInfo.hasNature(AppInfoJpaNature.class.getName())) {
          AppInfoJpaNature nature = new AppInfoJpaNature(appInfo);
          List<?> content = nature.getContent();
          if (content != null && !content.isEmpty()) {
            Iterator<?> it = content.iterator();
            if (it.hasNext()) {
              cInfo.addNature(JDOClassInfoNature.class.getName());
              JDOClassInfoNature cNature = new JDOClassInfoNature(cInfo);
              while (it.hasNext()) {
                Object tmpObject = it.next();
                if (tmpObject instanceof Table) {
                  Table table = (Table) tmpObject;
                  cNature.setTableName(table.getName());
                  cNature.setAccessMode(AccessMode.valueOf("shared"));
                  cNature.setDetachable(table.isDetachable());
                  // TODO: Uncomment next line as soon as Annotation Classes have been updated!
                  // cNature.setAccessMode(AccessMode.valueOf(table.getAccessMode().toString()));
                  PrimaryKey pk = table.getPrimaryKey();
                  Iterator<? extends String> pIt = pk.iterateKey();
                  while (pIt.hasNext()) {
                    cNature.addPrimaryKey(pIt.next());
                  }
                }
              }
            }
          }
        }
      }
    }
  }

  /**
   * This method handles the processing of AppInfo elements from the input schema and adds the
   * information found therein to the specified {@link FieldInfo}.
   * 
   * @param annotated the {@link org.exolab.castor.xml.schema.Structure} holding the {@link AppInfo}
   *        annotations.
   * @param fInfo the {@link ClassInfo} to store the information found in the AppInfos in.
   * @see FieldInfo
   * @see AppInfo
   * @see Annotated
   */
  private void processAppInfo(final Annotated annotated, final FieldInfo fInfo) {
    Enumeration<Annotation> annotations = annotated.getAnnotations();

    while (annotations.hasMoreElements()) {
      Annotation ann = annotations.nextElement();
      Enumeration<AppInfo> appInfos = ann.getAppInfo();
      while (appInfos.hasMoreElements()) {
        AppInfo appInfo = appInfos.nextElement();

        if (appInfo.hasNature(AppInfoSolrjNature.class.getName())) {
          AppInfoSolrjNature nature = new AppInfoSolrjNature(appInfo);
          Object solrjRawContent = nature.getContent();
          if (solrjRawContent != null) {
            fInfo.addNature(SolrjFieldInfoNature.class.getName());
            SolrjFieldInfoNature solrjNature = new SolrjFieldInfoNature(fInfo);
            if (solrjRawContent instanceof Field) {
              Field solrjField = (Field) solrjRawContent;
              if (StringUtils.isNotBlank(solrjField.getName())) {
                solrjNature.setFieldName(solrjField.getName());
              }
            }
          }
        }

        if (appInfo.hasNature(AppInfoJpaNature.class.getName())) {
          AppInfoJpaNature nature = new AppInfoJpaNature(appInfo);
          List<?> content = nature.getContent();
          if (content != null && !content.isEmpty()) {
            Iterator<?> it = content.iterator();
            if (it.hasNext()) {
              while (it.hasNext()) {
                Object tmpObject = it.next();
                if (tmpObject instanceof Column) {
                  fInfo.addNature(JDOFieldInfoNature.class.getName());
                  JDOFieldInfoNature fNature = new JDOFieldInfoNature(fInfo);
                  Column column = (Column) tmpObject;
                  fNature.setColumnName(column.getName());
                  fNature.setColumnType(column.getType());
                  fNature.setReadOnly(column.isReadOnly());
                  fNature.setDirty(false);
                  fNature.setDirty(column.getDirty());
                } else if (tmpObject instanceof OneToOne) {
                  OneToOne relation = (OneToOne) tmpObject;
                  fInfo.addNature(JDOOneToOneNature.class.getName());
                  JDOOneToOneNature oneNature = new JDOOneToOneNature(fInfo);
                  oneNature.addForeignKey(relation.getName());
                  oneNature.setDirty(relation.isDirty());
                  oneNature.setReadOnly(relation.isReadOnly());
                } else if (tmpObject instanceof OneToMany) {
                  OneToMany relation = (OneToMany) tmpObject;
                  fInfo.addNature(JDOOneToManyNature.class.getName());
                  JDOOneToManyNature manyNature = new JDOOneToManyNature(fInfo);
                  manyNature.addForeignKey(relation.getName());
                  manyNature.setDirty(relation.isDirty());
                  manyNature.setReadOnly(relation.isReadOnly());
                }
              }
            }
          }
        }
      }
    }
  }

  /**
   * Process the attributes contained in this complexType.
   * 
   * @param binding
   * @param complexType the given complex type.
   * @param state the given FactoryState
   */
  private void processAttributes(final ExtendedBinding binding, final ComplexType complexType,
      final FactoryState state) {
    if (complexType == null) {
      return;
    }

    Enumeration<AttributeDecl> enumeration = complexType.getAttributeDecls();
    XMLBindingComponent component = new XMLBindingComponent(getConfig(), getGroupNaming());
    if (_binding != null) {
      component.setBinding(_binding);
    }

    while (enumeration.hasMoreElements()) {
      AttributeDecl attr = enumeration.nextElement();

      component.setView(attr);

      // -- if we have a new SimpleType...generate ClassInfo
      SimpleType sType = attr.getSimpleType();

      // look for simpleType def in base type(s)
      XMLType baseXMLType = complexType.getBaseType();
      while (sType == null) {
        // If no simple type found: Get the same attribute of the base type.
        // If base type is not complex, forget it; break out of loop now.
        if (baseXMLType == null || !(baseXMLType instanceof ComplexType)) {
          break;
        }

        // There's a base complexType; get the attribute with the same name
        // as this attribute (=attr) from it
        final ComplexType baseComplexType = (ComplexType) baseXMLType;
        AttributeDecl baseAttribute = baseComplexType.getAttributeDecl(attr.getName());

        if (baseAttribute != null) {
          // See if this one has a simple-type...
          sType = baseAttribute.getSimpleType();
          if (sType != null) {
            attr.setSimpleType(sType);
            break;
          }
        }

        // ... if not, go another step higher in the class hierarchy
        baseXMLType = baseXMLType.getBaseType();
      }

      // Look for referenced type (if any) for setting type, and use
      // it, if found.
      if (sType == null && attr.getReference() != null) {
        SimpleType referencedSimpleType = attr.getReference().getSimpleType();
        attr.setSimpleType(referencedSimpleType);
        sType = referencedSimpleType;
      }

      if (sType != null && !(SimpleTypesFactory.isBuiltInType(sType.getTypeCode()))) {
        if (sType.getSchema() == component.getSchema() && state.resolve(sType) == null) {
          if (sType.hasFacet(Facet.ENUMERATION)) {
            createSourceCode(component.getBinding(), sType, state.getSGStateInfo());
          }
        }
      }

      FieldInfo fieldInfo =
          _memberFactory.createFieldInfo(component, state, getConfig().useJava50());
      handleField(fieldInfo, state, component);
    }
  }

  /**
   * @param complexType the ComplexType to process
   * @param state the FactoryState.
   */
  private void processComplexType(final ComplexType complexType, final FactoryState state) {
    XMLBindingComponent component = new XMLBindingComponent(getConfig(), getGroupNaming());
    if (_binding != null) {
      component.setBinding(_binding);
    }
    component.setView(complexType);

    String typeName = component.getXMLName();

    ClassInfo classInfo = state.getClassInfo();
    XMLInfoNature xmlNature = new XMLInfoNature(classInfo);
    xmlNature.setSchemaType(new XSClass(state.getJClass(), typeName));

    /// I don't believe this should be here: kv 20030423
    /// classInfo.setNamespaceURI(component.getTargetNamespace());

    // - Handle derived types
    XMLType base = complexType.getBaseType();

    // -- if the base is a complexType, we need to process it
    if (base != null) {
      if (base.isComplexType()) {
        String baseClassName = null;

        component.setView(base);
        // -- Is this base type from the schema we are currently generating source for?
        //////////////////////////////////////////////////////////
        // NOTE: generate sources if the flag for generating sources
        // from imported schemas in on
        //////////////////////////////////////////////////////////
        if (base.getSchema() == complexType.getSchema()) {
          ClassInfo cInfo = state.resolve(base);
          // --no classInfo yet so no source code available
          // --for the base type: we need to generate it
          if (cInfo == null) {
            JClass[] classes = createSourceCode(component, state.getSGStateInfo());
            cInfo = state.resolve(base);
            baseClassName = classes[0].getName();
          } else {
            baseClassName = cInfo.getJClass().getName();
          }
          // set the base class
          classInfo.setBaseClass(cInfo);
        } else {
          // -- Create qualified class name for a base type class
          // -- from another package
          baseClassName = component.getQualifiedName();
        }
        // -- Set super class
        // -- and reset the view on the current ComplexType
        component.setView(complexType);
        // only set a super class name if the current complexType is not a
        // restriction of a simpleContent (--> no object hierarchy, only content hierarchy)
        /*
         * Note: There are times when a simpleContent restriction needs to extend the hierarchy,
         * such as a restriction of a restriction, so I'm commenting out the following line for now.
         * see bug 1875 for more details. If this causes any regressions we'll need to find a more
         * appropriate solution. if (! ( complexType.isRestricted() &&
         * ((ComplexType)base).isSimpleContent() ) )
         */
        state.getJClass().setSuperClass(baseClassName);
      } // --complexType

      // --if the content type is a simpleType create a field info for it.
      if (complexType.getContentType().getType() == ContentType.SIMPLE) {
        SimpleContent simpleContent = (SimpleContent) complexType.getContentType();
        SimpleType temp = simpleContent.getSimpleType();
        SimpleType baseType = (SimpleType) temp.getBaseType();
        XSType xsType =
            _typeConversion.convertType(temp, state.getPackageName(), getConfig().useJava50());

        FieldInfo fieldInfo = null;
        if ((baseType != null) && extendsSimpleType(state.getJClass(), baseType, state)) {
          if (xsType.isEnumerated()) {
            fieldInfo = _memberFactory.createFieldInfoForContent(component, xsType,
                getConfig().useJava50());
            fieldInfo.setBound(false);

            handleField(fieldInfo, state, component);

            // -- remove getter since we don't need to override the original getter
            String mname = fieldInfo.getReadMethodName();
            JClass jClass = state.getJClass();
            JMethod method = jClass.getMethod(mname, 0);
            jClass.removeMethod(method);

            // -- update setter method
            mname = fieldInfo.getWriteMethodName();
            method = jClass.getMethod(mname, 0);
            JSourceCode jsc = method.getSourceCode();
            jsc.add("super.");
            jsc.append(mname);
            jsc.append("(this.");
            jsc.append(fieldInfo.getName());
            jsc.append(".toString());");
          }
          // -- else just use superclass setters/getters
          // -- do nothing
        } else {
          //
          while (temp.getBaseType() != null && !temp.isBuiltInType()) {
            temp = (SimpleType) temp.getBaseType();
          }

          xsType =
              _typeConversion.convertType(temp, state.getPackageName(), getConfig().useJava50());
          fieldInfo =
              _memberFactory.createFieldInfoForContent(component, xsType, getConfig().useJava50());
          handleField(fieldInfo, state, component);
        }
      }
    } // --base not null

    // ---------------------/
    // - handle attributes -/
    // - and mixed content -/
    // ---------------------/

    if (!state.isCreateGroupItem()) {
      processAttributes(component.getBinding(), complexType, state);
      // --reset the view on the current ComplexType
      component.setView(complexType);
      if (complexType.getContentType() == ContentType.mixed) {
        FieldInfo fieldInfo = _memberFactory.createFieldInfoForContent(component, new XSString(),
            getConfig().useJava50());
        handleField(fieldInfo, state, component);
      }
    }
    // --process the contentModelGroup
    processContentModel(complexType, state);
  } // -- processComplextype

  /**
   * Processes the given ContentModelGroup. This method is responsible for creating FieldInfos (or
   * sometimes ClassInfos) for elements and model group contained in the given ContentModelGroup.
   *
   * @param model the ContentModelGroup to process
   * @param state the current FactoryState.
   */
  private void processContentModel(final ContentModelGroup model, final FactoryState state) {
    // ------------------------------/
    // - handle elements and groups -/
    // ------------------------------/

    ContentModelGroup contentModel = model;
    Enumeration<Particle> enumeration = contentModel.enumerate();

    // -- handle choice item
    if (new XMLInfoNature(state.getClassInfo()).isChoice()
        && state.getFieldInfoForChoice() == null) {
      state.setFieldInfoForChoice(_memberFactory.createFieldInfoForChoiceValue());
      state.getFieldInfoForChoice().getMemberAndAccessorFactory()
          .createJavaField(state.getFieldInfoForChoice(), state.getJClass());
      state.getFieldInfoForChoice().getMemberAndAccessorFactory().createAccessMethods(
          state.getFieldInfoForChoice(), state.getJClass(), getConfig().useJava50(),
          getConfig().getAnnotationBuilders());
    }

    FieldInfo fieldInfo = null;
    XMLBindingComponent component = new XMLBindingComponent(getConfig(), getGroupNaming());
    if (_binding != null) {
      component.setBinding(_binding);
    }

    while (enumeration.hasMoreElements()) {
      Annotated annotated = enumeration.nextElement();
      component.setView(annotated);

      switch (annotated.getStructureType()) {
        case Structure.ELEMENT: // -- handle element declarations
          fieldInfo = _memberFactory.createFieldInfo(component, state, getConfig().useJava50());
          // -- Fix for element declarations being used in
          // -- a group with minOccurs = 0;
          // -- (kvisco - 20021007)
          if (contentModel.getMinOccurs() == 0) {
            XMLInfoNature xmlNature = new XMLInfoNature(fieldInfo);
            xmlNature.setRequired(false);
          }

          handleField(fieldInfo, state, component);
          break;
        case Structure.GROUP: // -- handle groups
          Group group = (Group) annotated;
          // set the compositor
          if ((contentModel instanceof ComplexType) || (contentModel instanceof ModelGroup)) {
            if (group.getOrder() == Order.choice) {
              new XMLInfoNature(state.getClassInfo()).getGroupInfo().setAsChoice();
            } else if (group.getOrder() == Order.all) {
              new XMLInfoNature(state.getClassInfo()).getGroupInfo().setAsAll();
            } else if (group.getOrder() == Order.sequence) {
              new XMLInfoNature(state.getClassInfo()).getGroupInfo().setAsSequence();
            }
          }

          // -- create class member,if necessary
          if (!((contentModel instanceof ComplexType) || (contentModel instanceof ModelGroup))) {
            if (contentModel instanceof ModelGroup) {
              ModelGroup mg = (ModelGroup) contentModel;
              if (mg.isReference()) {
                contentModel = mg.getReference();
              }
            }

            if (contentModel.getParticleCount() > 0) {
              fieldInfo = _memberFactory.createFieldInfo(component, state.getSGStateInfo(),
                  getConfig().useJava50());
              handleField(fieldInfo, state, component);
            }
          } else {
            // --else we just flatten the group
            processContentModel(group, state);
          }
          break;

        case Structure.MODELGROUP:
          ModelGroup modelgroup = (ModelGroup) annotated;
          // --a Model Group definition can only referenced
          // --another group at this point.
          // get the contentModel and proccess it
          if (modelgroup.getName() != null) {
            // create the field info for the element
            // that is referring to a model group in order
            // not to loose the Particle information
            if (modelgroup.isReference()) {
              modelgroup = modelgroup.getReference();
            }

            if (modelgroup.getParticleCount() > 0) {
              fieldInfo = _memberFactory.createFieldInfo(component, state.getSGStateInfo(),
                  getConfig().useJava50());
              handleField(fieldInfo, state, component);
            }
            break;
          }
          // --else we just flatten the group
          processContentModel(modelgroup.getContentModelGroup(), state);
          break;

        case Structure.WILDCARD:
          Wildcard wildcard = (Wildcard) annotated;
          FieldInfo fieldForAny =
              _memberFactory.createFieldInfoForAny(wildcard, getConfig().useJava50());
          handleField(fieldForAny, state, component);
          break;

        default:
          break;
      }
    }

  } // -- process(ContentModelGroup)

  /**
   * Creates all the necessary enumeration code from the given Creates all the necessary enumeration
   * code from the given SimpleType. Enumerations are handled a couple ways.
   *
   * @param binding CUrrent XML binding
   * @param simpleType the SimpleType we are processing an enumeration for
   * @param state our current state
   * @see #processEnumerationAsBaseType
   */
  private void processEnumeration(final ExtendedBinding binding, final SimpleType simpleType,
      final FactoryState state) {
    // Added by robertlaferla at comcast dot net 01/21/2004
    if (getConfig().useEnumeratedTypeInterface()) {
      state.getJClass().addInterface(ENUM_ACCESS_INTERFACE);
    } // end enumTypeInterface

    switch (_enumerationType) {
      case BASE_TYPE_ENUMERATION:
        processEnumerationAsBaseType(binding, simpleType, state);
        break;
      default:
        processEnumerationAsNewObject(binding, simpleType, state);
        break;
    }
  } // -- processEnumeration

  /**
   * Creates all the necessary enumeration code from the given SimpleType. Delegates to
   * EnumerationFactory.
   *
   * @param binding Current XML binding
   * @param simpleType the SimpleType we are processing an enumeration for
   * @param state our current state
   * @see #processEnumerationAsBaseType
   */
  private void processEnumerationAsNewObject(final ExtendedBinding binding,
      final SimpleType simpleType, final FactoryState state) {
    _enumerationFactory.processEnumerationAsNewObject(binding, simpleType, state);
    if (_testable && state.getJClass() instanceof JEnum) {
      createTestableMethods(state.getJClass(), state);
    }
  } // -- processEnumerationAsNewObject

  /**
   * Delegates creation of enumeration code to EnumerationFactory.
   *
   * @param binding Current XML binding
   * @param simpleType the SimpleType we are processing an enumeration for
   * @param state our current state
   */
  private void processEnumerationAsBaseType(final ExtendedBinding binding,
      final SimpleType simpleType, final FactoryState state) {
    _enumerationFactory.processEnumerationAsBaseType(binding, simpleType, state);
  } // -- processEnumerationAsBaseType

  /**
   * Adds a given FieldInfo to the JClass and ClassInfo stored in the given FactoryState.
   *
   * @param fieldInfo The fieldInfo to add
   * @param state the current FactoryState
   * @param component the current XMLBindingComponent to read AppInfos from.
   */
  private void handleField(final FieldInfo fieldInfo, final FactoryState state,
      final XMLBindingComponent component) {
    if (fieldInfo == null) {
      return;
    }

    XMLInfoNature xmlNature = new XMLInfoNature(fieldInfo);

    if (CLASS_METHOD_SUFFIX.equals(fieldInfo.getMethodSuffix())) {
      SGStateInfo sInfo = state.getSGStateInfo();
      if (!sInfo.getSuppressNonFatalWarnings()) {
        String warn = "warning a field name conflicts with \"" + CLASS_KEYWORD
            + "\", please use a binding file to specify " + "a different name for the "
            + xmlNature.getNodeTypeName() + " '" + xmlNature.getNodeName() + "'.";
        sInfo.getDialog().notify(warn);
      }
    } else if (CLASS_KEYWORD.equals(xmlNature.getNodeName())) {
      SGStateInfo sInfo = state.getSGStateInfo();
      if (!sInfo.getSuppressNonFatalWarnings()) {
        String warn = "warning a field name conflicts with \"" + CLASS_KEYWORD
            + "\" and is being replaced by \"clazz\". "
            + "You may use a binding file to specify a different " + "name for the "
            + xmlNature.getNodeTypeName() + " '" + xmlNature.getNodeName() + "'.";
        sInfo.getDialog().notify(warn);
      }
    }

    processAppInfo(component.getAnnotated(), fieldInfo);

    JSourceCode scInitializer = state.getJClass().getConstructor(0).getSourceCode();

    ClassInfo base = state.getClassInfo().getBaseClass();
    boolean present = false;
    if (base != null) {
      switch (new XMLInfoNature(fieldInfo).getNodeType()) {
        case ATTRIBUTE:
          present = (base.getAttributeField(xmlNature.getNodeName()) != null);
          break;
        case ELEMENT:
          String baseNodeName = xmlNature.getNodeName();
          // TODO[WG]: replace this error check with something more meaningful
          if (baseNodeName != null
              && !(baseNodeName.equals(XMLInfo.CHOICE_NODE_NAME_ERROR_INDICATION))) {
            // check whether there is an equally named field in the base
            // class, and don't forget to consider the namspaces as well to
            // determine whether the fields are really equal.
            FieldInfo inheritedFieldInfo = base.getElementField(baseNodeName);

            if (inheritedFieldInfo != null) {
              String namespaceURI = xmlNature.getNamespaceURI();
              if (namespaceURI != null
                  && namespaceURI.equals(new XMLInfoNature(inheritedFieldInfo).getNamespaceURI())) {
                present = true;
              }
            }
          }
          break;
        default:
          break;
      }
    }

    state.getClassInfo().addFieldInfo(fieldInfo);
    present = present && !xmlNature.isMultivalued();
    // create the relevant Java fields only if the field
    // info is not yet in the base classInfo or if it is not a collection
    if (!present) {
      if (state.getFieldInfoForChoice() != null) {
        if (fieldInfo != state.getFieldInfoForChoice()) {
          fieldInfo.setFieldInfoReference(state.getFieldInfoForChoice());
        }
      }

      fieldInfo.getMemberAndAccessorFactory().createJavaField(fieldInfo, state.getJClass());
      // -- do not create access methods for transient fields
      if (!fieldInfo.isTransient()) {
        fieldInfo.getMemberAndAccessorFactory().createAccessMethods(fieldInfo, state.getJClass(),
            getConfig().useJava50(), getConfig().getAnnotationBuilders());
        if (fieldInfo.isBound()) {
          state.setBoundProperties(true);
        }
      }
    }

    // -- Add initialization code
    fieldInfo.getMemberAndAccessorFactory().generateInitializerCode(fieldInfo, scInitializer);
  } // -- handleField

  /**
   * Returns true if the given JClass extends the class represented by the given SimpleType.
   *
   * @param jClass the JClass to check
   * @param type the SimpleType to check against
   * @param state the FactoryState
   *
   * @return true if the given JClass extends the class associated with the given SimpleType,
   *         otherwise false.
   */
  private boolean extendsSimpleType(final JClass jClass, final SimpleType type,
      final FactoryState state) {
    String superClassName = jClass.getSuperClassQualifiedName();
    if (superClassName != null) {
      ClassInfo cInfo = state.resolve(type);
      if (cInfo != null) {
        return superClassName.equals(cInfo.getJClass().getName());
      }
    }
    return false;
  } // -- extendsSimpleType

} // -- SourceFactory
