/*
 * 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.PrintWriter;
import java.util.Properties;

import org.exolab.castor.builder.binding.BindingException;
import org.exolab.castor.builder.binding.BindingLoader;
import org.exolab.castor.builder.factory.FieldInfoFactory;
import org.exolab.castor.util.CommandLineOptions;
import org.xml.sax.InputSource;

/**
 * Main line method for command-line invokation of the source generation tool. Because this class
 * exists only to provide a main(String[]) for command-line use, all methods are static.
 *
 * @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.
 * @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a> - Cut out of SourceGenerator.java
 * @version $Revision: 0000 $ $Date: $
 */
public final class SourceGeneratorMain {
  // --------------------------/
  // - Command line arguments -/
  // --------------------------/

  private static final String ARGUMENT_BINDING_FILENAME = "binding-file";
  private static final String ARGUMENT_CASE_INSENSITIVE = "case-insensitive";
  private static final String ARGUMENT_DESTINATION_DIR = "dest";
  private static final String ARGUMENT_DISABLE_DESCRIPTORS = "nodesc";
  private static final String ARGUMENT_FORCE = "f";
  private static final String ARGUMENT_GENERATE_IMPORTED_SCHEMAS = "generateImportedSchemas";
  private static final String ARGUMENT_GENERATE_MAPPING = "gen-mapping";
  private static final String ARGUMENT_HELP = "h";
  private static final String ARGUMENT_INPUT = "i";
  private static final String ARGUMENT_INPUT_SOURCE = "is";
  private static final String ARGUMENT_LINE_SEPARATOR = "line-separator";
  private static final String ARGUMENT_NOMARSHALL = "nomarshall";
  private static final String ARGUMENT_PACKAGE = "package";
  private static final String ARGUMENT_RESOURCES_DESTINATION_DIR = "resourcesDestination";
  private static final String ARGUMENT_SAX1 = "sax1";
  private static final String ARGUMENT_TESTABLE = "testable";
  private static final String ARGUMENT_TYPES = "types";
  private static final String ARGUMENT_TYPES_DEPRECATED = "type-factory";
  private static final String ARGUMENT_TYPES_JAVA2 = "j2";
  private static final String ARGUMENT_VERBOSE = "verbose";
  private static final String ARGUMENT_FAIL_ON_ERROR = "fail";
  private static final String ARGUMENT_NAME_CONFLICT_STRATEGY = "nameConflictStrategy";
  private static final String ARGUMENT_NAME_JCLASSPRINTER = "classPrinter";
  private static final String ARGUMENT_USE_OLD_FIELD_NAMING = "useOldFieldNaming";

  private static final String ARG_VALUE_LINE_SEPARATION_MAC = "mac";
  private static final String ARG_VALUE_LINE_SEPARATION_UNIX = "unix";
  private static final String ARG_VALUE_LINE_SEPARATION_WIN = "win";

  // -------------------------/
  // - Command line messages -/
  // -------------------------/

  /** Message used when descriptor creation is disabled. */
  private static final String DISABLE_DESCRIPTORS_MSG =
      "Disabling generation of Class descriptors.";

  /** Message used when marshaling methods creation is disabled. */
  private static final String DISABLE_MARSHALL_MSG =
      "Disabling generation of Marshaling framework methods (marshal, unmarshal, validate).";

  /** Message used when implementing CastorTestable. */
  private static final String CASTOR_TESTABLE_MSG =
      "The generated classes will implement org.castor.xmlctf.CastorTestable.";

  /** Message used when using SAX1. */
  private static final String SAX1_MSG = "The generated classes will use SAX 1.";

  private static final String GENERATE_IMPORT_MSG =
      "Imported XML Schemas will be processed automatically.";

  private static final String CASE_INSENSITIVE_MSG =
      "The generated classes will use a case insensitive method "
          + "for looking up enumerated type values.";

  private static final String TYPE_FACTORY_ARG_MSG =
      "The argument '-type-factory' is deprecated; please use '-types' in its place.";

  private static final String SUPPRESS_NON_FATAL_WARN_MSG = "Suppressing non fatal warnings.";

  private static final String GENERATING_MAPPING_FILE_MSG = "Generating mapping file: ";

  private static final String BINDING_FILE_ERROR1_MSG = "Unable to load binding file ";

  private static final String BINDING_FILE_ERROR2_MSG = " due to the following Exception:";

  private static final String BINDING_FILE_ERROR3_MSG = "No binding file will be used.";

  private static final String INVALID_TYPES_OPTION_MSG = "Invalid option for '-types': ";

  private static final String DEFAULT_FIELD_INFO_MSG = "Using default source generator types.";

  private static final String LINE_SEPARATION_WIN_MSG = "Using Windows style line separation.";

  private static final String LINE_SEPARATION_UNIX_MSG = "Using UNIX style line separation.";

  private static final String LINE_SEPARATION_MAC_MSG = "Using Macintosh style line separation.";

  private static final String DEFAULT_LINE_SEPARATOR_MSG =
      "Using default line separator for this platform";

  private static final String INVALID_LINE_SEPARATOR_MSG = "Invalid option for line-separator: ";

  private static final String NAME_CONFLICT_STRATEGY_MSG = "Using name conflict strategy ";

  private static final String JCLASSPRINTER_TYPE_MSG = "Using JClass printing type ";

  private static final String USING_SEPARATE_RESOURCES_DIRECTORY =
      "Using a separate destination for resources.";

  private static final String USE_OLD_FIELD_NAMING_MSG = "Using old Java field naming conventions";

  private static final String USE_NEW_FIELD_NAMING_MSG = "Using new Java field naming conventions";

  /** The full set of command-line options. */
  private static final CommandLineOptions ALL_OPTIONS = setupCommandLineOptions();

  /**
   * As a static utility class, we want a private constructor.
   */
  private SourceGeneratorMain() {
    // Private constructor
  }

  //////////////////
  // MAIN METHOD //
  //////////////////

  /**
   * Parses the command line, converting everything into the proper form for the source generation
   * main class, then invokes source generation.
   *
   * @param args the String[] consisting of the command line arguments
   */
  public static void main(final String[] args) {
    // -- Process the specified command line options
    Properties options = ALL_OPTIONS.getOptions(args);

    // -- check for help option
    if (options.getProperty(ARGUMENT_HELP) != null) {
      PrintWriter pw = new PrintWriter(System.out, true);
      ALL_OPTIONS.printHelp(pw);
      return;
    }

    // -- Make sure we have a schema to work on
    String schemaFilename = options.getProperty(ARGUMENT_INPUT);
    String schemaURL = options.getProperty(ARGUMENT_INPUT_SOURCE);

    if (schemaFilename == null && schemaURL == null) {
      System.out.println(SourceGenerator.APP_NAME);
      ALL_OPTIONS.printUsage(new PrintWriter(System.out));
      return;
    }

    // Instantiate our SourceGenerator
    FieldInfoFactory factory = getTypeFactory(options);
    SourceGenerator sgen = (factory == null) ? new SourceGenerator() : new SourceGenerator(factory);

    // Everything below here sets options on our SourceGenerator

    sgen.setLineSeparator(getLineSeparator(options.getProperty(ARGUMENT_LINE_SEPARATOR)));
    sgen.setDestDir(options.getProperty(ARGUMENT_DESTINATION_DIR));

    // set a resource destination if specified
    String resourcesDestination = options.getProperty(ARGUMENT_RESOURCES_DESTINATION_DIR);
    if (resourcesDestination != null) {
      sgen.setResourceDestination(resourcesDestination);
      System.out.print("-- ");
      System.out.println(USING_SEPARATE_RESOURCES_DIRECTORY);
    }

    sgen.setVerbose(options.getProperty(ARGUMENT_VERBOSE) != null);
    sgen.setFailOnFirstError(options.getProperty(ARGUMENT_FAIL_ON_ERROR) != null);

    boolean force = (options.getProperty(ARGUMENT_FORCE) != null);
    sgen.setSuppressNonFatalWarnings(force);
    if (force) {
      System.out.print("-- ");
      System.out.println(SUPPRESS_NON_FATAL_WARN_MSG);
    }

    if (options.getProperty(ARGUMENT_DISABLE_DESCRIPTORS) != null) {
      sgen.setDescriptorCreation(false);
      System.out.print("-- ");
      System.out.println(DISABLE_DESCRIPTORS_MSG);
    }

    String mappingFilename = options.getProperty(ARGUMENT_GENERATE_MAPPING);
    if (mappingFilename != null) {
      sgen.setGenerateMappingFile(true);
      if (mappingFilename.length() > 0) {
        sgen.setMappingFilename(mappingFilename);
      }
      System.out.print("-- ");
      System.out.println(GENERATING_MAPPING_FILE_MSG + "'" + mappingFilename + "'");
    }

    if (options.getProperty(ARGUMENT_NOMARSHALL) != null) {
      sgen.setCreateMarshalMethods(false);
      System.out.print("-- ");
      System.out.println(DISABLE_MARSHALL_MSG);
    }

    if (options.getProperty(ARGUMENT_TESTABLE) != null) {
      sgen.setTestable(true);
      System.out.print("-- ");
      System.out.println(CASTOR_TESTABLE_MSG);
    }

    if (options.getProperty(ARGUMENT_SAX1) != null) {
      sgen.setSAX1(true);
      System.out.print("-- ");
      System.out.println(SAX1_MSG);
    }

    if (options.getProperty(ARGUMENT_CASE_INSENSITIVE) != null) {
      sgen.setCaseInsensitive(true);
      System.out.print("-- ");
      System.out.println(CASE_INSENSITIVE_MSG);
    }

    String nameConflictStrategy = options.getProperty(ARGUMENT_NAME_CONFLICT_STRATEGY);
    if (nameConflictStrategy != null) {
      sgen.setNameConflictStrategy(nameConflictStrategy);
      System.out.print("-- ");
      System.out.println(CASE_INSENSITIVE_MSG + nameConflictStrategy);
    }

    String jClassPrinterType = options.getProperty(ARGUMENT_NAME_JCLASSPRINTER);
    if (jClassPrinterType != null) {
      sgen.setJClassPrinterType(jClassPrinterType);
      System.out.print("-- ");
      System.out.println(JCLASSPRINTER_TYPE_MSG + jClassPrinterType);
    }

    String bindingFilename = options.getProperty(ARGUMENT_BINDING_FILENAME);
    if (bindingFilename != null) {
      try {
        sgen.setBinding(BindingLoader.createBinding(bindingFilename));
      } catch (BindingException e) {
        System.out.print("--");
        System.out.println(
            BINDING_FILE_ERROR1_MSG + "'" + bindingFilename + "'" + BINDING_FILE_ERROR2_MSG);
        e.printStackTrace();
        System.out.print("--");
        System.out.println(BINDING_FILE_ERROR3_MSG);
      }
    }

    if (options.getProperty(ARGUMENT_GENERATE_IMPORTED_SCHEMAS) != null) {
      sgen.setGenerateImportedSchemas(true);
      System.out.print("-- ");
      System.out.println(GENERATE_IMPORT_MSG);
    }

    try {
      if (schemaFilename != null) {
        sgen.generateSource(schemaFilename, options.getProperty(ARGUMENT_PACKAGE));
      } else if (schemaURL != null) {
        sgen.generateSource(new InputSource(schemaURL), options.getProperty(ARGUMENT_PACKAGE));
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  } // -- main

  /**
   * Creates and returns a <code>FieldInfoFactory</code> (or null if none requested or if an error
   * is encountered making the requested type) to be used during source generation. If the
   * <code>-types</code> argument is not found, check for the deprecated but still supported
   * <code>-type-factory</code> argument. If we find that, give a deprecation warning.
   * <p>
   * Once we've parsed the command-line arguments, if there are no arguemnts appropriate for a type
   * factory, then we have nothing to do. Otherwise, try to instantiate the requested
   * <code>FieldInfoFactory</code>. If the <code>FieldInfoFactory</code> throws an
   * <code>IllegalArgumentException</code> then try to instantiate the class provided, which allows
   * someone to provide their own implementation of a <code>FieldInfoFactory</code>, which must
   * extend (@link org.exolab.castor.builder.FieldInfoFactory).
   *
   * @param options the full set of command-line options
   * @return a FieldInfoFactory to be used during source generation, or null if the default should
   *         be used
   * @see org.exolab.castor.builder.FieldInfoFactory
   */
  private static FieldInfoFactory getTypeFactory(final Properties options) {
    String typeFactory = options.getProperty(ARGUMENT_TYPES);
    if (typeFactory == null) {
      // This backwards-compatible option is retained temporarily
      typeFactory = options.getProperty(ARGUMENT_TYPES_DEPRECATED);
      if (typeFactory != null) {
        System.out.print("-- ");
        System.out.println(TYPE_FACTORY_ARG_MSG);
      }
    }

    // -- For backwards compatibility
    if (typeFactory != null && typeFactory.equals(ARGUMENT_TYPES_JAVA2)) {
      typeFactory = "arraylist";
    }

    // If no command-line arguments, then we have nothing to do
    if (typeFactory == null) {
      return null;
    }

    FieldInfoFactory factory = null;
    try {
      System.out.print("-- ");
      if (Boolean.valueOf(options.getProperty(ARGUMENT_USE_OLD_FIELD_NAMING, "true"))) {
        System.out.println(USE_OLD_FIELD_NAMING_MSG);
        factory = new FieldInfoFactory(typeFactory);
      } else {
        System.out.println(USE_NEW_FIELD_NAMING_MSG);
        factory = new FieldInfoFactory(typeFactory, false);
      }
    } catch (IllegalArgumentException e) {
      try {
        // Allow someone to provide their own FieldInfoFactory implementation
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        factory = (FieldInfoFactory) classLoader.loadClass(typeFactory).newInstance();
      } catch (Exception e2) {
        System.out.println("-- " + INVALID_TYPES_OPTION_MSG + typeFactory);
        System.out.println("-- " + e.getMessage());
        System.out.println("-- " + DEFAULT_FIELD_INFO_MSG);
      }
    }

    return factory;
  }

  /**
   * Parses the command-line argument for line separator style, handles bad values, and returns a
   * value that the source generator will understand.
   *
   * @param lineSepStyle the command-line argument for line separation style
   * @return the line separator string as the source generator understands it.
   */
  private static String getLineSeparator(final String lineSepStyle) {
    String lineSep = System.getProperty("line.separator");
    if (lineSepStyle != null) {
      if (ARG_VALUE_LINE_SEPARATION_WIN.equals(lineSepStyle)) {
        System.out.println("-- " + LINE_SEPARATION_WIN_MSG);
        lineSep = "\r\n";
      } else if (ARG_VALUE_LINE_SEPARATION_UNIX.equals(lineSepStyle)) {
        System.out.println("-- " + LINE_SEPARATION_UNIX_MSG);
        lineSep = "\n";
      } else if (ARG_VALUE_LINE_SEPARATION_MAC.equals(lineSepStyle)) {
        System.out.println("-- " + LINE_SEPARATION_MAC_MSG);
        lineSep = "\r";
      } else {
        System.out.println("-- " + INVALID_LINE_SEPARATOR_MSG + "'" + lineSepStyle + "'");
        System.out.println("-- " + DEFAULT_LINE_SEPARATOR_MSG);
      }
    }
    return lineSep;
  }

  /**
   * Configures our command-line options object with the command line options that we recognize.
   *
   * @return a new CommandLineOptions object fully configured to parse our command line.
   */
  private static CommandLineOptions setupCommandLineOptions() {
    CommandLineOptions allOptions = new CommandLineOptions();
    String desc;

    // -- filename flag
    desc = "Sets the filename for the schema used as input.";
    allOptions.addFlag(ARGUMENT_INPUT, "schema filename", desc);

    // -- filename flag
    desc = "Sets the input source for the schema used as input.";
    allOptions.addFlag(ARGUMENT_INPUT_SOURCE, "input source for XML schema", desc);

    // -- package name flag
    desc = "Sets the package name for generated code.";
    allOptions.addFlag(ARGUMENT_PACKAGE, "package name", desc, true);

    // -- destination directory
    desc = "Sets the destination output directory.";
    allOptions.addFlag(ARGUMENT_DESTINATION_DIR, "destination directory", desc, true);

    // -- resources destination directory
    desc = "Sets the destination output directory for resources.";
    allOptions.addFlag(ARGUMENT_RESOURCES_DESTINATION_DIR, "resources destination directory", desc,
        true);

    // -- line break flag
    desc = "Sets the line separator style for the desired platform.";
    allOptions.addFlag(ARGUMENT_LINE_SEPARATOR, "(unix | mac | win)", desc, true);

    // -- Force flag
    desc = "Suppresses non fatal warnings, such as overwriting files.";
    allOptions.addFlag(ARGUMENT_FORCE, "", desc, true);

    // -- Help flag
    desc = "Displays this help screen.";
    allOptions.addFlag(ARGUMENT_HELP, "", desc, true);

    // -- verbose flag
    desc = "Prints out additional messages when creating source.";
    allOptions.addFlag(ARGUMENT_VERBOSE, "", desc, true);

    // -- fail on first error flag
    desc = "Causes source generation to fail on the first error encountered.";
    allOptions.addFlag(ARGUMENT_FAIL_ON_ERROR, "", desc, true);

    // -- no descriptors flag
    desc = "Disables the generation of the Class descriptors.";
    allOptions.addFlag(ARGUMENT_DISABLE_DESCRIPTORS, "", desc, true);

    // -- mapping file flag
    desc = "Indicates that a mapping file should be generated.";
    allOptions.addFlag(ARGUMENT_GENERATE_MAPPING, "mapping filename", desc, true);

    // -- source generator types name flag
    desc = "Sets the source generator types name (SGTypeFactory).";
    allOptions.addFlag(ARGUMENT_TYPES, "types", desc, true);

    // -- We temporarily maintain backwards compatibility, but this argument is deprecated
    desc = "";
    allOptions.addFlag(ARGUMENT_TYPES_DEPRECATED, "collections class name", desc, true);

    // -- no marshaling framework methods
    desc = "Disables the generation of the methods specific to the XML marshaling framework.";
    allOptions.addFlag(ARGUMENT_NOMARSHALL, "", desc, true);

    // -- implements org.castor.xmlctf.CastorTestable?
    desc = "Implements some specific methods to allow the generated classes"
        + " to be used with Castor Testing Framework.";
    allOptions.addFlag(ARGUMENT_TESTABLE, "", desc, true);

    // -- use SAX1?
    desc = "Uses SAX 1 in the generated code.";
    allOptions.addFlag(ARGUMENT_SAX1, "", desc, true);

    // -- Source Generator Binding
    desc = "Sets the Source Generator Binding File name.";
    allOptions.addFlag(ARGUMENT_BINDING_FILENAME, "filename", desc, true);

    // -- Generates sources for imported XML Schemas
    desc = "Generates sources for imported XML schemas.";
    allOptions.addFlag(ARGUMENT_GENERATE_IMPORTED_SCHEMAS, "", desc, true);

    // -- Sets enumerated type to use a case insensitive lookup
    desc = "Sets enumerated types to use a case insensitive lookup.";
    allOptions.addFlag(ARGUMENT_CASE_INSENSITIVE, "", desc);

    // -- Sets enumerated type to use a case insensitive lookup
    desc = "Sets name conflict strategy to use (possible values are "
        + "'informViaLog', 'warnViaConsoleDialog').";
    allOptions.addFlag(ARGUMENT_NAME_CONFLICT_STRATEGY, "", desc);

    desc = "Selects the JClass printer type (default 'standard')";
    allOptions.addFlag(ARGUMENT_NAME_JCLASSPRINTER, "<mode>", desc, true);

    desc = "Whether to use old Java field naming conventions (default to 'true')";
    allOptions.addFlag(ARGUMENT_USE_OLD_FIELD_NAMING, "", desc, true);

    return allOptions;
  }

}
