com.nextep.datadesigner.dbgm.gui.ForeignKeyNewEditorGUI.java Source code

Java tutorial

Introduction

Here is the source code for com.nextep.datadesigner.dbgm.gui.ForeignKeyNewEditorGUI.java

Source

/*******************************************************************************
 * Copyright (c) 2011 neXtep Software and contributors.
 * All rights reserved.
 *
 * This file is part of neXtep designer.
 *
 * NeXtep designer is free software: you can redistribute it 
 * and/or modify it under the terms of the GNU General Public 
 * License as published by the Free Software Foundation, either 
 * version 3 of the License, or any later version.
 *
 * NeXtep designer 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     neXtep Softwares - initial API and implementation
 *******************************************************************************/
/**
 *
 */
package com.nextep.datadesigner.dbgm.gui;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import com.nextep.datadesigner.Designer;
import com.nextep.datadesigner.dbgm.impl.ForeignKeyConstraint;
import com.nextep.datadesigner.dbgm.model.ConstraintType;
import com.nextep.datadesigner.dbgm.model.ForeignKeyAction;
import com.nextep.datadesigner.dbgm.model.IBasicColumn;
import com.nextep.datadesigner.dbgm.model.IBasicTable;
import com.nextep.datadesigner.dbgm.model.IDatabaseRawObject;
import com.nextep.datadesigner.dbgm.model.IKeyConstraint;
import com.nextep.datadesigner.exception.UnresolvedItemException;
import com.nextep.datadesigner.gui.impl.ColorFocusListener;
import com.nextep.datadesigner.gui.impl.FontFactory;
import com.nextep.datadesigner.gui.impl.TableDisplayConnector;
import com.nextep.datadesigner.gui.impl.editors.ComboColumnEditor;
import com.nextep.datadesigner.gui.impl.editors.ComboEditor;
import com.nextep.datadesigner.gui.impl.editors.TextEditor;
import com.nextep.datadesigner.gui.model.NextepTableEditor;
import com.nextep.datadesigner.model.ChangeEvent;
import com.nextep.datadesigner.model.IElementType;
import com.nextep.datadesigner.model.IEventListener;
import com.nextep.datadesigner.model.INamedObject;
import com.nextep.datadesigner.model.IObservable;
import com.nextep.datadesigner.vcs.gui.external.VersionedTableEditor;
import com.nextep.datadesigner.vcs.services.NamingService;
import com.nextep.datadesigner.vcs.services.VersionHelper;
import com.nextep.designer.dbgm.ui.DBGMImages;
import com.nextep.designer.dbgm.ui.DBGMUIMessages;
import com.nextep.designer.dbgm.ui.controllers.ForeignKeyUIController;
import com.nextep.designer.helper.DatatypeHelper;
import com.nextep.designer.ui.UIImages;
import com.nextep.designer.ui.factories.ImageFactory;
import com.nextep.designer.ui.factories.UIControllerFactory;
import com.nextep.designer.vcs.ui.VCSUIPlugin;
import com.nextep.designer.vcs.ui.services.ICommonUIService;

/**
 * This class is the graphical representation of a foreign key edition. It handles remote constraint
 * selection (navigating through containers / tables), column mapping and foreign key naming.
 * 
 * @author Christophe Fondacci
 * @author Bruno Gautier
 */
public class ForeignKeyNewEditorGUI extends TableDisplayConnector {

    private static final Log log = LogFactory.getLog(ForeignKeyNewEditorGUI.class);
    private Composite group = null; // @jve:decl-index=0:visual-constraint="10,10"
    private CLabel nameLabel = null;
    private Text nameText = null;
    private CLabel remoteLabel = null;
    private Text remoteTableText = null;
    private Button changeRemoteTableButton = null;
    private CLabel joinToLabel = null;
    private Combo remoteConstraintCombo = null;
    private Label enforcedLabel = null;
    private Text enforcedText = null;
    private Button createCols;
    private Table columnsTable;
    private TableColumn remoteColumn;
    private TableColumn tableColumn;
    private TableColumn warnColumn;
    private NextepTableEditor editor;
    private ComboColumnEditor columnProposals;
    private Combo onUpdateCombo;
    private Combo onDeleteCombo;

    /**
     * Default constructor
     * 
     * @param constraint the foreign key constraint to edit
     * @param controller the foreign key controller
     */
    public ForeignKeyNewEditorGUI(ForeignKeyConstraint constraint, ForeignKeyUIController controller) {
        super(constraint, controller);
    }

    /**
     * This method initializes the remote table combo which will hold the eligible tables for
     * foreign key edition.
     */
    private void createRemoteTableCombo() {
        GridData gridData3 = new GridData();
        gridData3.horizontalAlignment = GridData.FILL;
        gridData3.grabExcessHorizontalSpace = true;
        gridData3.verticalAlignment = GridData.CENTER;
        remoteTableText = new Text(group, SWT.BORDER | SWT.BOLD);
        remoteTableText.setEditable(false);
        remoteTableText.setLayoutData(gridData3);

        changeRemoteTableButton = new Button(group, SWT.PUSH);
        changeRemoteTableButton.setImage(ImageFactory.ICON_EDIT_TINY);
        changeRemoteTableButton.setToolTipText(DBGMUIMessages.getString("toolTipChangeRemoteTable")); //$NON-NLS-1$
        changeRemoteTableButton.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false, 2, 1));
        changeRemoteTableButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                final Object elt = VCSUIPlugin.getService(ICommonUIService.class).findElement(
                        changeRemoteTableButton.getShell(), DBGMUIMessages.getString("changeRemoteTableTitle"), //$NON-NLS-1$
                        IElementType.getInstance(IBasicTable.TYPE_ID));
                if (elt != null) {
                    IBasicTable t = (IBasicTable) elt;
                    remoteTableText.setText(t.getName());
                    refreshConstraintCombo(t);
                    remoteConstraintChanged();
                    refreshConnector();
                }
            }
        });
    }

    /**
     * This method initializes the remote constraint combo which will propose the remote unique key
     * constraints to join this foreign key to.
     */
    private void createRemoteConstraintCombo() {
        GridData gridData4 = new GridData();
        gridData4.horizontalAlignment = GridData.FILL;
        gridData4.grabExcessHorizontalSpace = true;
        gridData4.horizontalSpan = 1;
        gridData4.verticalAlignment = GridData.CENTER;
        remoteConstraintCombo = new Combo(group, SWT.READ_ONLY);
        remoteConstraintCombo.setLayoutData(gridData4);
        Label filler = new Label(group, SWT.NONE);
        filler.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
        // Handling combo edition
        ComboEditor.handle(remoteConstraintCombo, ChangeEvent.REMOTE_CONSTRAINT_CHANGED, this);

    }

    protected Control createSWTControl(Composite parent) {
        ForeignKeyConstraint constraint = (ForeignKeyConstraint) getModel();

        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 4;
        group = new Composite(parent, SWT.NONE);
        group.setLayout(gridLayout);
        createName();
        createRemoteTable();
        createRemoteConstraint();
        createEnforcedBy();
        createOnUpdateOnDeleteCombos();
        createToolbox();
        createRemoteColumnTable();
        // Registering table
        initializeTable(columnsTable, editor);
        try {
            constraint.getRemoteConstraint();
        } catch (UnresolvedItemException e) {
            // If unresolved constraint we say it
            remoteTableText.setText("Unresolved parent constraint");
            remoteConstraintCombo.setText("Unresolved remote constraint");
        }
        // Creating table editor
        editor = VersionedTableEditor.handle(columnsTable, constraint.getConstrainedTable());
        initializeTable(columnsTable, editor);
        columnProposals = ComboColumnEditor.handle(editor, 1, ChangeEvent.COLUMN_CHANGED, this, null);
        refreshColumnsProposals();

        return group;
    }

    /**
     * Creates the On Update and On delete label and combos
     */
    private void createOnUpdateOnDeleteCombos() {
        final Label onUpdateLabel = new Label(group, SWT.RIGHT);
        onUpdateLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        onUpdateLabel.setText(DBGMUIMessages.getString("fk.editor.onUpdate"));

        onUpdateCombo = new Combo(group, SWT.READ_ONLY);
        onUpdateCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

        final Label onDeleteLabel = new Label(group, SWT.RIGHT);
        onDeleteLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        onDeleteLabel.setText(DBGMUIMessages.getString("fk.editor.onDelete"));

        onDeleteCombo = new Combo(group, SWT.READ_ONLY);
        onDeleteCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

        // Filling read-only action values
        for (ForeignKeyAction action : ForeignKeyAction.values()) {
            onUpdateCombo.add(action.getLabel());
            onUpdateCombo.setData(action.getLabel(), action);
            onDeleteCombo.add(action.getLabel());
            onDeleteCombo.setData(action.getLabel(), action);
        }
        // Listening to selection changes
        ComboEditor.handle(onUpdateCombo, ChangeEvent.CUSTOM_1, this);
        ComboEditor.handle(onDeleteCombo, ChangeEvent.CUSTOM_2, this);
    }

    /**
     * Refreshes the combo box containing column name proposals that will be used to define column
     * mappings.
     */
    private void refreshColumnsProposals() {
        ForeignKeyConstraint constraint = (ForeignKeyConstraint) getModel();
        if (columnProposals != null) {
            List<String> proposals = new ArrayList<String>();
            for (IBasicColumn c : constraint.getConstrainedTable().getColumns()) {
                proposals.add(c.getName());
            }
            columnProposals.setProposals(proposals);
        }
    }

    /**
     * Creates the name edition controls
     */
    private void createName() {
        GridData gridData1 = new GridData();
        gridData1.horizontalAlignment = GridData.FILL;
        gridData1.verticalAlignment = GridData.CENTER;
        GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalSpan = 3;
        gridData.verticalAlignment = GridData.CENTER;
        nameLabel = new CLabel(group, SWT.RIGHT);
        nameLabel.setText(DBGMUIMessages.getString("fk.editor.name"));
        nameLabel.setLayoutData(gridData1);
        nameText = new Text(group, SWT.BORDER);
        nameText.setLayoutData(gridData);
        nameText.setTextLimit(60);
        ColorFocusListener.handle(nameText);
        TextEditor.handle(nameText, ChangeEvent.NAME_CHANGED, this);
    }

    /**
     * Creates the remote table selection controls
     */
    private void createRemoteTable() {
        GridData gridData2 = new GridData();
        gridData2.horizontalAlignment = GridData.FILL;
        gridData2.verticalAlignment = GridData.CENTER;
        remoteLabel = new CLabel(group, SWT.RIGHT);
        remoteLabel.setText(DBGMUIMessages.getString("fk.editor.remoteTable"));
        remoteLabel.setLayoutData(gridData2);
        createRemoteTableCombo();
    }

    /**
     * Creates the remote constraint selection controls
     */
    private void createRemoteConstraint() {
        GridData gridData11 = new GridData();
        gridData11.horizontalAlignment = GridData.FILL;
        gridData11.verticalAlignment = GridData.CENTER;
        joinToLabel = new CLabel(group, SWT.RIGHT);
        joinToLabel.setText(DBGMUIMessages.getString("fk.editor.joinedKey"));
        joinToLabel.setLayoutData(gridData11);
        createRemoteConstraintCombo();
    }

    /**
     * Creates the "Enforced by index" label and text display controls
     */
    private void createEnforcedBy() {
        GridData gridData11 = new GridData();
        gridData11.horizontalAlignment = GridData.FILL;
        gridData11.verticalAlignment = GridData.CENTER;
        enforcedLabel = new Label(group, SWT.RIGHT);
        enforcedLabel.setText(DBGMUIMessages.getString("fk.editor.enforcingIndex"));
        enforcedLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        GridData gridData4 = new GridData();
        gridData4.horizontalAlignment = GridData.FILL;
        gridData4.grabExcessHorizontalSpace = true;
        gridData4.horizontalSpan = 3;
        gridData4.verticalAlignment = GridData.CENTER;
        enforcedText = new Text(group, SWT.BORDER);
        enforcedText.setFont(FontFactory.FONT_BOLD);
        enforcedText.setEditable(false);
        enforcedText.setLayoutData(gridData4);
    }

    private void createToolbox() {
        final Composite toolbox = new Composite(group, SWT.NONE);
        addNoMarginLayout(toolbox, 1);
        toolbox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 4, 1));
        createCols = new Button(toolbox, SWT.PUSH);
        createCols.setText(DBGMUIMessages.getString("fk.editor.createUnmappedCols"));
        createCols.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                cloneUnmappedColumns();
            }
        });
    }

    /**
     * Creates the table controls used to define mappings between remote key table columns and
     * current table columns
     */
    private void createRemoteColumnTable() {
        GridData gridData21 = new GridData();
        gridData21.horizontalSpan = 4;
        gridData21.verticalAlignment = GridData.FILL;
        gridData21.grabExcessHorizontalSpace = true;
        gridData21.grabExcessVerticalSpace = true;
        gridData21.horizontalAlignment = GridData.FILL;
        columnsTable = new Table(group, SWT.FULL_SELECTION | SWT.BORDER);
        remoteColumn = new TableColumn(columnsTable, SWT.NONE);
        remoteColumn.setWidth(150);
        remoteColumn.setText(DBGMUIMessages.getString("fk.editor.remoteTableColumns"));
        tableColumn = new TableColumn(columnsTable, SWT.NONE);
        tableColumn.setWidth(150);
        tableColumn.setText(DBGMUIMessages.getString("fk.editor.tableColumns"));
        columnsTable.setLayoutData(gridData21);
        columnsTable.setHeaderVisible(true);
        columnsTable.setLinesVisible(true);
        warnColumn = new TableColumn(columnsTable, SWT.CENTER);
        warnColumn.setText(DBGMUIMessages.getString("fk.editor.warnings"));
        warnColumn.setWidth(200);
    }

    public Control getSWTConnector() {
        return group;
    }

    public void refreshConnector() {
        final ForeignKeyConstraint constraint = (ForeignKeyConstraint) getModel();
        if (constraint != null) {
            nameText.setText(notNull(constraint.getName()));
        }
        // If a remote constraint is defined
        boolean unmappedCols = false;
        IKeyConstraint currentRemoteConstraint = null;
        try {
            currentRemoteConstraint = constraint.getRemoteConstraint();
        } catch (UnresolvedItemException e) {
            log.error(e.getMessage(), e);
        }
        if (currentRemoteConstraint != null) {
            /*
             * If the remote constraint is defined but not the foreign key columns, we try to find
             * matching columns.
             */
            if (constraint.getColumns().size() == 0) {
                adjustForeignKeyColumns(true);
            }
            // Then we synchronize our 2 columns collection (remote and local) to add correspondent
            // items
            final Iterator<IBasicColumn> remoteIt = currentRemoteConstraint.getColumns().iterator();
            final Iterator<IBasicColumn> fkIt = constraint.getColumns().iterator();
            // Pre-cleanup
            this.clean(columnsTable);
            // Looping on remote constraint columns
            while (remoteIt.hasNext()) {
                final IBasicColumn remoteColumn = remoteIt.next();
                final TableItem i = this.getOrCreateItem(remoteColumn);
                // Remote (left) section setup
                i.setText(remoteColumn.getName());
                i.setImage(DatatypeHelper.getDatatypeIcon(remoteColumn.getDatatype(), true));
                // FK (right) mapped section setup (we may have no mapping)
                if (fkIt.hasNext()) {
                    IBasicColumn tableColumn = fkIt.next();
                    if (isMapped(constraint, tableColumn)) {
                        // If we have a table mapping, we set the mapped column information
                        Designer.getListenerService().registerListener(i, tableColumn, new IEventListener() {

                            @Override
                            public void handleEvent(ChangeEvent event, IObservable source, Object data) {
                                refreshConnector();
                            }
                        });
                        i.setText(1, tableColumn.getName());
                        i.setImage(1, DatatypeHelper.getDatatypeIcon(tableColumn.getDatatype(), true));
                        // Checking datatype "perfect" match for warning
                        final String remoteColType = remoteColumn.getDatatype().toString();
                        final String fkColType = tableColumn.getDatatype().toString();
                        if (!remoteColType.equals(fkColType)) {
                            i.setText(2,
                                    MessageFormat.format(
                                            DBGMUIMessages.getString("fk.editor.warnings.inconsistenDatatypes"),
                                            remoteColType, fkColType));
                            i.setImage(2, UIImages.ICON_MARKER_WARNING);
                        } else {
                            i.setText(2, "");
                            i.setImage(2, null);
                        }
                    } else {
                        // Incorrect mapping, we display an error
                        displayMappingError(i);
                        unmappedCols = true;
                    }
                } else {
                    // Not enough FK columns to fulfill the mapping with the remote key, we display
                    // an error
                    displayMappingError(i);
                    unmappedCols = true;
                }
            }
        }
        // Refreshing enforcing index
        Collection<IDatabaseRawObject> enforcerIndexes = constraint.getEnforcingIndex();
        enforcedText.setText(enforcerIndexes.isEmpty() ? DBGMUIMessages.getString("fk.editor.noEnforcingIndex") //$NON-NLS-1$
                : getCommaSeparatedNames(enforcerIndexes));
        if (currentRemoteConstraint != null) {
            final IBasicTable t = currentRemoteConstraint.getConstrainedTable();
            remoteTableText.setText(t.getName());
            refreshConstraintCombo(t);
        }
        // Refreshing on update / delete combos
        onUpdateCombo.setText(constraint.getOnUpdateAction().getLabel());
        onDeleteCombo.setText(constraint.getOnDeleteAction().getLabel());

        // Checking parent table locking status
        if (constraint.getConstrainedTable() != null) {
            boolean enabled = !constraint.getConstrainedTable().updatesLocked();
            changeRemoteTableButton.setEnabled(enabled);
            remoteConstraintCombo.setEnabled(enabled);
            onUpdateCombo.setEnabled(enabled);
            onDeleteCombo.setEnabled(enabled);
            nameText.setEnabled(enabled);
            createCols.setEnabled(enabled && unmappedCols);
        }
        // Refreshing columns proposals
        refreshColumnsProposals();
    }

    /**
     * Converts the collection of {@link INamedObject} elements into a comma seperated String with
     * the names of all elements, preserving the input ordering.
     * 
     * @param elements a {@link Collection} of {@link INamedObject}
     * @return a comma seperated String with elements names
     */
    private String getCommaSeparatedNames(Collection<? extends INamedObject> elements) {
        if (elements == null) {
            return "";
        }
        StringBuilder buf = new StringBuilder();
        String seperator = "";
        for (INamedObject namedObj : elements) {
            buf.append(seperator + namedObj.getName());
            seperator = ", ";
        }
        return buf.toString();
    }

    /**
     * Displays a mapping error on the foreign key section of the columns mapping table for the
     * specified item.
     * 
     * @param item {@link TableItem} on which the error should be displayed
     */
    private void displayMappingError(TableItem item) {
        item.setText(1, DBGMUIMessages.getString("fk.editor.unmappedColumn"));
        item.setImage(1, ImageFactory.ICON_ERROR_TINY);
    }

    @Override
    protected Object getTableVersionedParent(Object model) {
        return ((ForeignKeyConstraint) getModel()).getConstrainedTable();
    }

    public void handleEvent(ChangeEvent event, IObservable source, Object data) {
        ForeignKeyConstraint constraint = (ForeignKeyConstraint) getModel();
        // Each of the following catched event messages will trigger model
        // updates. Therefore we first ensure that we can update it.
        if (!VersionHelper.ensureModifiable(constraint.getConstrainedTable(), false)) {
            return;
        }
        switch (event) {
        case NAME_CHANGED:
            constraint.setName((String) data);
            break;
        case REMOTE_CONSTRAINT_CHANGED:
            remoteConstraintChanged();
            break;
        case CUSTOM_1:
            constraint.setOnUpdateAction((ForeignKeyAction) onUpdateCombo.getData((String) data));
            break;
        case CUSTOM_2:
            constraint.setOnDeleteAction((ForeignKeyAction) onDeleteCombo.getData((String) data));
            break;
        case COLUMN_CHANGED:
            // With this call we ensure we work on an equal set of columns between remote and fk
            // columns
            adjustForeignKeyColumns(false);
            // Which column are we working on (= line index of the table) ?
            final int columnIndex = columnsTable.getSelectionIndex();
            // Retrieving selected table column
            final IBasicColumn newCol = getConstrainedColumn(constraint.getConstrainedTable(), (String) data);
            // Removing listener on previous column
            if (newCol != null) {
                // Bug #332: Previous column mapping on this column index may not be defined
                if (constraint.getColumns().size() > columnIndex) {
                    final IBasicColumn previousCol = constraint.getColumns().get(columnIndex);
                    if (previousCol != null) {
                        Designer.getListenerService().unregisterListener(previousCol, this);
                        constraint.removeColumn(previousCol);
                    }
                }
                constraint.addConstrainedColumn(columnIndex, newCol);
            }
            break;
        }
        refreshConnector();

    }

    /**
     * Refreshes the remote constraint selection combo box. The combo will be re-initialized with
     * the unique constraints of the specified parent table.<br>
     * If the remote constraint currently associated to the edited foreign key is a constraint of
     * the specified table, it will be automatically selected. Otherwise the primary key will be
     * automatically selected if defined.
     * 
     * @param t
     */
    private void refreshConstraintCombo(IBasicTable t) {
        final ForeignKeyConstraint fk = (ForeignKeyConstraint) getModel();
        remoteConstraintCombo.removeAll();
        IKeyConstraint currentRemoteConstraint = null;
        try {
            currentRemoteConstraint = fk.getRemoteConstraint();
        } catch (UnresolvedItemException e) {
            log.error(e.getMessage(), e);
        }
        if (t != null) {
            int i = 0;
            for (IKeyConstraint c : t.getConstraints()) {
                switch (c.getConstraintType()) {
                case PRIMARY:
                case UNIQUE:
                    remoteConstraintCombo.add(c.getName());
                    remoteConstraintCombo.setData(String.valueOf(i), c);
                    if (currentRemoteConstraint != null && c.equals(currentRemoteConstraint)
                            || c.getConstraintType() == ConstraintType.PRIMARY) {
                        remoteConstraintCombo.select(i);
                    }
                    i++;
                }
            }
        }
    }

    /**
     * Updates the current foreign key model to setup the constraint defined by the user
     * primary/unique constraints combo.<br>
     * <b>WARNING:</b> This method may alter the foreign key definition and must only be called by
     * user actions.
     */
    private void remoteConstraintChanged() {
        final ForeignKeyConstraint fk = (ForeignKeyConstraint) getModel();
        IKeyConstraint currentRemoteConstraint = null;
        try {
            currentRemoteConstraint = fk.getRemoteConstraint();
        } catch (UnresolvedItemException e) {
            log.error(e.getMessage(), e);
        }
        int s = remoteConstraintCombo.getSelectionIndex();
        // Retrieving selected constraint
        IKeyConstraint userRemoteConstraint = (IKeyConstraint) remoteConstraintCombo.getData(String.valueOf(s));
        if (userRemoteConstraint != currentRemoteConstraint) {
            fk.setRemoteConstraint(userRemoteConstraint);
            // Adjusting our foreign key name
            NamingService.getInstance().adjustName(fk);
            // Constraint changed so we reset our columns mappings
            // First we remove the previous constrained columns
            removeSubsequentForeignKeyColumns(fk.getColumns().iterator());
            // Then we try to find a new mapping for the new remote constraint
            adjustForeignKeyColumns(true);
            // And we cleanup our columns mapping table before refreshing
            clean(columnsTable);
            refreshConnector();
        }
    }

    /**
     * Retrieves the constrained column from its name string.
     * 
     * @param constraint constraint which reference the named column
     * @param columnName string value of the column name
     * @return the {@link IBasicColumn} instance
     */
    private IBasicColumn getConstrainedColumn(IBasicTable table, String columnName) {
        for (IBasicColumn c : table.getColumns()) {
            if (c.getName().equals(columnName)) {
                return c;
            }
        }
        return null;
    }

    /**
     * Clones the given column.
     * 
     * @param c constraint containing the column to clone
     * @param columnName name of the cloned column
     * @param reference reference column to clone
     * @return a clone of the reference column with the specified column name
     */
    private IBasicColumn cloneColumn(IKeyConstraint c, String columnName, IBasicColumn reference) {
        IBasicTable parentTable = c.getConstrainedTable();
        IBasicColumn clone = getConstrainedColumn(parentTable, columnName);
        if (clone == null) {
            clone = (IBasicColumn) UIControllerFactory.getController(IElementType.getInstance("COLUMN"))
                    .newInstance(parentTable);
            clone.setName(columnName);
            clone.setDatatype(reference.getDatatype());
        }
        return clone;
    }

    private void cloneUnmappedColumns() {
        final ForeignKeyConstraint fk = (ForeignKeyConstraint) getModel();
        final boolean confirmed = MessageDialog.openQuestion(group.getShell(),
                MessageFormat.format(DBGMUIMessages.getString("fk.editor.dialog.confirmColumnCreationTitle"),
                        fk.getConstrainedTable().getName()),
                MessageFormat.format(DBGMUIMessages.getString("fk.editor.dialog.confirmColumnCreation"),
                        fk.getConstrainedTable().getName()));
        if (confirmed) {
            final IKeyConstraint remote = fk.getRemoteConstraint();
            final Iterator<IBasicColumn> remoteColIt = remote.getColumns().iterator();
            final Iterator<IBasicColumn> fkColIt = new ArrayList<IBasicColumn>(fk.getColumns()).iterator();
            while (remoteColIt.hasNext()) {
                final IBasicColumn remoteCol = remoteColIt.next();
                IBasicColumn mappedCol = null;
                if (fkColIt.hasNext()) {
                    final IBasicColumn fkCol = fkColIt.next();
                    if (!isMapped(fk, fkCol)) {
                        mappedCol = cloneColumn(fk, remoteCol.getName(), remoteCol);
                        int colIndex = fk.getColumns().indexOf(fkCol);
                        fk.removeColumn(mappedCol);
                        fk.addConstrainedColumn(colIndex, mappedCol);
                    }
                } else {
                    mappedCol = cloneColumn(fk, remoteCol.getName(), remoteCol);
                    fk.addColumn(mappedCol);
                }
            }
            refreshConnector();
        }
    }

    /**
     * Refreshing current columns mappings from remote constraint to current table columns.
     * 
     * @param guessMappedColumn should this method try to "guess" the foreign key column which
     *        matches remote column
     */
    private void adjustForeignKeyColumns(boolean guessMappedColumn) {
        final ForeignKeyConstraint constraint = (ForeignKeyConstraint) getModel();
        final IKeyConstraint currentRemoteConstraint = constraint.getRemoteConstraint();

        if (currentRemoteConstraint != null) {
            final Iterator<IBasicColumn> remoteColIt = currentRemoteConstraint.getColumns().iterator();
            final Iterator<IBasicColumn> fkColIt = new ArrayList<IBasicColumn>(constraint.getColumns()).iterator();
            // Iterating over 2 collection synchronously to :
            // - remove any overflowing fk column
            // - guess any remote constraint column we could match
            while (remoteColIt.hasNext()) {
                final IBasicColumn remoteCol = remoteColIt.next();
                if (fkColIt.hasNext()) {
                    fkColIt.next();
                } else {
                    IBasicColumn guessedCol = null;
                    // Guessing any corresponding column by its name if needed
                    if (guessMappedColumn) {
                        guessedCol = getConstrainedColumn(constraint.getConstrainedTable(), remoteCol.getName());
                    }
                    if (guessedCol == null) {
                        // Forcing inconsistency by setting current col to remote col
                        guessedCol = remoteCol;
                    }
                    constraint.addColumn(guessedCol);
                }
            }
            // Removing any overflowing fk column
            removeSubsequentForeignKeyColumns(fkColIt);
        }
    }

    /**
     * Convenience method to remove from the list of constrained columns of the current foreign key
     * constraint all columns indicated by the specified <code>Iterator</code>.
     * 
     * @param fkColsIt an {@link Iterator} pointing to the {@link IBasicColumn} to remove from the
     *        list of the constrained columns of the current foreign key
     */
    private void removeSubsequentForeignKeyColumns(Iterator<IBasicColumn> fkColsIt) {
        final ForeignKeyConstraint constraint = (ForeignKeyConstraint) getModel();

        while (fkColsIt.hasNext()) {
            IBasicColumn fkCol = fkColsIt.next();
            constraint.removeColumn(fkCol);
        }
    }

    /**
     * Indicates whether the specified table column of a foreign key should be considered as an
     * appropriate column mapping.
     * 
     * @param foreignKey foreign key on which consistency should be verified
     * @param tableColumn table column to check (a column of the foreign key)
     * @return <code>true</code> if the column is a valid mapping, else <code>false</code>
     */
    private boolean isMapped(ForeignKeyConstraint foreignKey, IBasicColumn tableColumn) {
        return tableColumn != null && tableColumn.getParent() != null
                && tableColumn.getParent().equals(foreignKey.getConstrainedTable());
    }

    @Override
    public Image getConnectorIcon() {
        return DBGMImages.WIZARD_KEY;
    }
}