package org.apache.torque.templates.transformer.om;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.torque.generator.control.ControllerState;
import org.apache.torque.generator.source.SourceElement;
import org.apache.torque.generator.source.transform.SourceTransformerException;
import org.apache.torque.templates.TemplateOptionName;
import org.apache.torque.templates.TorqueSchemaAttributeName;
import org.apache.torque.templates.TorqueSchemaElementName;

/**
 * Sets the foreign tables for each of the foreign keys,
 * and define variables, getters and setters for the complexObjectModel.
 *
 * So the source elements are (attributes not shown)
 * foreign-key
 *   reference
 *   reference
 *   ...
 *
 * and the result is
 * foreign-key
 *   reference
 *     local-column
 *       column
 *     foreign-column
 *       column
 *   reference
 *     local-column
 *       column
 *     foreign-column
 *       column
 *   ...
 *   local-field (properties for the field on the local table's database object
 *                referencing the foreign database object)
 *   foreign-field (properties for the field on the foreign table's
 *                  database object referencing the local database objects)
 *   table (the foreign referenced table)
 *
 * On running this transformer, the javaName Attribute on the columns
 * must be set properly.
 */
public class OMForeignKeyTransformer
{
    public void transform(
            SourceElement foreignKey,
            ControllerState controllerState)
        throws SourceTransformerException
    {
        if (!TorqueSchemaElementName.FOREIGN_KEY.getName().equals(
                foreignKey.getName()))
        {
            throw new IllegalArgumentException("Illegal element Name "
                    + foreignKey.getName());
        }

        SourceElement localTable = foreignKey.getParent();
        SourceElement database = localTable.getParent();

        String foreignTableName = (String) foreignKey.getAttribute(
                TorqueSchemaAttributeName.FOREIGN_TABLE);
        SourceElement foreignTable
                = FindHelper.findTable(database, foreignTableName, true);
        foreignKey.getChildren().add(foreignTable);

        for (SourceElement reference : foreignKey.getChildren(
                TorqueSchemaElementName.REFERENCE))
        {
            createLocalElementForReference(localTable, reference);
            createForeignElementForReference(foreignTable, reference);
        }

        StringBuilder localParentPath = new StringBuilder();
        getParentPath(localTable, localParentPath);

        StringBuilder foreignParentPath = new StringBuilder();
        getParentPath(foreignTable, foreignParentPath);
        // create reference only if this table is not in a external-schema
        // element of the foreign table's database
        if (foreignParentPath.toString().startsWith(localParentPath.toString()))
        {
            addLocalField(foreignKey, controllerState);
            addLocalFieldInBean(foreignKey, controllerState);
        }

        // create backreference only if the foreign table is not
        // in a external-schema element of this table's database
        if (localParentPath.toString().startsWith(foreignParentPath.toString()))
        {
            addForeignField(foreignKey, controllerState);
            addForeignFieldInBean(foreignKey, controllerState);
        }

        setForeignKeyAttributes(foreignKey, controllerState);
    }

    /**
     * Second pass of the transformation. Performs all steps which require that
     * the first pass is complete.
     *
     * @param foreignKey the element to transform, not null.
     * @param controllerState the controller state, not null.
     *
     * @throws SourceTransformerException if the transformation fails
     */
    public void transformSecondPass(
            SourceElement foreignKey,
            ControllerState controllerState)
        throws SourceTransformerException
    {
        modifyForeignFieldSecondPass(foreignKey, controllerState);
    }

   /**
    * Create a foreign-field element which describes a field
    * in the foreign table referencing the local table.
    * As more than one foreign object can point
    * to this object, the filed needs to be a collection.
    *
     * @param foreignKey the foreign-key element to which the foreign-field
     *        element should be added.
     * @param controllerState the current controller state.
    */
    private void addForeignField(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement localTable = foreignKey.getParent();
        SourceElement foreignTable = foreignKey.getChild(
                TorqueSchemaElementName.TABLE);
        String referencedBySuffix = getForeignReferencedBySuffix(
                foreignKey, controllerState);
        {
            // the field name for the variable used
            String foreignFieldName = (String) controllerState.getOption(
                        TemplateOptionName.OM_FOREIGN_FIELD_NAME_PREFIX)
                    + localTable.getAttribute(
                            TableAttributeName.DB_OBJECT_CLASS_NAME)
                    + controllerState.getOption(
                            TemplateOptionName.OM_FOREIGN_FIELD_NAME_SUFFIX)
                    + referencedBySuffix;
            // the field name to create the name of the getter and setter
            String getterSetterFieldName
                    = (String) localTable.getAttribute(
                            TableAttributeName.DB_OBJECT_CLASS_NAME)
                        + referencedBySuffix;
            SourceElement foreignFieldElement
                    = new SourceElement(
                            ForeignKeyChildElementName.FOREIGN_FIELD);

            foreignFieldElement.setAttribute(
                    JavaFieldAttributeName.FIELD_NAME,
                    foreignFieldName);
            foreignFieldElement.setAttribute(
                    JavaFieldAttributeName.FIELD_ACCESS_MODIFIER,
                    "protected");
            {
                // the field name to cache the used Criteria
                String criteriaFieldName = "last"
                        + StringUtils.capitalize(getterSetterFieldName)
                        + "Criteria";
                foreignFieldElement.setAttribute(
                        ForeignKeyChildAttributeName
                                .FOREIGN_COLUMN_CRITERIA_CACHE_FIELD,
                        criteriaFieldName);
            }

            String fieldContainedType = (String) localTable.getAttribute(
                    TorqueSchemaAttributeName.JAVA_NAME);
            foreignFieldElement.setAttribute(
                    JavaFieldAttributeName.FIELD_CONTAINED_TYPE,
                    fieldContainedType);

            String fieldType = (String) controllerState.getOption(
                    TemplateOptionName.OM_FOREIGN_FIELD_TYPE)
                    + "<" + fieldContainedType + ">";
            foreignFieldElement.setAttribute(
                    JavaFieldAttributeName.FIELD_TYPE,
                    fieldType);

            foreignFieldElement.setAttribute(
                    JavaFieldAttributeName.DEFAULT_VALUE,
                    "null");
            {
                String getterName = FieldHelper.getGetterName(
                            getterSetterFieldName,
                            fieldType,
                            controllerState)
                        + "s";
                foreignFieldElement.setAttribute(
                        JavaFieldAttributeName.GETTER_NAME,
                        getterName);
            }
            {
                String setterName = FieldHelper.getSetterName(
                            getterSetterFieldName)
                        + "s";
                foreignFieldElement.setAttribute(
                        JavaFieldAttributeName.SETTER_NAME,
                            setterName);
            }
            {
                String adderName = FieldHelper.getAdderName(
                        getterSetterFieldName,
                        controllerState);
                foreignFieldElement.setAttribute(
                        JavaFieldAttributeName.ADDER_NAME,
                        adderName);
            }
            {
                String initializerName = FieldHelper.getInitializerName(
                        getterSetterFieldName,
                        controllerState);
                foreignFieldElement.setAttribute(
                        JavaFieldAttributeName.INITIALIZER_NAME,
                        initializerName);
            }
            {
                String isInitializedName = FieldHelper.getIsInitializedName(
                        getterSetterFieldName,
                        controllerState);
                foreignFieldElement.setAttribute(
                        JavaFieldAttributeName.IS_INITIALIZED_NAME,
                        isInitializedName);
            }
            {
                String initType = (String) controllerState.getOption(
                        TemplateOptionName.OM_FOREIGN_FIELD_INIT_TYPE)
                    + "<" + fieldContainedType + ">";
                foreignFieldElement.setAttribute(
                        JavaFieldAttributeName.INITIALIZER_TYPE,
                        initType);

            }
            {
                String fillerName = FieldHelper.getFillerName(
                        getterSetterFieldName,
                        "",
                        controllerState);
                foreignFieldElement.setAttribute(
                        JavaFieldAttributeName.FILLER_NAME,
                        fillerName);
            }
            {
                String setAndSaveMethodName
                    = FieldHelper.getSetAndSaveMethodName(
                        getterSetterFieldName,
                        "",
                        controllerState);
                foreignFieldElement.setAttribute(
                        "setAndSaveMethodName",
                        setAndSaveMethodName);
            }
            {
                // Name for a getter in the foreign table to
                // retrieve entries in the foreign table plus the joined
                // entries in the local table.
                String joinGetterFieldName
                        = StringUtils.capitalize(getterSetterFieldName)
                        + "sJoin"
                        + foreignTable.getAttribute(
                            TorqueSchemaAttributeName.JAVA_NAME);
                String joinGetterName = FieldHelper.getGetterName(
                        joinGetterFieldName,
                        fieldType,
                        controllerState);
                foreignFieldElement.setAttribute(
                        ForeignKeyChildAttributeName
                            .FOREIGN_FIELD_JOIN_GETTER,
                        joinGetterName);
            }
            {
                // Name for the doSelectJoinXXX method in the Peer Class
                // of the foreign table.
                String peerJoinSelectMethodName
                        = "doSelectJoin"
                            + foreignTable.getAttribute(
                                    TorqueSchemaAttributeName.JAVA_NAME)
                            + referencedBySuffix;
                foreignFieldElement.setAttribute(
                        ForeignKeyChildAttributeName
                            .PEER_JOIN_SELECT_METHOD,
                        peerJoinSelectMethodName);
            }
            {
                // Name for the doSelectJoinAllExceptXXX method
                // in the Peer Class of the foreign table.
                String peerJoinAllExceptSelectMethodName
                        = "doSelectJoinAllExcept"
                            + foreignTable.getAttribute(
                                    TorqueSchemaAttributeName.JAVA_NAME)
                            + referencedBySuffix;
                foreignFieldElement.setAttribute(
                        ForeignKeyChildAttributeName
                            .PEER_JOIN_ALL_EXCEPT_SELECT_METHOD,
                        peerJoinAllExceptSelectMethodName);
            }
            foreignKey.getChildren().add(foreignFieldElement);
        }
    }

    /**
     * Adds the the filler attribute to a foreign-field element.
     * This method checks for naming conflicts with the local fields of the
     * referenced table and modifies the filler name accordingly if a conflict
     * is found.
     * The name of all filler attributes of the local-field elements
     * must be set when this method is called.
     *
     * @param foreignKey the foreign-key element which foreign-field
     *        element should be modified.
     * @param controllerState the current controller state.
     */
    private void modifyForeignFieldSecondPass(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement foreignFieldElement
            = foreignKey.getChild(ForeignKeyChildElementName.FOREIGN_FIELD);
        if (foreignFieldElement == null)
        {
            return;
        }
        String setterName = (String) foreignFieldElement.getAttribute(
                JavaFieldAttributeName.SETTER_NAME);
        // setter gets a "s" appended, remove that
        String regularSetterName
                = setterName.substring(0, setterName.length() - 1);
        String fieldName
                = FieldHelper.getFieldNameFromSetterName(regularSetterName);
        String fillerName = FieldHelper.getFillerName(
                fieldName,
                "",
                controllerState);
         // check whether there is a local-field in the referenced table
        // which has the same filler name
        SourceElement referencedTable = foreignKey.getChild(
                TorqueSchemaElementName.TABLE);
        boolean fillerNamingConflictFound = false;
        for (SourceElement referencedTableForeignKey
                : referencedTable.getChildren(
                        TorqueSchemaElementName.FOREIGN_KEY))
        {
            SourceElement referencedTableLocalField
                    = referencedTableForeignKey.getChild(
                            ForeignKeyChildElementName.LOCAL_FIELD);
            if (referencedTableLocalField == null)
            {
                continue;
            }
            String referencedTableFiller
                    = (String) referencedTableLocalField.getAttribute(
                            JavaFieldAttributeName.FILLER_NAME);
            if (fillerName.equals(referencedTableFiller))
            {
                fillerNamingConflictFound = true;
                break;
            }
        }
        if (fillerNamingConflictFound)
        {
            fillerName = FieldHelper.getFillerName(
                    fieldName,
                    (String) controllerState.getOption(
                        TemplateOptionName.OM_FILLER_REFERENCING_DISTICTION),
                    controllerState);
        }
        foreignFieldElement.setAttribute(
                JavaFieldAttributeName.FILLER_NAME,
                fillerName);
    }

    /**
     * Create a foreign-field-in-bean element which describes the referenced
     * instances of the local table bean object in the foreign table bean class.
     * As more than one foreign bean can point to this bean,
     * the field needs to be a collection.
     *
     * @param foreignKey the foreign-key element
     *        to which the foreign-field-in-bean element should be added.
     * @param controllerState the current controller state.
     */
    private void addForeignFieldInBean(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement localTable = foreignKey.getParent();
        String referencedBySuffix = getForeignReferencedBySuffix(
                foreignKey, controllerState);
        // the field name to create the name of the bean getter and setter
        String beanGetterSetterFieldName
                = (String) localTable.getAttribute(
                        TableAttributeName.BEAN_CLASS_NAME)
                    + referencedBySuffix;

        // the field name for the variable used
        String foreignFieldInBeanName
                = (String) controllerState.getOption(
                        TemplateOptionName.OM_FOREIGN_FIELD_NAME_PREFIX)
                    + localTable.getAttribute(
                            TableAttributeName.BEAN_CLASS_NAME)
                    + controllerState.getOption(
                            TemplateOptionName.OM_FOREIGN_FIELD_NAME_SUFFIX)
                    + referencedBySuffix;
        SourceElement foreignFieldInBeanElement
                = new SourceElement(
                    ForeignKeyChildElementName.FOREIGN_FIELD_IN_BEAN);

        foreignFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.FIELD_NAME,
                foreignFieldInBeanName);
        foreignFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.FIELD_ACCESS_MODIFIER,
                "protected");

        String fieldContainedType = (String) localTable.getAttribute(
                TableAttributeName.BEAN_CLASS_NAME);
        foreignFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.FIELD_CONTAINED_TYPE,
                fieldContainedType);

        String fieldType = (String) controllerState.getOption(
                TemplateOptionName.OM_FOREIGN_FIELD_TYPE)
                + "<" + fieldContainedType + ">";
        foreignFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.FIELD_TYPE,
                fieldType);

        {
            String initType = (String) controllerState.getOption(
                    TemplateOptionName.OM_FOREIGN_FIELD_INIT_TYPE)
                    + "<" + fieldContainedType + ">";
            foreignFieldInBeanElement.setAttribute(
                    JavaFieldAttributeName.INITIALIZER_TYPE,
                    initType);
        }

        foreignFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.DEFAULT_VALUE,
                "null");
        {
            String getterName = FieldHelper.getGetterName(
                        beanGetterSetterFieldName,
                        fieldType,
                        controllerState)
                    + "s";
            foreignFieldInBeanElement.setAttribute(
                    JavaFieldAttributeName.GETTER_NAME,
                    getterName);
        }
        {
            String setterName = FieldHelper.getSetterName(
                        beanGetterSetterFieldName)
                    + "s";
            foreignFieldInBeanElement.setAttribute(
                    JavaFieldAttributeName.SETTER_NAME,
                        setterName);
        }
        foreignKey.getChildren().add(foreignFieldInBeanElement);
    }

    /**
     * Create a local-field-in-bean element which describes the referenced
     * instance of the foreign table bean object in the local table bean class.
     * This field will store one instance of the foreign table's bean object.
     *
     * @param foreignKey the foreign-key element
     *        to which the local-field-in-bean element should be added.
     * @param controllerState the current controller state.
     */
    private void addLocalFieldInBean(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement foreignTable = foreignKey.getChild(
                TorqueSchemaElementName.TABLE);
        String referencedBySuffix = getLocalReferencedBySuffix(
                foreignKey, controllerState);
        String beanGetterSetterFieldName
                = (String) foreignTable.getAttribute(
                        TableAttributeName.BEAN_CLASS_NAME)
                    + referencedBySuffix;
        // the field name for the variable used
        String localBeanFieldName = (String) controllerState.getOption(
                TemplateOptionName.OM_LOCAL_FIELD_NAME_PREFIX)
                + foreignTable.getAttribute(
                        TableAttributeName.BEAN_CLASS_NAME)
                + controllerState.getOption(
                        TemplateOptionName.OM_LOCAL_FIELD_NAME_SUFFIX)
                + referencedBySuffix;
        SourceElement localFieldInBeanElement
                = new SourceElement(
                        ForeignKeyChildElementName.LOCAL_FIELD_IN_BEAN);

        localFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.FIELD_NAME,
                localBeanFieldName.toString());

        String fieldType = (String) foreignTable.getAttribute(
                TableAttributeName.BEAN_CLASS_NAME);
        localFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.FIELD_TYPE,
                fieldType);
        localFieldInBeanElement.setAttribute(
                JavaFieldAttributeName.DEFAULT_VALUE,
                "null");
        {
            String getterName = FieldHelper.getGetterName(
                    beanGetterSetterFieldName,
                    fieldType,
                    controllerState);
            localFieldInBeanElement.setAttribute(
                    JavaFieldAttributeName.GETTER_NAME,
                    getterName);
        }
        {
            String setterName = FieldHelper.getSetterName(
                    beanGetterSetterFieldName);
            localFieldInBeanElement.setAttribute(
                    JavaFieldAttributeName.SETTER_NAME,
                    setterName);
        }
        foreignKey.getChildren().add(localFieldInBeanElement);
    }

    /**
     * Create a local-field element which describes a field in the local table
     * referencing the foreign table.
     * This field will store one instance of the foreign table's java object.
     *
     * @param foreignKey the foreign-key element to which the local-field
     *        element should be added.
     * @param controllerState the current controller state.
     */
    private void addLocalField(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement foreignTable = foreignKey.getChild(
                TorqueSchemaElementName.TABLE);
        String referencedBySuffix = getLocalReferencedBySuffix(
                foreignKey, controllerState);
        // the field name for the variable used
        String localFieldName = (String) controllerState.getOption(
                TemplateOptionName.OM_LOCAL_FIELD_NAME_PREFIX)
                + foreignTable.getAttribute(
                        TableAttributeName.DB_OBJECT_CLASS_NAME)
                + controllerState.getOption(
                        TemplateOptionName.OM_LOCAL_FIELD_NAME_SUFFIX)
                + referencedBySuffix;
        // the field name to create the getter and setter names
        String getterSetterFieldName
                = (String) foreignTable.getAttribute(
                        TableAttributeName.DB_OBJECT_CLASS_NAME)
                    + referencedBySuffix;

        SourceElement localFieldElement
                = new SourceElement(
                        ForeignKeyChildElementName.LOCAL_FIELD);

        localFieldElement.setAttribute(
                JavaFieldAttributeName.FIELD_NAME,
                localFieldName.toString());
        localFieldElement.setAttribute(
                JavaFieldAttributeName.PROPERTY_NAME,
                getterSetterFieldName.toString());

        String fieldType = (String) foreignTable.getAttribute(
                TableAttributeName.DB_OBJECT_CLASS_NAME);
        localFieldElement.setAttribute(
                JavaFieldAttributeName.FIELD_TYPE,
                fieldType);
        localFieldElement.setAttribute(
                JavaFieldAttributeName.DEFAULT_VALUE,
                "null");
        {
            String getterName = FieldHelper.getGetterName(
                    getterSetterFieldName,
                    fieldType,
                    controllerState);
            localFieldElement.setAttribute(
                    JavaFieldAttributeName.GETTER_NAME,
                    getterName);
        }
        {
            String setterName = FieldHelper.getSetterName(
                    getterSetterFieldName);
            localFieldElement.setAttribute(
                    JavaFieldAttributeName.SETTER_NAME,
                    setterName);
        }
        {
            String fillerName = FieldHelper.getFillerName(
                    getterSetterFieldName,
                    "",
                    controllerState);
            localFieldElement.setAttribute(
                    JavaFieldAttributeName.FILLER_NAME,
                    fillerName);
        }
        foreignKey.getChildren().add(localFieldElement);
    }

    /**
     * If a foreign table is referenced more than once from this table,
     * the local field for the foreign key must be qualified by which
     * field(s) the foreign table is referenced. This method calculates
     * this qualifying suffix and returns it.
     *
     * @param foreignKey the foreign key element for which the suffix
     *        is calculated, not null.
     * @param controllerState the current controller state, not null.
     *
     * @return the qualifying suffix, or the empty String if none is needed.
     *         Not null.
     */
    private String getLocalReferencedBySuffix(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement localTable = foreignKey.getParent();
        String localTableName = (String) localTable.getAttribute(
                TorqueSchemaAttributeName.NAME);
        String foreignTableName = (String) foreignKey.getAttribute(
                TorqueSchemaAttributeName.FOREIGN_TABLE);

        StringBuilder result = new StringBuilder();
        List<SourceElement> referencesToSameTable
                = FindHelper.findForeignKeyByReferencedTable(
                        localTable,
                        foreignTableName);
        if (referencesToSameTable.size() > 1
                || foreignKey.getAttribute(
                        TorqueSchemaAttributeName.FOREIGN_TABLE)
                    .equals(localTableName))
        {
            result.append((String) controllerState.getOption(
                    TemplateOptionName.OM_LOCAL_FIELD_NAME_RELATED_BY));
            for (SourceElement reference : foreignKey.getChildren(
                    TorqueSchemaElementName.REFERENCE))
            {
                SourceElement localColumnElement
                        = reference.getChildren(
                                ReferenceChildElementName.LOCAL_COLUMN)
                            .get(0);
                SourceElement localColumn
                        = localColumnElement.getChildren(
                                TorqueSchemaElementName.COLUMN)
                            .get(0);
                String fieldName = (String) localColumn.getAttribute(
                        JavaFieldAttributeName.FIELD_NAME);
                result.append(
                        StringUtils.capitalize(fieldName));
            }
        }
        return result.toString();
    }

    /**
     * If a foreign table is referenced more than once from this table,
     * the foreign field for the foreign key must be qualified by which
     * field(s) the foreign table is referenced. This method calculates
     * this qualifying suffix and returns it.
     *
     * @param foreignKey the foreign key element for which the suffix
     *        is calculated, not null.
     * @param controllerState the current controller state, not null.
     *
     * @return the qualifying suffix, or the empty String if none is needed.
     *         Not null.
     */
    private String getForeignReferencedBySuffix(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement localTable = foreignKey.getParent();
        String foreignTableName = (String) foreignKey.getAttribute(
                TorqueSchemaAttributeName.FOREIGN_TABLE);

        StringBuilder result = new StringBuilder();
        List<SourceElement> referencingSameTable
                = FindHelper.findForeignKeyByReferencedTable(
                        localTable,
                        foreignTableName);
        if (referencingSameTable.size() > 1)
        {
            result.append((String) controllerState.getOption(
                    TemplateOptionName.OM_FOREIGN_FIELD_NAME_RELATED_BY));
            for (SourceElement reference : foreignKey.getChildren(
                    TorqueSchemaElementName.REFERENCE))
            {
                SourceElement localColumnElement
                        = reference.getChildren(
                                ReferenceChildElementName.LOCAL_COLUMN)
                            .get(0);
                SourceElement localColumn
                        = localColumnElement.getChildren(
                                TorqueSchemaElementName.COLUMN)
                            .get(0);
                String fieldName = (String) localColumn.getAttribute(
                        JavaFieldAttributeName.FIELD_NAME);
                result.append(
                        StringUtils.capitalize(fieldName));
            }
        }
        return result.toString();
    }

    /**
     * Creates the child element "foreign-colum" for the reference element
     * and adds the foreign column as a child of it.
     *
     * @param foreignTable the foreign table element.
     * @param reference the refenced element to enrich.
     */
    private void createForeignElementForReference(
            SourceElement foreignTable,
            SourceElement reference)
    {
        {
            String foreignColumnName = (String)
                    reference.getAttribute(
                            TorqueSchemaAttributeName.FOREIGN);
            SourceElement foreignColumnElement
                    = new SourceElement(
                            ReferenceChildElementName.FOREIGN_COLUMN);
            SourceElement column
                    = FindHelper.findColumn(foreignTable, foreignColumnName);
            foreignColumnElement.getChildren().add(column);
            reference.getChildren().add(foreignColumnElement);
        }
    }

    /**
     * Creates the child element "local-column" for the reference element
     * and adds the local column as a child of it.
     *
     * @param localTable the local table element.
     * @param reference the refenced element to enrich.
     */
    protected void createLocalElementForReference(
            SourceElement localTable,
            SourceElement reference)
    {
        {
            String localColumnName = (String)
                    reference.getAttribute(
                            TorqueSchemaAttributeName.LOCAL);
            SourceElement localColumnElement
                    = new SourceElement(
                            ReferenceChildElementName.LOCAL_COLUMN);
            SourceElement column
                    = FindHelper.findColumn(localTable, localColumnName);
            localColumnElement.getChildren().add(column);
            reference.getChildren().add(localColumnElement);
        }
    }

    private void setForeignKeyAttributes(
            SourceElement foreignKey,
            ControllerState controllerState)
    {
        SourceElement foreignTable = foreignKey.getChild(
                TorqueSchemaElementName.TABLE);
        String referencedBySuffix = getLocalReferencedBySuffix(
                foreignKey, controllerState);
        String foreignKeyGetterName = (String) controllerState.getOption(
                TemplateOptionName.OM_FOREIGN_KEY_GETTER_PREFIX)
            + foreignTable.getAttribute(
                    TableAttributeName.DB_OBJECT_CLASS_NAME)
            + controllerState.getOption(
                    TemplateOptionName.OM_FOREIGN_KEY_GETTER_SUFFIX)
            + referencedBySuffix;
        foreignKey.setAttribute(
                ForeignKeyAttributeName.FOREIGN_KEY_GETTER,
                foreignKeyGetterName);

        boolean referencesPrimaryKey = false;
        List<SourceElement> foreignTablePrimaryKeys
                = foreignTable.getChild(TableChildElementName.PRIMARY_KEYS)
                        .getChildren(TorqueSchemaElementName.COLUMN);
        List<SourceElement> foreignTableForeignKeyColumns
            = new ArrayList<SourceElement>();
        for (SourceElement reference
                : foreignKey.getChildren(TorqueSchemaElementName.REFERENCE))
        {
            SourceElement column = reference.getChild(
                    ReferenceChildElementName.FOREIGN_COLUMN)
                        .getChild(TorqueSchemaElementName.COLUMN);
            foreignTableForeignKeyColumns.add(column);
        }
        if (foreignTablePrimaryKeys.size()
                == foreignTableForeignKeyColumns.size())
        {
            referencesPrimaryKey = true;
            for (int i = 0; i < foreignTablePrimaryKeys.size(); ++i)
            {
                if (foreignTablePrimaryKeys.get(i)
                        != foreignTableForeignKeyColumns.get(i))
                {
                    referencesPrimaryKey = false;
                    break;
                }
            }
        }
        foreignKey.setAttribute(
                ForeignKeyAttributeName.REFERENCES_PRIMARY_KEY,
                referencesPrimaryKey);
    }

    private void getParentPath(
            SourceElement sourceElement,
            StringBuilder result)
    {
        SourceElement parent = sourceElement.getParent();
        if (parent == null)
        {
            return;
        }
        result.append(parent.getName());
        if (TorqueSchemaElementName.EXTERNAL_SCHEMA.getName().equals(
                parent.getName()))
        {
            result.append("[")
                .append(parent.getAttribute(TorqueSchemaAttributeName.FILENAME))
                .append("]");
        }
        result.append("/");
        getParentPath(parent, result);
    }
}
