/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.deployment.ejb;

import java.util.HashMap;
import java.util.Iterator;

import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.ejb.xml.EjbRelation;
import org.ow2.jonas.deployment.ejb.xml.EjbRelationshipRole;
import org.ow2.jonas.deployment.ejb.xml.JonasEjbRelation;
import org.ow2.jonas.deployment.ejb.xml.JonasEjbRelationshipRole;

import org.objectweb.util.monolog.api.Logger;

/**
 * Class to hold meta-information related to an ejb-relation.
 * @author Christophe Ney [cney@batisseurs.com] : Initial developer
 * @author Helene Joanin on May 2003: code cleanup
 * @author Helene Joanin on May 2003: complement for legacy first version
 * @author Ph Durieux (may 2004): default names for relations and roles.
 */

public class EjbRelationDesc {

    private Logger logger = null;
    private String name;        // name of the relation
    private String name1;        // name of the relation role 1
    private String name2;        // name of the relation role 2
    private EjbRelationshipRoleDesc relationshipRoleDesc1;
    private EjbRelationshipRoleDesc relationshipRoleDesc2;
    private String jdbcTableName = null;


    // Zeus objects for the associated mapping information (needed in fillMappingInfo);
    private EjbRelationshipRole role1;
    private EjbRelationshipRole role2;
    JonasEjbRelation jRel = null;
    JonasEjbRelationshipRole jRsRole1 = null;
    JonasEjbRelationshipRole jRsRole2 = null;


    /**
     * constructor to be used by parent node.
     * @param er The described object EjbRelation
     * @param logger The logger
     * @throws DeploymentDescException thrown in error case.
     */
    public EjbRelationDesc(EjbRelation er, Logger logger) throws DeploymentDescException {

        this.logger = logger;

        role1 = er.getEjbRelationshipRole();
        role2 = er.getEjbRelationshipRole2();

        String cmr1 = "";
        if (role1.getCmrField() != null) {
            cmr1 = role1.getCmrField().getCmrFieldName();
        }
        String cmr2 = "";
        if (role2.getCmrField() != null) {
            cmr2 = role2.getCmrField().getCmrFieldName();
        }

        // first role. A name is mandatory.
        // If not set: choose the cmr name of the opposite role, if it exists.
        name1 = role1.getEjbRelationshipRoleName();
        if (name1 == null || name1.length() == 0) {
            if (role2.getCmrField() != null) {
                name1 = cmr2;
            } else {
                name1 = "role1"; // a default value
            }
            // We have changed the name, keep the new one.
            role1.setEjbRelationshipRoleName(name1);
        }

        // second role. A name is mandatory.
        // If not set: choose the cmr name of the opposite role, if it exists.
        name2 = role2.getEjbRelationshipRoleName();
        if (name2 == null || name2.length() == 0) {
            if (role1.getCmrField() != null) {
                name2 = cmr1;
            } else {
                name2 = "role2"; // a default value
            }
            // We have changed the name, keep the new one.
            role2.setEjbRelationshipRoleName(name2);
        }

        // the two roles must have different names
        if (name1.equals(name2)) {
            throw new DeploymentDescException("Relation " + name + " have 2 roles with same name: " + name1);
        }

        // name of the relation. If not set, choose a combination of the 2 cmr names.
        String ern = er.getEjbRelationName();
        if (ern == null || ern.length() == 0) {
            name = cmr2 + "-" + cmr1;
        } else {
            name = ern;
        }
    }

    /**
     * Finish initialisation
     * @param jer The described object JonasEjbRelation. This param may be null.
     * @throws DeploymentDescException in error case
     */
    public void setJonasInfo(JonasEjbRelation jer) throws DeploymentDescException {
        // search the associated JonasEjbRelationshipRole of EjbRelationshipRole.
        // They may not exist.
        jRel = jer;
        HashMap table = new HashMap();
        if (jRel != null) {
            for (Iterator i = jRel.getJonasEjbRelationshipRoleList().iterator(); i.hasNext();) {
                JonasEjbRelationshipRole jersr = (JonasEjbRelationshipRole) i.next();
                String rname = jersr.getEjbRelationshipRoleName();
                if (!rname.equals(name1) && !rname.equals(name2)) {
                    throw new DeploymentDescException("Invalid relationship-role-name \"" + rname + "\" for relation \"" + name + "\" in jonas-ejb-jar.xml");
                }
                table.put(rname, jersr);
            }
        }
        jRsRole1 = (JonasEjbRelationshipRole) table.get(name1);
        jRsRole2 = (JonasEjbRelationshipRole) table.get(name2);

        relationshipRoleDesc1 = new EjbRelationshipRoleDesc(this, name1, role1, jRsRole1, role2, true, logger);
        relationshipRoleDesc2 = new EjbRelationshipRoleDesc(this, name2, role2, jRsRole2, role1, false, logger);

        // Add the opposite CMR field for the relation XXu
        // in order to implement the coherence.
        boolean r1hf = relationshipRoleDesc1.hasCmrField();
        boolean r2hf = relationshipRoleDesc2.hasCmrField();
        EjbRelationshipRoleDesc nocmr = null;
        EjbRelationshipRoleDesc cmr = null;
        if (r1hf && !r2hf) {
            nocmr = relationshipRoleDesc2;
            cmr = relationshipRoleDesc1;
        } else if (!r1hf && r2hf) {
            nocmr = relationshipRoleDesc1;
            cmr = relationshipRoleDesc2;
        }
        if (nocmr != null) {
            // The relation is OXu, the role 'role' does not have cmr field
            String cmrName = name;
            // calculate a cmr field name with the relation name. The bad
            // character are replaced by the character '_'.
            for (int i = 0; i < cmrName.length(); i++) {
                char c = cmrName.charAt(i);
                if (!Character.isJavaIdentifierPart(c)) {
                    cmrName = cmrName.replace(c, '_');
                }
            }
            cmrName = "jonasCMR" + cmrName;
            // Add the cmr, no type is specified because the added cmr field
            // is mono valued.
            nocmr.setCmrFieldName(cmrName);
            nocmr.setIsJOnASCmrField();
            if (nocmr.isTargetMultiple()) {
                if (cmr.isTargetMultiple()) {
                    nocmr.setCmrFieldType(cmr.cmrFieldType.getName());
                } else {
                    nocmr.setCmrFieldType("java.util.Collection");
                }
            }
        }
    }

    /**
     * Fills the mapping information of this relation with the values defined in jonas DD.
     * @throws DeploymentDescException thrown in error case.
     */
    protected void fillMappingInfo() throws DeploymentDescException {
        if (jRel != null) {
            if (jRel.getJdbcTableName() != null) {
                if (jRel.getJdbcTableName().length() != 0) {
                    jdbcTableName = jRel.getJdbcTableName();
                }
            }
            relationshipRoleDesc1.fillMappingInfo();
            relationshipRoleDesc2.fillMappingInfo();
        }
    }

    /**
     * Fills the mapping information of this relation with default values,
     * if the mapping information is not already initialized.
     */
    protected void fillMappingInfoWithDefault() {
        if (!hasJdbcTable()) {
            if (getRelationshipRole1().isTargetMultiple()
                && getRelationshipRole2().isTargetMultiple()) {
                // Many-Many: join table needed for the relation
                jdbcTableName = getRelationshipRole1().getSourceBean().getAbstractSchemaName().toUpperCase()
                    + "_" + getRelationshipRole2().getSourceBean().getAbstractSchemaName().toUpperCase();
            }
        }
        if (!getRelationshipRole1().isSourceMultiple()
            && !getRelationshipRole2().isSourceMultiple()) {
            // One-One
            if (!getRelationshipRole1().hasJdbcMapping()
                && !getRelationshipRole2().hasJdbcMapping()) {
                if (!getRelationshipRole1().isJOnASCmrField()
                    && !getRelationshipRole2().isJOnASCmrField()) {
                    // One <-> One: foreign keys in the source bean of the first RsRole defined
                    getRelationshipRole1().fillMappingInfoWithDefault();
                } else {
                    if (!getRelationshipRole1().isJOnASCmrField()) {
                        // One -> One: foreign keys in the source bean of RsRole1
                        getRelationshipRole1().fillMappingInfoWithDefault();
                    } else {
                        // One <- One: foreign keys in the target bean of RsRole1
                        getRelationshipRole2().fillMappingInfoWithDefault();
                    }
                }
            }
        } else if (getRelationshipRole1().isSourceMultiple()
                   && getRelationshipRole2().isSourceMultiple()) {
            // Many-Many
            getRelationshipRole1().fillMappingInfoWithDefault();
            getRelationshipRole2().fillMappingInfoWithDefault();
        } else {
            // One-Many or Many-One
            if (getRelationshipRole1().isSourceMultiple()) {
                // Many-One
                getRelationshipRole1().fillMappingInfoWithDefault();
            } else {
                // One-Many
                getRelationshipRole2().fillMappingInfoWithDefault();
            }
        }
    }

    /**
     * get the name of the relationship.
     * @return the String name of the relationship.
     */
    public String getName() {
        return name;
    }

    /**
     * get the meta-information for the first relation-ship-role
     * @return the EjbRelationshipRoleDesc for the first relation-ship-role
     */
    public EjbRelationshipRoleDesc getRelationshipRole1() {
        return relationshipRoleDesc1;
    }

    /**
     * get the meta-information for the second relation-ship-role
     * @return the EjbRelationshipRoleDesc for the second relation-ship-role
     */
    public EjbRelationshipRoleDesc getRelationshipRole2() {
        return relationshipRoleDesc2;
    }

    /**
     * Is a table in the database is defined for this relation ?
     * @return true if table name in the database is defined for this relation.
     */
    public boolean hasJdbcTable() {
        return (jdbcTableName != null);
    }

    /**
     * Return the table name in the database associated to this relation.
     * @return the String name of the table associated to this relation.
     */
    public String getJdbcTableName() {
        return jdbcTableName;
    }

    /**
     * String representation of the object for test purpose
     * @return String representation of this object
     */
    public String toString() {
        StringBuffer ret = new StringBuffer();
        ret.append("\ngetName()=" + getName());
        if (hasJdbcTable()) {
            ret.append("\ngetJdbcTableName() = " + getJdbcTableName());
        }
        ret.append("\ngetRelationshipRole1() = " + getRelationshipRole1());
        ret.append("\ngetRelationshipRole2() = " + getRelationshipRole2());
        return ret.toString();
    }
}
