/**
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
 *
 * This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
 * All portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
 * Visco. All Rights Reserved.
 *
 * $Id$
 */
package org.exolab.castor.builder;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.exceptions.CastorRuntimeException;
import org.castor.xml.BackwardCompatibilityContext;
import org.castor.xml.InternalContext;
import org.exolab.castor.builder.binding.BindingException;
import org.exolab.castor.builder.binding.BindingLoader;
import org.exolab.castor.builder.binding.ExtendedBinding;
import org.exolab.castor.builder.binding.XMLBindingComponent;
import org.exolab.castor.builder.binding.XPathHelper;
import org.exolab.castor.builder.binding.xml.PackageType;
import org.exolab.castor.builder.binding.xml.PackageTypeChoice;
import org.exolab.castor.builder.binding.xml.types.BindingType;
import org.exolab.castor.builder.conflict.strategy.ClassNameConflictResolver;
import org.exolab.castor.builder.conflict.strategy.TypeClassNameConflictResolver;
import org.exolab.castor.builder.conflict.strategy.XPATHClassNameConflictResolver;
import org.exolab.castor.builder.conflictresolution.WarningViaDialogClassNameCRStrategy;
import org.exolab.castor.builder.factory.FieldInfoFactory;
import org.exolab.castor.builder.factory.SourceFactory;
import org.exolab.castor.builder.info.ClassInfo;
import org.exolab.castor.builder.printing.JClassPrinter;
import org.exolab.castor.builder.printing.JClassPrinterFactory;
import org.exolab.castor.builder.printing.JClassPrinterFactoryRegistry;
import org.exolab.castor.mapping.xml.MappingRoot;
import org.exolab.castor.util.NestedIOException;
import org.exolab.castor.util.Version;
import org.exolab.castor.util.dialog.ConsoleDialog;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.ValidationException;
import org.exolab.castor.xml.XMLException;
import org.exolab.castor.xml.schema.AttributeDecl;
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.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.Particle;
import org.exolab.castor.xml.schema.Schema;
import org.exolab.castor.xml.schema.SchemaContext;
import org.exolab.castor.xml.schema.SchemaContextImpl;
import org.exolab.castor.xml.schema.SimpleContent;
import org.exolab.castor.xml.schema.SimpleType;
import org.exolab.castor.xml.schema.Structure;
import org.exolab.castor.xml.schema.XMLType;
import org.exolab.castor.xml.schema.reader.Sax2ComponentReader;
import org.exolab.castor.xml.schema.reader.SchemaUnmarshaller;
import org.exolab.javasource.JClass;
import org.xml.sax.InputSource;
import org.xml.sax.Parser;
import org.xml.sax.SAXParseException;

/**
 * A Java Source generation tool which uses XML Schema definitions to create an Object model.
 *
 * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a> - Main author.
 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a> - Contributions.
 * @author <a href="mailto:nsgreen@thazar.com">Nathan Green</a> - Contributions.
 * @version $Revision$ $Date: 2006-03-30 14:58:45 -0700 (Thu, 30 Mar 2006) $
 */
public class SourceGenerator extends BuilderConfiguration {

  /** Jakarta's common-logging logger. */
  private static final Log LOG = LogFactory.getLog(SourceGenerator.class);

  // -------------/
  // - Constants -/
  // -------------/
  /** The application name. */
  static final String APP_NAME = "Castor";
  /** The application description. */
  static final String APP_DESC = "XML data binder for Java";
  /** The application version. */
  static final String VERSION = Version.VERSION;
  /** The application URI. */
  static final String APP_URI = "http://www.castor.org";
  /** Warning message to remind users to create source code for imported schema. */
  private static final String IMPORT_WARNING =
      "Note: No code will be generated for the following *imported* schema: ";

  // ----------------------/
  // - Instance Variables -/
  // ----------------------/

  /** Castor internal context - mother of all. */
  private final InternalContext _internalContext;
  /** The XMLBindingComponent used to create Java classes from an XML Schema. */
  private final XMLBindingComponent _bindingComponent;
  /** Our object used to generate source for a single source file. */
  private final SingleClassGenerator _singleClassGenerator;
  /** The field info factory. */
  private final FieldInfoFactory _infoFactory;
  /** Allows us to ask the user questions. */
  private final ConsoleDialog _dialog;
  /** A vector that keeps track of all the schemas processed. */
  private final Vector<Schema> _schemasProcessed = new Vector<Schema>(7);

  /** True if we should suppress non-fatal warnings. */
  private boolean _suppressNonFatalWarnings = false;
  /** Determines whether or not to print extra messages. */
  private boolean _verbose = false;

  /**
   * A flag indicating whether or not to create XML-specific class descriptors for the generated
   * classes.
   */
  private boolean _createDescriptors = true;
  /**
   * A flag indicating whether or not to create JDO-specific class descriptors for the generated
   * classes.
   */
  private boolean _createJdoDescriptors = false;

  /**
   * A flag indicating whether or not to generate sources for imported XML Schemas.
   */
  private boolean _generateImported = false;
  /** The source factory. */
  private SourceFactory _sourceFactory = null;
  /** A flag to indicate that the mapping file should be generated. */
  private boolean _generateMapping = false;
  /** The name of the mapping file to create used with the gen-mapping flag. */
  private String _mappingFilename = "mapping.xml";
  /**
   * A flag indicating whether or not to generate XML marshalling 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;
  /**
   * A flag indicating that enumerated types should be constructed to perform case insensitive
   * lookups based on the values.
   */
  private boolean _caseInsensitive = false;
  /**
   * A flag indicating, if true, that source generation should fail on the first error.
   */
  private boolean _failOnFirstError = false;
  /** A GroupNaming helper class used to named anonymous groups. */
  private GroupNaming _groupNaming = null;
  /** Strategy for name conflict resolution. */
  private String _nameConflictStrategy = WarningViaDialogClassNameCRStrategy.NAME;
  /** JClass to XPATH registry, used for class name conflict resolution. */
  private JClassRegistry _xmlInfoRegistry;

  /**
   * Strategy implementation for resolving class name conflicts.
   */
  private ClassNameConflictResolver _conflictResolver = new XPATHClassNameConflictResolver();

  /**
   * The default type of the {@link JClassPrinterFactory} to use for instantiating instances of
   * {@link JClassPrinter} instances.
   */
  private String _jclassPrinterType = "standard";

  /**
   * {@link JClassPrinterFactoryRegistry} instance to be used for obtaining instances of
   * {@link JClassPrinterFactory} instances.
   */
  private JClassPrinterFactoryRegistry _jclassPrinterFactoryRegistry;

  protected SGStateInfo _sInfo;

  /**
   * Creates a SourceGenerator using the default FieldInfo factory.
   */
  public SourceGenerator() {
    this(null);
  } // -- SourceGenerator

  /**
   * Creates a SourceGenerator using the specific field info Factory.
   *
   * @param infoFactory the FieldInfoFactory to use.
   */
  public SourceGenerator(final FieldInfoFactory infoFactory) {
    this(infoFactory, null);
  }

  /**
   * Creates a SourceGenerator using the specific field info Factory and the given Binding element.
   *
   * @param infoFactory the FieldInfoFactory to use.
   * @param binding the binding element to use.
   */
  public SourceGenerator(final FieldInfoFactory infoFactory, final ExtendedBinding binding) {
    super();

    _internalContext = new BackwardCompatibilityContext();

    setJavaNaming(_internalContext.getJavaNaming());
    _dialog = new ConsoleDialog();
    if (infoFactory == null) {
      if (useOldFieldNaming()) {
        _infoFactory = new FieldInfoFactory();
      } else {
        _infoFactory = new FieldInfoFactory(false);
      }
    } else {
      _infoFactory = infoFactory;
    }

    super.load();

    _groupNaming = new GroupNaming(getJavaNaming());

    _jclassPrinterFactoryRegistry = new JClassPrinterFactoryRegistry(this);
    _singleClassGenerator =
        new SingleClassGenerator(_dialog, this, _nameConflictStrategy, _jclassPrinterType);
    _bindingComponent = new XMLBindingComponent(this, _groupNaming);
    setBinding(binding);

    _conflictResolver.setSourceGenerator(this);
    _xmlInfoRegistry = new JClassRegistry(_conflictResolver, getJavaNaming());
  } // -- SourceGenerator

  /**
   * Returns the selected {@link JClassPrinter} type, as defined by the list of
   * {@link JClassPrinterFactory} instances enlisted in the Castor XML code generator property file.
   * 
   * @return the selected {@link JClassPrinter} type.
   */
  private String getJClassPrinterType() {
    return _jclassPrinterType;
  }

  /**
   * Sets the filename of the mapping file.
   * 
   * @param filename filename of the mapping file
   */
  public final void setMappingFilename(final String filename) {
    _mappingFilename = filename;
  }

  /**
   * Sets the strategy for handling name conflicts.
   *
   * @param nameConflictStrategy the name of the stretegy to use for handling name conflicts.
   */
  public final void setNameConflictStrategy(final String nameConflictStrategy) {
    _nameConflictStrategy = nameConflictStrategy;
    _singleClassGenerator.setNameConflictStrategy(nameConflictStrategy);
  }

  /**
   * Returns the version number of this SourceGenerator.
   *
   * @return the version number of this SourceGenerator
   */
  public static String getVersion() {
    return VERSION;
  } // -- getVersion

  /**
   * Set to true if SAX1 should be used in the marshal method.
   * 
   * @param sax1 true if SAX1 should be used in the marshal method
   */
  public final 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, enumerated type lookups will be performed in a case
   *        insensitive manner.
   */
  public final void setCaseInsensitive(final boolean caseInsensitive) {
    _caseInsensitive = caseInsensitive;
  }

  /**
   * If true, the source generator will fail on the first error encountered. Otherwise, the source
   * generator will continue on certain errors.
   *
   * @param failOnFirstError if true, the source generator will fail on the first error encountered.
   */
  public final void setFailOnFirstError(final boolean failOnFirstError) {
    _failOnFirstError = failOnFirstError;
  }

  /**
   * Sets whether or not to suppress non-fatal warnings encountered during source generation.
   * 
   * @param suppress true if non-fatal warnings should be suppressed.
   */
  public final void setSuppressNonFatalWarnings(final boolean suppress) {
    _singleClassGenerator.setPromptForOverwrite(!suppress);
    _suppressNonFatalWarnings = suppress;
  } // -- setSuppressNonFatalWarnings

  /**
   * Sets whether or not the source code generator prints additional messages during generating
   * source code.
   *
   * @param verbose a boolean, when true indicates to print additional messages
   */
  public void setVerbose(final boolean verbose) {
    _verbose = verbose;
  } // -- setVerbose

  /**
   * Sets the ClassNameConflictResolver instance to be used for automatic class name conflict
   * resolution.
   *
   * @param resolverName The name of the resolver to be used for automatic class name conflict
   *        resolution
   */
  public void setClassNameConflictResolver(final String resolverName) {
    if (resolverName.equals("type")) {
      _conflictResolver = new TypeClassNameConflictResolver();
      _conflictResolver.setSourceGenerator(this);
    } else if (resolverName.equals("xpath")) {
      // leave default
    } else {
      throw new IllegalArgumentException("Invalid resolver type.");
    }
    _xmlInfoRegistry.setClassNameConflictResolver(_conflictResolver);
  } // -- setClassNameConflictResolver

  /**
   * Sets whether or not to create ClassDescriptors for the generated classes. By default,
   * descriptors are generated.
   *
   * @param createDescriptors a boolean, when true indicates to generated ClassDescriptors
   *
   */
  public final void setDescriptorCreation(final boolean createDescriptors) {
    _createDescriptors = createDescriptors;
    _singleClassGenerator.setDescriptorCreation(createDescriptors);
  } // -- setDescriptorCreation

  /**
   * Sets whether or not to create JDO-specific class descriptors for the generated classes. By
   * default, JDO-specific class descriptors are NOT generated.
   *
   * @param createJdoDescriptors a boolean, when true indicates to generated JDO-specific class
   *        descriptors
   *
   */
  public final void setJdoDescriptorCreation(final boolean createJdoDescriptors) {
    _createJdoDescriptors = createJdoDescriptors;
    _singleClassGenerator.setJdoDescriptorCreation(createJdoDescriptors);
  } // -- setDescriptorCreation

  /**
   * Sets the destination directory.
   *
   * @param destDir the destination directory.
   */
  public final void setDestDir(final String destDir) {
    _singleClassGenerator.setDestDir(destDir);
  }

  /**
   * Sets the destination directory for resources, e.g. '.castor.cdr' files.
   *
   * @param destDir the destination directory for resources.
   */
  public final void setResourceDestination(final String destination) {
    _singleClassGenerator.setResourceDestinationDirectory(destination);
  }

  /**
   * 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 final void setCreateMarshalMethods(final boolean createMarshalMethods) {
    _createMarshalMethods = createMarshalMethods;
  } // -- setCreateMarshalMethods

  /**
   * Sets whether or not to generate Java sources for imported XML Schema. By default Java sources
   * for imported XML schemas are not generated.
   *
   * @param generate true to generate the java classes for the imported XML Schema
   */
  public final void setGenerateImportedSchemas(final boolean generate) {
    _generateImported = generate;
  }

  /**
   * Sets whether or not a mapping file should be generated, this is false by default. Note that
   * this will only be used when generation of descriptors has been disabled.
   *
   * @param generateMapping a flag that indicates whether or not a mapping file should be generated.
   */
  public final void setGenerateMappingFile(final boolean generateMapping) {
    _generateMapping = generateMapping;
  } // -- setGenerateMappingFile

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

  /**
   * Sets the binding to use with this instance of the SourceGenerator.
   *
   * @param binding the binding to use, null indicates that the default binding will be used.
   */
  public final void setBinding(final ExtendedBinding binding) {
    if (binding != null) {
      processNamespaces(binding.getPackage());
    }
    // --initialize the XMLBindingComponent
    _bindingComponent.setBinding(binding);
  } // -- setBinding

  /**
   * Sets the binding to use given the path name of a Castor Binding File.
   *
   * @param fileName the file that represents a Binding
   */
  public final void setBinding(final String fileName) {
    try {
      ExtendedBinding binding = BindingLoader.createBinding(fileName);
      setBinding(binding);
    } catch (BindingException e) {
      String err = "Unable to load a binding file due to the following:\n" + e.getMessage()
          + "\nThe Source Generator will continue with no binding file.";
      _dialog.notify(err);
    }
  }

  /**
   * Sets the binding to use given an InputSource identifying a Castor Binding File.
   *
   * @param source an InputSource identifying a Castor Binding File.
   */
  public final void setBinding(final InputSource source) {
    try {
      ExtendedBinding binding = BindingLoader.createBinding(source);
      setBinding(binding);
    } catch (BindingException e) {
      String err = "unable to load a binding file due to the following:\n" + e.getMessage()
          + "\nThe Source Generator will continue with no binding file.";
      _dialog.notify(err);
    }
  }

  /**
   * Sets the line separator to use when printing the source code.
   * <p>
   * <B>Note:</B>This can be any string, so be careful. I recommend either using the default or
   * using one of the following:
   *
   * <PRE>
   * windows systems use: "\r\n"
   * unix systems use: "\n"
   * mac systems use: "\r"
   * </PRE>
   *
   * @param lineSeparator the line separator to use when printing the source code. This method is
   *        useful if you are generating source on one platform, but will be compiling the source on
   *        a different platform.
   */
  public final void setLineSeparator(final String lineSeparator) {
    _singleClassGenerator.setLineSeparator(lineSeparator);
  } // -- setLineSeparator

  /**
   * Tests the org.exolab.castor.builder.javaclassmapping property for the 'element' value.
   *
   * @return True if the Source Generator is mapping schema elements to Java classes.
   */
  public final boolean mappingSchemaElement2Java() {
    if (_bindingComponent != null) {
      ExtendedBinding binding = _bindingComponent.getBinding();
      if (binding != null) {
        BindingType type = binding.getDefaultBindingType();
        if (type != null) {
          return (type == BindingType.ELEMENT);
        }
      }
    }
    return super.mappingSchemaElement2Java();
  } // -- mappingSchemaElement2Java

  /**
   * Tests the org.exolab.castor.builder.javaclassmapping property for the 'type' value.
   *
   * @return True if the Source Generator is mapping schema types to Java classes.
   */
  public final boolean mappingSchemaType2Java() {
    if (_bindingComponent != null) {
      ExtendedBinding binding = _bindingComponent.getBinding();
      if (binding != null) {
        BindingType type = binding.getDefaultBindingType();
        if (type != null) {
          return (type == BindingType.TYPE);
        }
      }
    }
    return super.mappingSchemaType2Java();
  } // -- mappingSchemaType2Java

  /**
   * Creates Java Source code (Object model) for the given XML Schema. If the file exists, opens a
   * FileReader and passes control to {@link #generateSource(InputSource, String)}.
   *
   * @param filename the full path to the XML Schema definition
   * @param packageName the package for the generated source files
   * @throws IOException if an IOException occurs writing the new source files
   */
  public final void generateSource(final String filename, final String packageName)
      throws IOException {
    final File schemaFile;
    if (filename.startsWith("./")) {
      schemaFile = new File(filename.substring(2));
    } else {
      schemaFile = new File(filename);
    }

    FileReader reader = new FileReader(schemaFile);

    try {
      InputSource source = new InputSource(reader);
      source.setSystemId(toURIRepresentation(schemaFile.getAbsolutePath()));
      generateSource(source, packageName);
    } finally {
      try {
        reader.close();
      } catch (java.io.IOException iox) {
        // ignore
      }
    }
  } // -- generateSource

  /**
   * Creates Java Source code (Object model) for the given XML Schema. This method just passes
   * control to {@link #generateSource(InputSource, String)}.
   *
   * @param reader the Reader with which to read the XML Schema definition. The caller should close
   *        the reader, since thie method will not do so.
   * @param packageName the package for the generated source files
   * @throws IOException if an IOException occurs writing the new source files
   */
  public final void generateSource(final Reader reader, final String packageName)
      throws IOException {
    InputSource source = new InputSource(reader);
    generateSource(source, packageName);
  } // -- generateSource

  /**
   * Creates Java Source code (Object model) for the given XML Schema. Parses the schema provided by
   * the InputSource and then calls {@link #generateSource(Schema, String)} to actually generate the
   * source.
   *
   * @param source - the InputSource representing the XML schema.
   * @param packageName the package for the generated source files
   * @throws IOException if an IOException occurs writing the new source files
   */
  public void generateSource(final InputSource source, final String packageName)
      throws IOException {
    // -- get default parser from Configuration
    Parser parser = null;
    try {
      parser = _internalContext.getParser();
    } catch (RuntimeException rte) {
      // ignore
    }

    if (parser == null) {
      _dialog.notify("fatal error: unable to create SAX parser.");
      return;
    }

    SchemaContext schemaContext = new SchemaContextImpl();
    SchemaUnmarshaller schemaUnmarshaller = null;
    try {
      schemaUnmarshaller = new SchemaUnmarshaller(schemaContext);
    } catch (XMLException e) {
      // --The default constructor cannot throw exception so this should never happen
      // --just log the exception
      e.printStackTrace();
      System.exit(1);
    }

    Sax2ComponentReader handler = new Sax2ComponentReader(schemaUnmarshaller);
    parser.setDocumentHandler(handler);
    parser.setErrorHandler(handler);

    try {
      parser.parse(source);
    } catch (java.io.IOException ioe) {
      _dialog.notify("error reading XML Schema file");
      if (_failOnFirstError) {
        throw ioe;
      }
      return;
    } catch (org.xml.sax.SAXException sx) {
      Exception except = sx.getException();
      if (except == null) {
        except = sx;
      }

      if (except instanceof SAXParseException) {
        SAXParseException spe = (SAXParseException) except;
        _dialog.notify("SAXParseException: " + spe);
        _dialog.notify(" - occured at line ");
        _dialog.notify(Integer.toString(spe.getLineNumber()));
        _dialog.notify(", column ");
        _dialog.notify(Integer.toString(spe.getColumnNumber()));
      } else {
        except.printStackTrace();
      }
      if (_failOnFirstError) {
        String msg = "Source Generator: schema parser threw an Exception";
        throw new CastorRuntimeException(msg, sx);
      }
      return;
    }

    Schema schema = schemaUnmarshaller.getSchema();

    try {
      schema.validate();
    } catch (ValidationException vx) {
      throw new NestedIOException(vx);
    }

    generateSource(schema, packageName);
  } // -- generateSource

  /**
   * Creates Java Source code (Object model) for the given XML Schema. Convenience methods exist if
   * you don't have a {@link org.exolab.castor.xml.schema.Schema} already parsed.
   *
   * @param schema the XML schema to generate the Java sources for.
   * @param packageName the package for the generated source files.
   * @throws IOException if this Exception occurs while generating source
   * @see #generateSource(String, String) to provide the schema filename
   * @see #generateSource(Reader, String) to provide a Reader for the schema
   * @see #generateSource(InputSource, String) to provide an InputSource for the schema
   */
  public final void generateSource(final Schema schema, final String packageName)
      throws IOException {
    if (schema == null) {
      throw new IllegalArgumentException("The argument 'schema' must not be null.");
    }

    // --make sure the XML Schema is valid
    try {
      schema.validate();
    } catch (ValidationException ve) {
      String err =
          "The schema: " + schema.getSchemaLocation() + " is not valid.\n" + ve.getMessage();
      throw new IllegalArgumentException(err);
    }

    // Now that we're ready to generate source and we know our configuration
    // has been fully parsed, create our SourceFactory. (See CASTOR-1346.)
    // We will reuse this SourceFactory if we are invoked multiple times.
    if (_sourceFactory == null) {
      _sourceFactory = new SourceFactory(this, _infoFactory, _groupNaming, this);
      _sourceFactory.setCreateMarshalMethods(_createMarshalMethods);
      _sourceFactory.setTestable(_testable);
      _sourceFactory.setSAX1(_sax1);
      _sourceFactory.setCaseInsensitive(_caseInsensitive);
    }

    _sInfo = new SGStateInfo(schema, this);
    _sInfo.setPackageName(packageName);
    _sInfo.setDialog(_dialog);
    _sInfo.setVerbose(_verbose);
    _sInfo.setSuppressNonFatalWarnings(_suppressNonFatalWarnings);

    // --map the schemaLocation of the schema with the packageName defined
    if (packageName != null) {
      super.setLocationPackageMapping(schema.getSchemaLocation(), packageName);
    }

    // --We start with a blank list of schemas processed
    _schemasProcessed.clear();

    generateAllClassFiles(schema, _sInfo);

    // -- TODO Cleanup integration (what does this comment mean?)
    if (!_createDescriptors && _generateMapping) {
      generateMappingFile(packageName, _sInfo);
    }

    // output statistical information from JClassRegistry in 'automatic'mode only
    if (isAutomaticConflictResolution()) {
      _xmlInfoRegistry.printStatistics(_bindingComponent);
    }
  } // -- generateSource

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

  /**
   * Generate all class files for the provided schema. Before processing the current schema, process
   * the schemas imported by this one (unless our configuration says not to).
   *
   * @param schema the schema whose imported schemas to process
   * @param sInfo source generator state information
   * @throws IOException if this Exception occurs while processing an import schema
   */
  private void generateAllClassFiles(final Schema schema, final SGStateInfo sInfo)
      throws IOException {
    // Before processing the current schema, process its imported schemas
    processImportedSchemas(schema, sInfo);

    // -- ** Generate code for all TOP-LEVEL structures **

    // -- register all global element names for name conflict resolution
    for (ElementDecl element : schema.getElementDecls()) {
      _xmlInfoRegistry.prebindGlobalElement(XPathHelper.getSchemaLocation(element));
    }
    for (ModelGroup modelGroup : schema.getModelGroups()) {
      _xmlInfoRegistry.prebindGlobalElement(XPathHelper.getSchemaLocation(modelGroup));
    }

    // -- handle all top-level element declarations
    for (ElementDecl element : schema.getElementDecls()) {
      createClasses(element, sInfo);
    }

    // -- handle all top-level complextypes
    for (ComplexType complexType : schema.getComplexTypes()) {
      processComplexType(complexType, sInfo);
    }

    // -- handle all top-level simpletypes
    for (SimpleType simpleType : schema.getSimpleTypes()) {
      processSimpleType(simpleType, sInfo);
    }

    // -- handle all top-level groups
    for (ModelGroup modelGroup : schema.getModelGroups()) {
      createClasses(modelGroup, sInfo);
    }

    // -- clean up any remaining JClasses which need printing
    _singleClassGenerator.processIfNotAlreadyProcessed(sInfo.keys(), sInfo);

    // -- handle cdr files
    for (Enumeration<String> cdrFiles = sInfo.getCDRFilenames(); cdrFiles.hasMoreElements();) {
      String filename = cdrFiles.nextElement();
      Properties props = sInfo.getCDRFile(filename);
      final FileOutputStream fileOutputStream = new FileOutputStream(new File(filename));
      props.store(fileOutputStream, null);
      fileOutputStream.close();
    }
  } // -- createClasses

  /**
   * Look at each schema imported by the given schema. Either warn that the invoker needs to
   * separately generate source from that schema or process that schema, depending on settings.
   * 
   * @param schema the schema whose imported schemas to process
   * @param sInfo source generator state information
   * @throws IOException if this Exception occurs while processing an import schema
   */
  private void processImportedSchemas(final Schema schema, final SGStateInfo sInfo)
      throws IOException {
    Iterator<Schema> enumeration = schema.getImportedSchema().iterator();
    while (enumeration.hasNext()) {
      Schema importedSchema = enumeration.next();
      if (!_generateImported) {
        LOG.warn("Note: No code will be generated for the following *imported* schema: "
            + importedSchema.getSchemaLocation() + "; if this is on intention, please "
            + "do not forget to generate code for this schema as well. Alternatively,"
            + "consider using the 'generateImportedSchemas' flag.");
        continue;
      }

      _schemasProcessed.add(schema);
      if (!_schemasProcessed.contains(importedSchema)) {
        SGStateInfo importedSInfo = new SGStateInfo(importedSchema, this);
        String packageName = sInfo.getPackageName();
        importedSInfo.setPackageName(packageName);

        // --map the schemaLocation of the imported schema with the packageName defined
        if (packageName != null) {
          setLocationPackageMapping(importedSchema.getSchemaLocation(), packageName);
        }

        generateAllClassFiles(importedSchema, importedSInfo);

        // --'store' the imported JClass instances
        sInfo.storeImportedSourcesByName(importedSInfo.getSourcesByName());
        sInfo.storeImportedSourcesByName(importedSInfo.getImportedSourcesByName());
        // --discard the SGStateInfo
        importedSInfo = null;
      }
    }
  }

  /**
   * Generates the mapping file.
   * 
   * @param packageName Package name to be generated
   * @param sInfo Source Generator current state
   * @throws IOException if this Exception occurs while generating the mapping file
   */
  private void generateMappingFile(final String packageName, final SGStateInfo sInfo)
      throws IOException {
    String pkg = (packageName != null) ? packageName : "";
    MappingRoot mapping = sInfo.getMapping(pkg);
    if (mapping == null) {
      return;
    }

    FileWriter writer = new FileWriter(_mappingFilename);
    try {
      Marshaller marshaller = new Marshaller(writer);
      marshaller.setSuppressNamespaces(true);
      marshaller.marshal(mapping);
    } catch (Exception ex) {
      throw new NestedIOException(ex);
    } finally {
      writer.flush();
      writer.close();
    }
  }

  /**
   * Processes the given Element declaration and creates all necessary classes to support it.
   *
   * @param elementDecl the Element declaration to process
   * @param sInfo our state information
   * @throws IOException if this exception occurs while writing source files
   */
  private void createClasses(final ElementDecl elementDecl, final SGStateInfo sInfo)
      throws IOException {
    if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || elementDecl == null) {
      return;
    }

    // -- when mapping schema types, only interested in producing classes
    // -- for elements with anonymous complex types
    XMLType xmlType = elementDecl.getType();
    if (mappingSchemaType2Java()) {
      if (elementDecl.isReference() || ((xmlType != null) && (xmlType.getName() != null))) {
        return;
      }
    }

    // --create component
    _bindingComponent.setView(elementDecl);

    // -- already processed --> just return
    ClassInfo cInfo = sInfo.resolve(elementDecl);
    if (cInfo != null && cInfo.getJClass() != null) {
      JClass jClass = cInfo.getJClass();
      if (sInfo.processed(jClass)) {
        return;
      }
      jClass = null;
    }

    // -- No type definition
    if (xmlType == null) {
      if (sInfo.verbose()) {
        String msg = "No type found for element: " + elementDecl.getName();
        sInfo.getDialog().notify(msg);
      }
      return;
    } else if (xmlType.isComplexType()) {
      // -- ComplexType
      JClass[] classes = _sourceFactory.createSourceCode(_bindingComponent, sInfo);
      if (!_singleClassGenerator.process(classes, sInfo)) {
        return;
      }

      // only create classes for types that are not imported
      if (xmlType.getSchema() == _bindingComponent.getSchema()) {
        processComplexType((ComplexType) xmlType, sInfo);
      }
    } else if (xmlType.isSimpleType()) {
      // -- SimpleType
      processSimpleType((SimpleType) xmlType, sInfo);
    } else {
      // -- AnyType
      // -- no processing needed for 'anyType'
    }
  } // -- createClasses

  /**
   * Processes the given Group and creates all necessary classes to support it.
   *
   * @param group the Group to process
   * @param sInfo our state information
   * @throws IOException if this exception occurs while writing source files
   */
  private void createClasses(final Group group, final SGStateInfo sInfo) throws IOException {
    if (group == null) {
      return;
    }

    // -- don't generate classes for empty groups
    if (group.getParticleCount() == 0) {
      if (group instanceof ModelGroup) {
        ModelGroup mg = (ModelGroup) group;
        if (mg.isReference()) {
          mg = mg.getReference();
          if (mg.getParticleCount() == 0) {
            return;
          }
        }
      } else {
        return;
      }
    }

    _bindingComponent.setView(group);

    JClass[] classes = _sourceFactory.createSourceCode(_bindingComponent, sInfo);
    processContentModel(group, sInfo);
    _singleClassGenerator.process(classes, sInfo);
  } // -- createClasses

  /**
   * Processes the given ComplexType and creates all necessary classes to support it.
   *
   * @param complexType the ComplexType to process
   * @param sInfo our state information
   * @throws IOException if this exception occurs while writing source files
   */
  private void processComplexType(final ComplexType complexType, final SGStateInfo sInfo)
      throws IOException {
    if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || complexType == null) {
      return;
    }

    _bindingComponent.setView(complexType);

    ClassInfo classInfo = sInfo.resolve(complexType);
    if (classInfo == null) {
      // -- handle top-level complextypes
      if (complexType.isTopLevel()) {
        JClass[] classes = _sourceFactory.createSourceCode(_bindingComponent, sInfo);
        if (!_singleClassGenerator.process(classes, sInfo)) {
          return;
        }
      }

      // -- process AttributeDecl
      processAttributes(complexType, sInfo);

      // --process content type if necessary
      ContentType temp = complexType.getContentType();
      if (temp.getType() == ContentType.SIMPLE) {
        processSimpleType(((SimpleContent) temp).getSimpleType(), sInfo);
      }

      // -- process ContentModel
      processContentModel(complexType, sInfo);
    } else {
      JClass jClass = classInfo.getJClass();
      if (!sInfo.processed(jClass)) {
        // -- process AttributeDecl
        processAttributes(complexType, sInfo);
        // -- process ContentModel
        processContentModel(complexType, sInfo);
        _singleClassGenerator.process(jClass, sInfo);
      }
    }
  } // -- processComplexType

  /**
   * Processes the attribute declarations for the given complex type.
   *
   * @param complexType the ComplexType containing the attribute declarations to process.
   * @param sInfo the current source generator state information
   * @throws IOException if this Exception occurs while generating source files
   */
  private void processAttributes(final ComplexType complexType, final SGStateInfo sInfo)
      throws IOException {
    if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || complexType == null) {
      return;
    }

    Enumeration<AttributeDecl> enumeration = complexType.getAttributeDecls();
    while (enumeration.hasMoreElements()) {
      AttributeDecl attribute = enumeration.nextElement();
      processSimpleType(attribute.getSimpleType(), sInfo);
    }
  } // -- processAttributes

  /**
   * Processes the given ContentModelGroup.
   *
   * @param cmGroup the ContentModelGroup to process
   * @param sInfo the current source generator state information
   * @throws IOException if this Exception occurs while generating source files
   */
  private void processContentModel(final ContentModelGroup cmGroup, final SGStateInfo sInfo)
      throws IOException {
    if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || cmGroup == null) {
      return;
    }

    // Some special code to handle the fact that the enumerate method will simply skip
    // the first group is the number of particle is one

    Enumeration<Particle> enumeration = cmGroup.enumerate();

    while (enumeration.hasMoreElements()) {
      Structure struct = enumeration.nextElement();
      switch (struct.getStructureType()) {
        case Structure.ELEMENT:
          ElementDecl eDecl = (ElementDecl) struct;
          if (eDecl.isReference()) {
            continue;
          }
          createClasses(eDecl, sInfo);
          break;
        case Structure.GROUP:
          processContentModel((Group) struct, sInfo);
          // handle nested groups
          if (!((cmGroup instanceof ComplexType) || (cmGroup instanceof ModelGroup))) {
            createClasses((Group) struct, sInfo);
          }
          break;
        default:
          break;
      }
    }
  } // -- processContentModel

  /**
   * Handle simpleTypes.
   *
   * @param simpleType the SimpleType to be processed
   * @param sInfo the current source generator state information
   * @throws IOException if this Exception occurs while generating source files
   */
  private void processSimpleType(final SimpleType simpleType, final SGStateInfo sInfo)
      throws IOException {
    if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || simpleType == null
        || simpleType.getSchema() != sInfo.getSchema()) {
      return;
    }

    // -- Right now the only time we actually generate source for a simpletype is
    // -- when it's an enumeration
    if (simpleType.hasFacet(Facet.ENUMERATION)) {
      ClassInfo classInfo = sInfo.resolve(simpleType);
      if (classInfo == null) {
        JClass jClass =
            _sourceFactory.createSourceCode(_bindingComponent.getBinding(), simpleType, sInfo);
        _singleClassGenerator.process(jClass, sInfo);
      } else {
        JClass jClass = classInfo.getJClass();
        _singleClassGenerator.process(jClass, sInfo);
      }
    }
  } // -- processSimpleType

  /**
   * Called by setBinding to fill in the mapping between namespaces and Java packages.
   *
   * @param packages the array of package element
   */
  private void processNamespaces(final PackageType[] packages) {
    if (packages.length == 0) {
      return;
    }

    for (PackageType temp : packages) {
      PackageTypeChoice choice = temp.getPackageTypeChoice();
      if (choice.getNamespace() != null) {
        super.setNamespacePackageMapping(choice.getNamespace(), temp.getName());
      } else if (choice.getSchemaLocation() != null) {
        // 1--Handle relative locations
        String tempLocation = choice.getSchemaLocation();
        String currentDir = System.getProperty("user.dir");
        currentDir = currentDir.replace('\\', '/');
        if (tempLocation.startsWith("./")) {
          tempLocation = tempLocation.substring(1);
          tempLocation = currentDir + tempLocation;
        } else if (tempLocation.startsWith("../")) {
          tempLocation = tempLocation.substring("../".length());
          int lastDir = currentDir.lastIndexOf('/');
          currentDir = currentDir.substring(0, lastDir + 1);
          tempLocation = currentDir + tempLocation;
        }
        super.setLocationPackageMapping(tempLocation, temp.getName());
        currentDir = null;
        tempLocation = null;
      }
    }
  }

  /**
   * Returns a string which is the URI of a file.
   * <ul>
   * <li>file:///DOSpath</li>
   * <li>file://UnixPath</li>
   * </ul>
   * No validation is done to check whether the file exists or not. This method will be no longer
   * used when the JDK URL.toString() is fixed.
   *
   * @param path The absolute path of the file.
   * @return A string representing the URI of the file.
   */
  public static String toURIRepresentation(final String path) {
    String result = path;
    if (!new File(result).isAbsolute()) {
      throw new IllegalArgumentException("The parameter must represent an absolute path.");
    }

    if (File.separatorChar != '/') {
      result = result.replace(File.separatorChar, '/');
    }

    if (result.startsWith("/")) {
      result = "file://" + result; /* Unix platform */
    } else {
      result = "file:///" + result; /* DOS platform */
    }

    return result;
  }

  /**
   * For backwards compability, when we are called as the main() routine, delegate the command-line
   * usage to the proper class.
   *
   * @param args our command line arguments.
   * @deprecated Please use {@link SourceGeneratorMain#main(String[])}
   */
  public static void main(final String[] args) {
    LOG.info("org.exolab.castor.builder.SourceGenerator.main() is deprecated. "
        + "Please use org.exolab.castor.builder.SourceGeneratorMain#main() instead.");

    SourceGeneratorMain.main(args);
  }

  /**
   * Returns the {@link JClassRegistry} instance associated with this source generator.
   * 
   * @return the {@link JClassRegistry} instance currently in use.
   */
  public JClassRegistry getXMLInfoRegistry() {
    return _xmlInfoRegistry;
  }

  /**
   * Sets the jclassPrinter type.
   * 
   * @param jClassPrinterType The string identifier of the printer to use.
   */
  public final void setJClassPrinterType(final String jClassPrinterType) {
    LOG.info("Setting JClass printing mode " + jClassPrinterType);
    _jclassPrinterType = jClassPrinterType;
    if (_singleClassGenerator != null) {
      _singleClassGenerator.setJClassPrinterType(jClassPrinterType);
    }
  }

  /**
   * Indicates whether classes should be created for imported XML schemas as well.
   * 
   * @return true if classes should be created for imported schemas.
   */
  public boolean getGenerateImportedSchemas() {
    return _generateImported;
  }

  /**
   * Returns the registry for {@link JClassPrinterFactory} instances.
   * 
   * @return the registry for {@link JClassPrinterFactory} instances.
   */
  public JClassPrinterFactoryRegistry getJClassPrinterFactoryRegistry() {
    return _jclassPrinterFactoryRegistry;
  }

} // -- SourceGenerator
