/**
 * 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: DescriptorJClass.java 6772 2007-01-25 13:01:10Z wguttmn $
 */
package org.exolab.castor.builder.descriptors;

import org.exolab.castor.builder.BuilderConfiguration;
import org.exolab.castor.builder.SGTypes;
import org.exolab.castor.xml.XMLConstants;
import org.exolab.javasource.JAnnotation;
import org.exolab.javasource.JAnnotationType;
import org.exolab.javasource.JClass;
import org.exolab.javasource.JConstructor;
import org.exolab.javasource.JField;
import org.exolab.javasource.JMethod;
import org.exolab.javasource.JPrimitiveType;
import org.exolab.javasource.JSourceCode;
import org.exolab.javasource.JType;

/**
 * A class which defines the necessary methods for generating ClassDescriptor
 * source files.
 *
 * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
 * @version $Revision: 6772 $ $Date: 2006-03-10 15:42:54 -0700 (Fri, 10 Mar 2006) $
 */
public final class DescriptorJClass extends JClass {

    /** Class Descriptors extend this base class. */
    private static final String XMLCLASS_DESCRIPTOR_IMPL =
        "org.exolab.castor.xml.util.XMLClassDescriptorImpl";

    private static final String MAPPING_ACCESS_MODE = "org.exolab.castor.mapping.AccessMode";

    /** Field descriptors implement this interface from org.exolab.castor.mapping. */
    private static final JClass FIELD_DESCRIPTOR_CLASS;

    /** Field descriptors implement this interface from org.exolab.castor.xml. */
    private static final JClass XML_FIELD_DESCRIPTOR_CLASS;
    /** Type validators implement this interface from org.exolab.castor.xml. */
    private static final JType  TYPE_VALIDATOR_CLASS;

    static {
        FIELD_DESCRIPTOR_CLASS     = new JClass("org.exolab.castor.mapping.FieldDescriptor");
        XML_FIELD_DESCRIPTOR_CLASS = new JClass("org.exolab.castor.xml.XMLFieldDescriptor");
        TYPE_VALIDATOR_CLASS       = new JClass("org.exolab.castor.xml.TypeValidator");
    }

    /** The type being described by the Descriptor class we'll generate. */
    private final JClass               _type;
    /** Source Builder configuration. */
    private final BuilderConfiguration _config;

    /**
     * Constructs a DescriptorJClass.
     * @param config Builder Configuration
     * @param className name of this descriptor class
     * @param type the type that is described by this descriptor
     */
    public DescriptorJClass(final BuilderConfiguration config, final String className,
                            final JClass type) {
        super(className);
        this._config = config;
        this._type   = type;
        init();
    } //-- DescriptorJClass

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

    /**
     * Initializes this DescriptorJClass with the required methods.
     */
    private void init() {
        // Make sure that the Descriptor is extended XMLClassDescriptor even when
        // the user has specified a super class for all the generated classes
        String superClass = null;
        if (_config != null) {
            superClass = _config.getProperty(BuilderConfiguration.Property.SUPER_CLASS, null);
        }

        boolean extended = false;

        if (_type.getSuperClassQualifiedName() == null
            || _type.getSuperClassQualifiedName().equals(superClass)) {
            setSuperClass(XMLCLASS_DESCRIPTOR_IMPL);
        } else {
            if (_type.getSuperClass() == null) {
                setSuperClass(null);
            } else {
                extended = true;
                setSuperClass(getSuperClassName());
            }
        }
        superClass = null;

        if (_type.getPackageName() != null && _type.getPackageName().length() > 0) {
            addImport(_type.getName());
        }

        addField(new JField(JType.BOOLEAN, "_elementDefinition"));

        addField(new JField(SGTypes.STRING, "_nsPrefix"));
        addField(new JField(SGTypes.STRING, "_nsURI"));
        addField(new JField(SGTypes.STRING, "_xmlName"));
        //-- if there is a super class, the identity field must remain
        //-- the same than the one in the super class
        addField(new JField(XML_FIELD_DESCRIPTOR_CLASS, "_identity"));

        //-- create default constructor
        addDefaultConstructor(extended);

        //jsc.add("Class[] emptyClassArgs = new Class[0];");
        //jsc.add("Class[] classArgs = new Class[1];");

        //---------------------------------------------/
        //- Methods Defined by XMLClassDescriptorImpl -/
        //---------------------------------------------/

        addXMLClassDescriptorImplOverrides();

        //-----------------------------------------/
        //- Methods Defined by XMLClassDescriptor -/
        //-----------------------------------------/

        addXMLClassDescriptorOverrides();

        //--------------------------------------/
        //- Methods defined by ClassDescriptor -/
        //--------------------------------------/

        addClassDescriptorOverrides(extended);
    } //-- createSource

    /**
     * Returns the qualified class name of the super class, adjusted to add the
     * 'descriptors' sub-package.
     * @return Returns the qualified class name of the super class
     */
    private String getSuperClassName() {
        final String superClassName;
        if (_type.getSuperClass().getPackageName() == null
                || _type.getSuperClass().getPackageName().equals("")) {
            if (getPackageName() == null) {
                // no target package specified --> do not append package (=null)
                superClassName = _type.getSuperClass().getLocalName()
                        + XMLConstants.DESCRIPTOR_SUFFIX;
            } else {
                // target package specified --> simply use it
                superClassName = getPackageName() + "." + _type.getSuperClass().getLocalName()
                        + XMLConstants.DESCRIPTOR_SUFFIX;
            }
        } else {
            superClassName = _type.getSuperClass().getPackageName()
                    + "." + XMLConstants.DESCRIPTOR_PACKAGE + "."
                    + _type.getSuperClass().getLocalName() +  XMLConstants.DESCRIPTOR_SUFFIX;
        }
        return superClassName;
    }

    /**
     * Adds our default constructor.
     * @param extended true if we extend another class and thus need to call super()
     */
    private void addDefaultConstructor(final boolean extended) {
        addConstructor(createConstructor());
        JConstructor cons = getConstructor(0);
        JSourceCode jsc = cons.getSourceCode();
        jsc.add("super();");

        if (extended) {
            //-- add base class (for validation)
            jsc.add("setExtendsWithoutFlatten(");
            jsc.append("new ");
            jsc.append(getSuperClassQualifiedName());
            jsc.append("());");
        }
    }

    /**
     * Adds the methods we override from.
     * {@link org.exolab.castor.xml.util.XMLClassDescriptorImpl}
     */
    private void addXMLClassDescriptorImplOverrides() {
        //-- create isElementDefinition method
        JMethod getElementDefinition = new JMethod("isElementDefinition", JType.BOOLEAN,
                               "true if XML schema definition of this Class is that of a global\n"
                               + "element or element with anonymous type definition.");
        JSourceCode jsc = getElementDefinition.getSourceCode();
        jsc.add("return _elementDefinition;");
        addMethod(getElementDefinition);
    }

    /**
     * Adds the methods we override from.
     * {@link org.exolab.castor.xml.XMLClassDescriptor}
     */
    private void addXMLClassDescriptorOverrides() {
        JMethod method;
        JSourceCode jsc;
        //-- create getNameSpacePrefix method
        method = new JMethod("getNameSpacePrefix", SGTypes.STRING,
                             "the namespace prefix to use when marshaling as XML.");

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

        jsc = method.getSourceCode();
        jsc.add("return _nsPrefix;");
        addMethod(method);

        //-- create getNameSpaceURI method
        method = new JMethod("getNameSpaceURI", SGTypes.STRING,
                             "the namespace URI used when marshaling and unmarshaling as XML.");

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

        jsc = method.getSourceCode();
        jsc.add("return _nsURI;");
        addMethod(method);

        //-- create getValidator method
        method = new JMethod("getValidator", TYPE_VALIDATOR_CLASS,
                             "a specific validator for the class described"
                             + " by this ClassDescriptor.");

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

        jsc = method.getSourceCode();
        jsc.add("return this;");
        addMethod(method);

        //-- create getXMLName method
        method = new JMethod("getXMLName", SGTypes.STRING,
                             "the XML Name for the Class being described.");

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

        jsc = method.getSourceCode();
        jsc.add("return _xmlName;");
        addMethod(method);
    }

    /**
     * Adds the methods we override from.
     * {@link org.exolab.castor.mapping.ClassDescriptor}
     * @param extended true if we extend another class and thus need to call super()
     */
    private void addClassDescriptorOverrides(final boolean extended) {
        JSourceCode jsc;

        //-- create getAccessMode method
        JClass amClass = new JClass(MAPPING_ACCESS_MODE);
        JMethod getAccessMode = new JMethod("getAccessMode", amClass,
                                     "the access mode specified for this class.");

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

        jsc = getAccessMode.getSourceCode();
        jsc.add("return null;");
        addMethod(getAccessMode);

        //-- create getIdentity method
        JMethod getIdentity = new JMethod("getIdentity", FIELD_DESCRIPTOR_CLASS,
                                   "the identity field, null if this class has no identity.");

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

        jsc = getIdentity.getSourceCode();
        if (extended) {
            jsc.add("if (_identity == null) {");
            jsc.indent();
            jsc.add("return super.getIdentity();");
            jsc.unindent();
            jsc.add("}");
        }
        jsc.add("return _identity;");

        //--don't add the type to the import list
        addMethod(getIdentity, false);

        //-- create getJavaClass method
        JMethod getJavaClass = new JMethod("getJavaClass", SGTypes.CLASS,
                                    "the Java class represented by this descriptor.");

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

        jsc = getJavaClass.getSourceCode();
        jsc.add("return ");
        jsc.append(classType(_type));
        jsc.append(";");

        //--don't add the type to the import list
        addMethod(getJavaClass, false);
    }

    /**
     * Returns the Class type (as a String) for the given XSType.
     *
     * @param jType
     *            the JType we are to return the class name of
     * @return the Class name (as a String) for the given XSType
     */
    private static String classType(final JType jType) {
        if (jType.isPrimitive()) {
            JPrimitiveType primitive = (JPrimitiveType) jType;
            return primitive.getWrapperName() + ".TYPE";
        }
        return jType.toString() + ".class";
    } //-- classType

} //-- DescriptorJClass
