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

//--Castor imports
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.AbstractProperties;
import org.castor.core.util.Messages;
import org.castor.xml.JavaNaming;
import org.castor.xml.JavaNamingImpl;
import org.castor.xml.XMLProperties;

/**
 * The configuration for the SourceGenerator.
 * HACK this class is a configuration but does not base on Castor
 * Configuration! Raised CASTOR-2195 to solve this issue.
 *
 * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
 * @version $Revision: 8600 $ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 */
public class BuilderConfiguration {
    /**
     * The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
     * Commons Logging</a> instance used for all logging.
     */
    private static final Log LOG = LogFactory.getLog(BuilderConfiguration.class);

    /** String representation of directory self pointer. */
    private static final String SELF_DIRECTORY = "./";
    
    /** Length of string representation of directory self pointer. */
    private static final int SELF_DIRECTORY_LENGTH = SELF_DIRECTORY.length();

    /** String representation of directory parent pointer. */
    private static final String PARENT_DIRECTORY = "../";

    /** Length of string representation of directory parent pointer. */
    private static final int PARENT_DIRECTORY_LENGTH = PARENT_DIRECTORY.length();

    /**
     * Names of properties used in the configuration file.
     */
    public static class Property {
        /**
         * Property specifying whether or not to generate source code for bound
         * properties. Currently all properties will be treated as bound
         * properties if this flag is set to true. A value of 'true' enables
         * bound properties.
         *
         * <pre>
         * org.exolab.castor.builder.boundproperties
         * </pre>
         */
        public static final String BOUND_PROPERTIES =
            "org.exolab.castor.builder.boundproperties";

        /**
         * Property specifying whether to implement EnumeratedTypeAccess
         * interface for all generated enumerated type classes.
         *
         * <pre>
         * org.exolab.castor.builder.enumTypeAccessInterface
         * </pre>
         */
        public static final String ENUM_TYPE_ACCESS_INTERFACE =
            "org.exolab.castor.builder.enumTypeAccessInterface";

        /**
         * Property specifying whether or not to generate source code for extra
         * collection methods.
         *
         * <pre>
         * org.exolab.castor.builder.extraCollectionMethods
         * </pre>
         */
        public static final String EXTRA_COLLECTION_METHODS =
            "org.exolab.castor.builder.extraCollectionMethods";

        /**
         * Property specifying the super class for all generated classes.
         *
         * <pre>
         * org.exolab.castor.builder.superclass
         * </pre>
         */
        public static final String SUPER_CLASS =
            "org.exolab.castor.builder.superclass";

        /**
         * Property specifying how element's and type's are mapped into a Java
         * class hierarchy by the Source Generator. The value must contain one
         * of the following. 'element' outputs a Java class hierarchy based on
         * element names used in the XML Schema. This is the default. 'type'
         * outputs a Java class hierarchy based on the type information defined
         * in the XML Schema.
         *
         * <pre>
         * org.exolab.castor.builder.javaclassmapping
         * </pre>
         */
        public static final String JAVA_CLASS_MAPPING =
            "org.exolab.castor.builder.javaclassmapping";

        /**
         * Property listing mapping between XML namespaces and Java packages.
         */
        public static final String NAMESPACE_PACKAGES_OLD =
            "org.exolab.castor.builder.nspackages";

        /**
         * Property listing mapping between XML namespaces and Java packages.
         */
        public static final String NAMESPACE_PACKAGES =
            "org.exolab.castor.xml.nspackages";

        /**
         * Property specifying if we want to have the equals method generated
         * for each generated class.
         */
        public static final String EQUALS_METHOD =
            "org.exolab.castor.builder.equalsmethod";

        /**
         * Property specifying if we want to use Wrapper Objects instead of
         * primitives (eg java.lang.Float instead of float).
         */
        public static final String WRAPPER =
            "org.exolab.castor.builder.primitivetowrapper";

        /**
         * Property specifying if we want to have a 'public static final String'
         * generated for each attribute and element name used within a class
         * descriptor.
         */
        public static final String CLASS_DESC_FIELD_NAMES =
            "org.exolab.castor.builder.classdescfieldnames";

        /**
         * Property specifying whether the Java sources generated should be 1.4
         * or 5.0 compliant.
         */
        public static final String JAVA_VERSION =
            "org.exolab.castor.builder.javaVersion";
        
        /**
         * Forces the code generator to create 'old' Java 1.4 enumeration classes instead 
         * of Java 5 enums for &lt;simpleType&gt; enumerations, even in Java 5 mode.
         */
        public static final String FORCE_JAVA4_ENUMS =
            "org.exolab.castor.builder.forceJava4Enums";

        /**
         * The name of the configuration file.
         *
         * <pre>
         * castor.properties
         * </pre>
         */
        public static final String CONFIG_FILENAME_PROPERTY =
            "castorbuilder.properties";

        /**
         * Maximum number of constant definitions within one file. This property
         * is used to allow the user to configure the maximum number of
         * constant definitions (within a Java class as generated as a result of
         * an enumeration); default is 1000. Is this number if exceeded, no constants
         * will be generated anymore.
         *
         * <pre>
         * org.exolab.castor.builder.maxNumberOfConstants
         * </pre>
         */
        public static final String MAX_CONSTANTS_PROPERTY =
            "org.exolab.castor.builder.maxNumberOfConstants";

        /**
         * Resource to load from the JAR file to load our defaults.
         */
        static final String RESOURCE_NAME =
            "/org/exolab/castor/builder/castorbuilder.properties";

        /**
         * Registered class name conflict resolution strategies.
         *
         * <pre>
         * org.exolab.castor.builder.nameConflictStrategies
         * </pre>
         */
        public static final String NAME_CONFLICT_STRATEGIES =
            "org.exolab.castor.builder.nameConflictStrategies";

        /**
         * Property specifying whether automatic class name conflict resolution
         * should be used or not; defaults to false.
         *
         * <pre>
         * org.exolab.castor.builder.automaticConflictResolution
         * </pre>
         */
        public static final String AUTOMATIC_CONFLICT_RESOLUTION = 
            "org.exolab.castor.builder.automaticConflictResolution";

        /**
         * Property specifying the 'string' used in type strategy to be inserted 
         * between the actual element name and the type name (during automatic class name 
         * conflict resolution); defaults to 'By'.
         *
         * <pre>
         * org.exolab.castor.builder.automaticConflictResolutionTypeSuffix
         * </pre>
         */
        public static final String AUTOMATIC_CONFLICT_RESOLUTION_TYPE_SUFFIX = 
            "org.exolab.castor.builder.automaticConflictResolutionTypeSuffix";
        
        /**
         * Property enlisting the supported {@link JClassPrinterFactory} instances available 
         * for creating {@link JClassPrinter} instances.
         */
        public static final String JCLASSPRINTER_FACTORIES = 
            "org.exolab.castor.builder.jclassPrinterFactories";

        /**
         * Property specifying whether extra members/methods for extracting XML schema
         * documentation should be made available; defaults to false.
         * <pre>
         * org.exolab.castor.builder.extraDocumentationMethods=false
         * </pre>
         */
        public static final String EXTRA_DOCUMENTATION_METHODS = 
            "org.exolab.castor.builder.extraDocumentationMethods";

        /**
         * Property specifying whether cycle breaker code should be added to
         * generated methods 'equals' and 'hashcode'; defaults to <i>true</i>.
         * 
         * <pre>
         * org.exolab.castor.builder.useCycleBreaker = true
         * </pre>
         */
        public static final String USE_CYCLE_BREAKER = "org.exolab.castor.builder.useCycleBreaker";
        
    } //--Property

    /**
     * String value of false.
     */
    private static final String FALSE = "false";

    /**
     * String value of true.
     */
    private static final String TRUE = "true";

    /**
     * String value of element binding property.
     */
    private static final String ELEMENT_VALUE = "element";

    /**
     * String value of type binding property.
     */
    private static final String TYPE_VALUE = "type";

    /**
     * The default properties loaded from the configuration file.
     */
    private Properties _defaultProps = null;

    /**
     * Our properties after all configuration has been loaded.
     */
    private Properties _localProps = null;

    /**
     * Namespace URL to Java package mapping.
     */
    private Hashtable<String, String> _nspackages = new Hashtable<String, String>();

    /**
     * schemaLocation to Java package mapping.
     */
    private Hashtable<String, String> _locpackages = new Hashtable<String, String>();

    /** JavaNaming to be used. */
    private JavaNaming _javaNaming;
   
    /**
     * hooks for (external) tools to add custom
     * annotations to fields, classes and enumConstants 
     * during source generation.
     */
    private List<AnnotationBuilder> _annotationBuilders = new ArrayList<AnnotationBuilder>();

    //------------------/

    /**
     * Creates a default BuilderConfiguration.
     */
    public BuilderConfiguration() {
        super();
        getDefault();
        _localProps = new Properties(_defaultProps);
    } //-- BuilderConfiguration

    /**
     * Returns the default configuration file. Changes to the returned
     * properties set will affect all Castor functions relying on the default
     * configuration.
     *
     * @return The default configuration
     */
    public final synchronized Properties getDefault() {
        if (_defaultProps == null) {
            load();
        }
        return _defaultProps;
    } //-- getDefault

    /**
     * Returns a property from the default configuration file. Equivalent to
     * calling <tt>getProperty</tt> on the result of {@link #getDefault}.
     *
     * @param name
     *            The property name
     * @param defValue
     *            The property's default value
     * @return The property's value
     */
    public final String getProperty(final String name, final String defValue) {
        return _localProps.getProperty(name, defValue);
    } //-- getProperty

    /**
     * Returns true if bound properties are enabled.
     * <p>
     * Enabling bound properties is controlled via the
     * org.exolab.castor.builder.boundproperties item in the
     * castorbuilder.properties file. The value is either 'true' or 'false'.
     *
     * @return true if bound properties are enabled.
     */
    public final boolean boundPropertiesEnabled() {
        return TRUE.equalsIgnoreCase(_localProps.getProperty(Property.BOUND_PROPERTIES));
    } //-- boundPropertiesEnabled

    /**
     * Returns true if we generate an 'equals' method for each generated class.
     * <p>
     * Enabling this property is controlled via the
     * org.exolab.castor.builder.equalsmethod item in the
     * castorbuilder.properties file. The value is either 'true' or 'false'.
     *
     * @return true if bound properties are enabled.
     */
    public final boolean equalsMethod() {
        return TRUE.equalsIgnoreCase(_localProps.getProperty(Property.EQUALS_METHOD));
    } //-- equalsMethod

    /**
     * Sets the 'equalsmethod' property.
     *
     * @param equals The value we want to use.
     */
     public final void setEqualsMethod(final boolean equals) {
        String value = (equals) ? TRUE : FALSE;
        _localProps.setProperty(Property.EQUALS_METHOD, value);
     } //-- setEqualsMethod

    /**
     * Returns true if we generate a 'public static final String' for the name
     * of each attribute and element described by the class descriptor
     * <p>
     * Enabling this property is controlled via the
     * org.exolab.castor.builder.classdescfieldnames item in the
     * castorbuilder.properties file. The value is either 'true' or 'false'.
     *
     * @return true if bound properties are enabled.
     */
    public final boolean classDescFieldNames() {
        return _localProps.getProperty(Property.CLASS_DESC_FIELD_NAMES, "").equalsIgnoreCase(TRUE);
    } //-- classDescFieldNames

    /**
     * Returns true if extra methods for collection fields should be generated.
     * Such methods include set/get methods for the actual collection in
     * addition to the array methods.
     * <p>
     * Enabling extra collection methods is controlled via the
     * org.exolab.castor.builder.extraCollectionMethods property in the
     * castorbuilder.properties file. The value is either 'true' or 'false'.
     *
     * @return true if extra collection methods are enabled.
     */
    public final boolean generateExtraCollectionMethods() {
        return _localProps.getProperty(Property.EXTRA_COLLECTION_METHODS, "")
                .equalsIgnoreCase(TRUE);
    } //-- generateExtraCollectionMethods

    /**
     * Sets the 'classDescFieldNames' property.
     *
     * @param classDescFieldNames
     *            the value we want to ues
     */
     public final void setClassDescFieldNames(final boolean classDescFieldNames) {
        String value = (classDescFieldNames) ? TRUE : FALSE;
        _localProps.setProperty(Property.CLASS_DESC_FIELD_NAMES, value);
     } //-- setClassDescFieldNames

     /**
      * Returns true if primitive types have to be used as Objects (eg.
      * replacing <code>float</code> by <code>java.lang.Float</code>).
      * @return true if primitive types have to be used as Objects.
      */
    public final boolean usePrimitiveWrapper() {
        return _localProps.getProperty(Property.WRAPPER, "").equalsIgnoreCase(TRUE);
    } //-- usePrimitiveWrapper

    /**
     * Sets the 'primitivetowrapper' property.
     *
     * @param wrapper the value we want to use.
     */
    public final void setPrimitiveWrapper(final boolean wrapper) {
        String value = (wrapper) ? TRUE : FALSE;
        _localProps.setProperty(Property.WRAPPER, value);
    } //-- setPrimitiveWrapper

    /**
     * Returns true if we generate the implements EnumeratedTypeAccess interface
     * for enumerated type classes. The value is either 'true' or 'false'
     *
     * @return true if use enumerated type interface is enabled
     */
    public final boolean useEnumeratedTypeInterface() {
        return TRUE.equalsIgnoreCase(_localProps.getProperty(Property.ENUM_TYPE_ACCESS_INTERFACE));
    } //-- useEnumeratedTypeInterface

    /**
     * Returns true if we generate the implements EnumeratedTypeAccess interface
     * for enumerated type classes. The value is either 'true' or 'false'
     *
     * @return true if use enumerated type interface is enabled
     */
    public final boolean useJava50() {
        return "5.0".equalsIgnoreCase(_localProps.getProperty(Property.JAVA_VERSION, "1.4"));
    } //-- useEnumeratedTypeInterface
    
    /**
     * Indicates what kind of enumeration should be created for 
     * &lt;xs:simpleType&gt; enumerations.
     *
     * @return true if Java 5 source code should be generated.
     */
    public final boolean useJava5Enums() {
        return useJava50() && FALSE.equalsIgnoreCase(
                _localProps.getProperty(Property.FORCE_JAVA4_ENUMS, FALSE));
    }

    /**
     * Add support to set java version programmatically.
     */
    public final void forceUseJava50() {
        _localProps.setProperty(Property.JAVA_VERSION, "5.0");
    }
        
    /**
     * Returns true if extra methods for accessing XML schema documentation should
     * be generated; default to 'false'.
     *
     * @return true if if extra methods for accessing XML schema documentation should be generated
     */
    public final boolean generateExtraDocumentationMethods() {
        String extraDocumentationMethods = 
            _localProps.getProperty(Property.EXTRA_DOCUMENTATION_METHODS, "false");
        return new Boolean(extraDocumentationMethods).booleanValue();
    }

    /**
     * Returns true if the class {@link CycleBreaker} should be used during code generation; 
     * defaults to 'true'.
     *
     * @return true if the class {@link CycleBreaker} should be used during code generation
     */
    public final boolean useCycleBreaker() {
        return Boolean.valueOf(_localProps.getProperty(Property.USE_CYCLE_BREAKER, "true"));
    }

    /**
     * Returns the maximum number of static constant definitions that are
     * acceptable within one class file; default is 1000.
     *
     * @return the maximum number of static constant definitions acceptable within
     *    one class file
     */
    public final int getMaximumNumberOfConstants() {
        String property = _localProps.getProperty(Property.MAX_CONSTANTS_PROPERTY, "1000");
        return Integer.valueOf(property).intValue();
    }

    /**
     * Sets the 'enumTypeAccessInterface' property.
     *
     * @param flag the value we want to use
     */
    public final void setUseEnumeratedTypeInterface(final boolean flag) {
        String value = (flag) ? TRUE : FALSE;
        _localProps.setProperty(Property.ENUM_TYPE_ACCESS_INTERFACE, value);
    } //-- setUseEnumeratedTypeInterface

    /**
     * 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 boolean mappingSchemaElement2Java() {
        String value = _localProps.getProperty(Property.JAVA_CLASS_MAPPING, "");
        return ELEMENT_VALUE.equalsIgnoreCase(value);
    } //-- 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 boolean mappingSchemaType2Java() {
        String value = _localProps.getProperty(Property.JAVA_CLASS_MAPPING, "");
        return TYPE_VALUE.equalsIgnoreCase(value);
    } //-- mappingSchemaType2Java

    /**
     * Overrides the current set of properties with the given properties. Once
     * the properties are set, only a copy will be uses, so any changes to the
     * given properties file after the fact will go unnoticed.
     *
     * @param properties
     *            the Properties file
     */
    public final void setDefaultProperties(final Properties properties) {
        Properties defaults = null;
        if (properties == null) {
            defaults = _defaultProps;
        } else {
            defaults = new Properties(_defaultProps);
            Enumeration<?> enumeration = properties.keys();
            while (enumeration.hasMoreElements()) {
                String name = (String) enumeration.nextElement();
                defaults.setProperty(name, properties.getProperty(name));
            }
        }
        _localProps = new Properties(defaults);
        processNamespacePackageMappings(
                _localProps.getProperty(Property.NAMESPACE_PACKAGES_OLD, ""));
        processNamespacePackageMappings(
                _localProps.getProperty(Property.NAMESPACE_PACKAGES, ""));
    } //-- setDefaultProperties

    /**
     * Sets the namespace to package mapping.
     *
     * @param ns the namespace URI to map
     * @param packageName the package name
     */
    public final void setNamespacePackageMapping(final String ns, final String packageName) {
        _nspackages.put(ns, packageName);
    } //-- setNamespcaePackageMapping

    /**
     * Sets the schemaLocation to package mapping.
     *
     * @param schemaLocation the schemaLocation to map
     * @param packageName the package name to map to
     */
    public final void setLocationPackageMapping(
            final String schemaLocation, final String packageName) {
        _locpackages.put(schemaLocation, packageName);
    }
    
    /**
     * Returns a String representing the list of {@link JClassPrinterFactory} instances
     * configured in the Castor XML code generator property file.
     * @return the list of {@link JClassPrinterFactory} instances as configured, as string.
     */
    public final String getJClassPrinterFactories() {
        return _localProps.getProperty(Property.JCLASSPRINTER_FACTORIES);
    }


    /**
     * Called by {@link #getDefault} to load the configuration the first time.
     * Will not complain about inability to load configuration file from one of
     * the default directories, but if it cannot find the JAR's configuration
     * file, will throw a run time exception.
     */
    protected final synchronized void load() {
        if (_defaultProps == null) {
            //-- load defaults from JAR
            _defaultProps = loadProperties(
                    Property.RESOURCE_NAME, Property.CONFIG_FILENAME_PROPERTY);

            //-- load local defaults

            boolean found = false;

            // Get overriding configuration from the classpath, ignore if not found.
            // If found, merge any existing properties.
            try {
                InputStream is = SourceGenerator.class.getResourceAsStream(
                        "/" + Property.CONFIG_FILENAME_PROPERTY);
                if (is != null) {
                    found = true;
                    _defaultProps.load(is);
                    is.close();
                }
            } catch (Exception except) {
                //-- do nothing
            }

            //-- if not found, either it doesn't exist, or "." is not part of the
            //-- class path, try looking at local working directory
            if (!found) {
                try {
                    File file = new File(Property.CONFIG_FILENAME_PROPERTY);
                    if (file.exists() && file.canRead()) {
                        InputStream is = new FileInputStream(file);
                        _defaultProps.load(is);
                        is.close();
                    }
                } catch (Exception except) {
                    //-- do nothing
                }
            }
        }

        AbstractProperties rtconf =  XMLProperties.newInstance();

        // Parse XML namespace and package list from both castor.properties and
        // castorbuilder.properties
        processNamespacePackageMappings(rtconf.getString(Property.NAMESPACE_PACKAGES_OLD, ""));
        processNamespacePackageMappings(rtconf.getString(Property.NAMESPACE_PACKAGES, ""));
        processNamespacePackageMappings(_defaultProps.getProperty(
                Property.NAMESPACE_PACKAGES_OLD, ""));
        processNamespacePackageMappings(_defaultProps.getProperty(
                Property.NAMESPACE_PACKAGES, ""));

        //-- backward compatibility with 0.9.3.9
        String prop = _defaultProps.getProperty(
                JavaNamingImpl.UPPER_CASE_AFTER_UNDERSCORE_PROPERTY, null);
        if (prop != null) {
            /*
             * TODO: joachim: Argh! this shouldn't exist and it shouldn't be here! Setting a
             * static attribute into a class because somewhere later the class is instantiated
             * and use but then the attribute is required... I need to sort out the order how
             * objects are created...
             */
            JavaNamingImpl._upperCaseAfterUnderscore = Boolean.valueOf(prop).booleanValue();
        }
    } //-- load

    /**
     * Gets a Java package to an XML namespace URL.
     * @param nsURL the XML namespace URL to convert into a Java package name
     * @return a Java package name
     */
    public final String lookupPackageByNamespace(final String nsURL) {
        String namespaceURL = (nsURL == null) ? "" : nsURL;

        // Lookup Java package via NS
        String javaPackage = _nspackages.get(namespaceURL);
        if (javaPackage == null) {
            return "";
        }
        return javaPackage;
    } //-- lookupPackageNamespace

    /**
     * Converts a schema location into a Java package.
     *
     * @param schemaLocation
     *            the Schema location to use to look up the Java package
     * @return a Java package name
     */
    public final String lookupPackageByLocation(final String schemaLocation) {
        if (schemaLocation == null) {
            return "";
        }

        // Lookup Java package via schemaLocation
        //--Full path
        String javaPackage = _locpackages.get(schemaLocation);
        if (javaPackage == null) {
            String cleanedSchemaLocation = schemaLocation;
            //--maybe a relative schemaLocation was given
            while (schemaLocation.startsWith(".")) {
                 if (schemaLocation.startsWith(SELF_DIRECTORY)) {
                     cleanedSchemaLocation = schemaLocation.substring(SELF_DIRECTORY_LENGTH);
                 } else if (schemaLocation.startsWith(PARENT_DIRECTORY)) {
                     cleanedSchemaLocation = schemaLocation.substring(PARENT_DIRECTORY_LENGTH);
                 }
            }
            Enumeration<String> keys = _locpackages.keys();
            boolean found = false;
            while (keys.hasMoreElements() && !found) {
                String key = keys.nextElement();
                if (cleanedSchemaLocation.endsWith(key)) {
                    javaPackage = _locpackages.get(key);
                    found = true;
                }
            }
            if (javaPackage == null) {
                javaPackage = "";
            }
        }

        return javaPackage;
    } //-- lookupPackageLocation

    /**
     * processes the given String which contains namespace-to-package mappings.
     *
     * @param mappings the namespace-to-package mappings
     */
    protected final void processNamespacePackageMappings(final String mappings) {
        if (mappings == null) {
            return;
        }

        StringTokenizer tokens = new StringTokenizer(mappings, ",");
        while (tokens.hasMoreTokens()) {
            String token = tokens.nextToken();
            int sepIdx = token.indexOf('=');
            if (sepIdx < 0) {
                continue;
            }

            String ns = token.substring(0, sepIdx).trim();
            String javaPackage = token.substring(sepIdx + 1).trim();
            _nspackages.put(ns, javaPackage);
        }
    } //-- processNamespacePackageMappings

    /**
     * indicates whether automatic class name conflict resolution during 
     * XML code generation should take place or not.
     * @return True if automatic mode should be used.
     */
    public boolean isAutomaticConflictResolution() {
        String automaticConflictResolutionProperty = 
            _localProps.getProperty(Property.AUTOMATIC_CONFLICT_RESOLUTION, "false");
        return "true".equalsIgnoreCase(automaticConflictResolutionProperty);
    }

    /**
     * Returns the type 'suffix' used for the type strategy during automatic class name 
     * conflict resolution during XML code generation; default to "" unless a value is specified.
     * @return The type suffix to be inserted between element name and type name
     */
    public String getAutomaticConflictResolutionTypeSuffix() {
        return _localProps.getProperty(Property.AUTOMATIC_CONFLICT_RESOLUTION_TYPE_SUFFIX, "");
    }

    /**
     * To set the {@link JavaNaming} implementation to be used.
     * @param javaNaming JavaNaming implementation to be used
     * @since 1.1.3
     */
    public void setJavaNaming(final JavaNaming javaNaming) {
        _javaNaming = javaNaming;
    }
    
    /**
     * To get the {@link JavaNaming} implementation to be used.
     * @return JavaNaming implementation to be used
     * @since 1.1.3
     */
    public JavaNaming getJavaNaming() {
        return _javaNaming;
    }
    
    /**
     * adds a custom annotation builder.
     * @param annotationBuilder the builder
     */
    public void addAnnotationBuilder(final AnnotationBuilder annotationBuilder) {
        this._annotationBuilders.add(annotationBuilder);
    }
    
    /**
     * returns all applied annotation builders.
     * @return a array of builders for type convenience
     */
    public AnnotationBuilder[] getAnnotationBuilders() {
        return
            this._annotationBuilders.toArray(
                    new AnnotationBuilder[this._annotationBuilders.size()]);
    }

    /**
     * Load the configuration will not complain about inability to load
     * configuration file from one of the default directories, but if
     * it cannot find the JAR's configuration file, will throw a
     * run time exception.
     * 
     * @param resourceName Name of the source.
     * @param fileName Name of the configuration file.
     * @return A {@link Properties} instance holding the actual XML code generator configuration.
     */
    public static Properties loadProperties(final String resourceName, final String fileName) {
        File        file;
        Properties properties = new Properties();

        boolean found = false;
        
        // Get detault configuration from the Castor JAR.
        // Complain if not found.
        InputStream resourceStream = null;
        try {
            resourceStream = AbstractProperties.class.getResourceAsStream(resourceName); 
            properties.load(resourceStream);
            
            //-- debug information:
            //URL url = Configuration.class.getResource( resourceName );
            //System.out.println("loading configuration: " + url.toExternalForm());
            //-- end debug information
            
            found = true;
        } catch (Exception except) {
            // Do nothing as we will check classpath 
            // and java lib directory below
        } finally {
            if (resourceStream != null) {
                try {
                    resourceStream.close();
                } catch (IOException e) {
                    LOG.warn("Problem closing stream for " + resourceName);
                }
            }
        }

        // Get overriding configuration from the Java
        // library directory, ignore if not found. If
        // found merge existing properties.
        String javaHome = null;
        try {
            javaHome = System.getProperty("java.home");
        } catch (SecurityException e) {
            // Not a critical error, but users will need to know if they need to 
            // change their config.   
            LOG.warn(Messages.format("conf.privilegesError", e));
        } catch (Exception e) {
            // As we will be trying something else later, record the error for setup purposes,
            // but do not actually treat as a critical failure.
            LOG.warn(Messages.format("conf.nonCriticalError", e));
        }
        
        if (javaHome != null) {
            InputStream fileStream = null;
            try {      
                file = new File(javaHome, "lib");
                file = new File(file, fileName);
                if (file.exists()) {
                    properties = new Properties(properties);
                    fileStream = new FileInputStream(file);
                    properties.load(fileStream);
                    found = true;
                }      
            } catch (SecurityException e) {
                // Not a critical error, but users will need to know if they need to change 
                // their config.   
                LOG.warn(Messages.format("conf.privilegesError", e));
            } catch (IOException e) {
                // Report that we were unable to load the resource.
                LOG.warn(Messages.format("conf.nonCriticalError", e));
            } finally {
                if (fileStream != null) {
                    try {
                        fileStream.close();
                    } catch (IOException e) {
                        LOG.warn("Problem closing stream for " + fileName);
                    }
                }
            }
        }
        

        //-- Cannot find any castor.properties file(s).
        if (!found) {
            throw new RuntimeException(Messages.format("conf.noDefaultConfigurationFile",
                    fileName));
        }

        return properties;
    }
}
