blob: 837d1ff40255b22a7a7678776e54729ae85ad153 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2009 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.ui.internal.mappings.db;
import java.util.Iterator;
import org.eclipse.jpt.core.JpaDataSource;
import org.eclipse.jpt.core.JpaNode;
import org.eclipse.jpt.core.JpaProject;
import org.eclipse.jpt.db.Catalog;
import org.eclipse.jpt.db.Column;
import org.eclipse.jpt.db.ConnectionListener;
import org.eclipse.jpt.db.ConnectionProfile;
import org.eclipse.jpt.db.Database;
import org.eclipse.jpt.db.ForeignKey;
import org.eclipse.jpt.db.Schema;
import org.eclipse.jpt.db.Sequence;
import org.eclipse.jpt.db.Table;
import org.eclipse.jpt.ui.WidgetFactory;
import org.eclipse.jpt.ui.internal.Tracing;
import org.eclipse.jpt.ui.internal.listeners.SWTConnectionListenerWrapper;
import org.eclipse.jpt.ui.internal.listeners.SWTPropertyChangeListenerWrapper;
import org.eclipse.jpt.ui.internal.mappings.JptUiMappingsMessages;
import org.eclipse.jpt.ui.internal.util.SWTUtil;
import org.eclipse.jpt.ui.internal.widgets.Pane;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.model.event.PropertyChangeEvent;
import org.eclipse.jpt.utility.model.listener.PropertyChangeListener;
import org.eclipse.jpt.utility.model.value.PropertyValueModel;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.widgets.Composite;
/**
* This abstract pane keeps a combo in sync with the database objects
* when a connection is active.
*
* @see CatalogCombo
* @see ColumnCombo
* @see SchemaCombo
* @see SequenceCombo
* @see TableCombo
*/
@SuppressWarnings("nls")
public abstract class DatabaseObjectCombo<T extends JpaNode>
extends Pane<T>
{
/**
* The main (only) widget of this pane.
*/
private CCombo comboBox;
/**
* The listener added to the <code>ConnectionProfile</code>.
* It keeps the combo in sync with the database metadata.
*/
private ConnectionListener connectionListener;
private PropertyChangeListener connectionProfileListener;
// ********** constructors **********
protected DatabaseObjectCombo(
Pane<? extends T> parentPane,
Composite parent
) {
super(parentPane, parent);
}
protected DatabaseObjectCombo(
Pane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent
) {
super(parentPane, subjectHolder, parent);
}
protected DatabaseObjectCombo(
PropertyValueModel<? extends T> subjectHolder,
Composite parent,
WidgetFactory widgetFactory
) {
super(subjectHolder, parent, widgetFactory);
}
// ********** initialization **********
@Override
protected void initialize() {
super.initialize();
this.connectionListener = this.buildConnectionListener();
this.connectionProfileListener = this.buildConnectionProfileListener();
}
protected ConnectionListener buildConnectionListener() {
return new SWTConnectionListenerWrapper(this.buildConnectionListener_());
}
protected ConnectionListener buildConnectionListener_() {
return new LocalConnectionListener();
}
protected PropertyChangeListener buildConnectionProfileListener() {
return new SWTPropertyChangeListenerWrapper(this.buildConnectionProfileListener_());
}
protected PropertyChangeListener buildConnectionProfileListener_() {
return new PropertyChangeListener(){
public void propertyChanged(PropertyChangeEvent event) {
connectionProfileChanged(event);
}
};
}
protected void connectionProfileChanged(PropertyChangeEvent event) {
if (event.getOldValue() != null) {
((ConnectionProfile) event.getOldValue()).removeConnectionListener(this.connectionListener);
}
if (event.getNewValue() != null) {
((ConnectionProfile) event.getNewValue()).addConnectionListener(this.connectionListener);
}
this.repopulateComboBox();
}
@Override
protected void initializeLayout(Composite container) {
this.comboBox = this.addEditableCCombo(container);
this.comboBox.addModifyListener(this.buildModifyListener());
SWTUtil.attachDefaultValueHandler(this.comboBox);
}
protected ModifyListener buildModifyListener() {
return new ModifyListener() {
public void modifyText(ModifyEvent e) {
DatabaseObjectCombo.this.comboBoxModified();
}
};
}
// ********** abstract methods **********
/**
* Return the possible values to be added to the combo during
* population.
*/
protected abstract Iterator<String> values();
/**
* Return the default value, or <code>null</code> if no default is
* specified. This method is only called when the subject is non-null.
*/
protected abstract String getDefaultValue();
/**
* Return the current value from the subject.
* This method is only called when the subject is non-null.
*/
protected abstract String getValue();
/**
* Set the specified value as the new value on the subject.
*/
protected abstract void setValue(String value);
// ********** overrides **********
@Override
protected void engageListeners_(T subject) {
super.engageListeners_(subject);
subject.getJpaProject().getDataSource().addPropertyChangeListener(JpaDataSource.CONNECTION_PROFILE_PROPERTY, this.connectionProfileListener);
ConnectionProfile cp = subject.getJpaProject().getConnectionProfile();
if (cp != null) {
cp.addConnectionListener(this.connectionListener);
}
}
@Override
protected void disengageListeners_(T subject) {
ConnectionProfile cp = subject.getJpaProject().getConnectionProfile();
if (cp != null) {
cp.removeConnectionListener(this.connectionListener);
}
subject.getJpaProject().getDataSource().removePropertyChangeListener(JpaDataSource.CONNECTION_PROFILE_PROPERTY, this.connectionProfileListener);
super.disengageListeners_(subject);
}
@Override
public void enableWidgets(boolean enabled) {
super.enableWidgets(enabled);
if ( ! this.comboBox.isDisposed()) {
this.comboBox.setEnabled(enabled);
}
}
@Override
protected void propertyChanged(String propertyName) {
super.propertyChanged(propertyName);
this.updateSelectedItem();
}
@Override
protected void doPopulate() {
super.doPopulate();
this.populateComboBox();
}
// ********** populating **********
/**
* Populate the combo-box list by clearing it, then adding first the default
* value, if available, and then the possible choices.
*/
protected void populateComboBox() {
this.comboBox.removeAll();
this.comboBox.add(this.buildDefaultValueEntry());
if (this.connectionProfileIsActive()) {
for (Iterator<String> stream = this.values(); stream.hasNext(); ) {
this.comboBox.add(stream.next());
}
}
this.updateSelectedItem_();
}
protected String buildDefaultValueEntry() {
if (getSubject() == null) {
return JptUiMappingsMessages.NoneSelected;
}
String defaultValue = this.getDefaultValue();
return (defaultValue == null) ? this.buildNullDefaultValueEntry() : this.buildNonNullDefaultValueEntry(defaultValue);
}
protected String buildNullDefaultValueEntry() {
return JptUiMappingsMessages.DefaultEmpty;
}
protected String buildNonNullDefaultValueEntry(String defaultValue) {
return NLS.bind(
JptUiMappingsMessages.DefaultWithOneParam,
defaultValue
);
}
protected void updateSelectedItem() {
// make sure the default value is up to date (??? ~bjv)
String defaultValueEntry = this.buildDefaultValueEntry();
if ( ! this.comboBox.getItem(0).equals(defaultValueEntry)) {
this.comboBox.remove(0);
this.comboBox.add(defaultValueEntry, 0);
}
this.updateSelectedItem_();
}
/**
* Updates the selected item by selecting the current value, if not
* <code>null</code>, or select the default value if one is available,
* otherwise remove the selection.
*/
protected void updateSelectedItem_() {
String value = (this.getSubject() == null) ? null : this.getValue();
if (value == null) {
// select the default value
this.comboBox.select(0);
} else {
// select the new value
if ( ! value.equals(this.comboBox.getText())) {
// This prevents the cursor from being set back to the beginning of the line (bug 234418).
// The reason we are hitting this method at all is because the
// context model is updating from the resource model in a way
// that causes change notifications to be fired (the annotation
// is added to the resource model, change notification occurs
// on the update thread, and then the name is set, these 2
// threads can get in the wrong order).
// The #valueChanged() method sets the populating flag to true,
// but in this case it is already set back to false when we
// receive notification back from the model because it has
// moved to the update thread and then jumps back on the UI thread.
this.comboBox.setText(value);
}
}
}
// ********** combo-box listener callback **********
protected void comboBoxModified() {
if ( ! this.isPopulating()) {
this.valueChanged(this.comboBox.getText());
}
}
/**
* The combo-box selection has changed, update the model if necessary.
* If the value has changed and the subject is null, we can build a subject
* before setting the value.
*/
protected void valueChanged(String newValue) {
JpaNode subject = this.getSubject();
String oldValue;
if (subject == null) {
if (this.nullSubjectIsNotAllowed()) {
return; // no subject to set the value on
}
oldValue = null;
} else {
oldValue = this.getValue();
}
// convert empty string or default to null
if (StringTools.stringIsEmpty(newValue) || this.valueIsDefault(newValue)) {
newValue = null;
}
// set the new value if it is different from the old value
if (this.valuesAreDifferent(oldValue, newValue)) {
this.setPopulating(true);
try {
this.setValue(newValue);
} finally {
this.setPopulating(false);
}
}
}
/**
* Return whether we can set the value when the subject is null
* (i.e. #setValue(String) will construct the subject if necessary).
*/
protected boolean nullSubjectIsAllowed() {
return false;
}
protected final boolean nullSubjectIsNotAllowed() {
return ! this.nullSubjectIsAllowed();
}
/**
* pre-condition: value is not null
*/
protected boolean valueIsDefault(String value) {
return (this.comboBox.getItemCount() > 0)
&& value.equals(this.comboBox.getItem(0));
}
protected boolean valuesAreEqual(String value1, String value2) {
if ((value1 == null) && (value2 == null)) {
return true; // both are null
}
if ((value1 == null) || (value2 == null)) {
return false; // one is null but the other is not
}
return value1.equals(value2);
}
protected boolean valuesAreDifferent(String value1, String value2) {
return ! this.valuesAreEqual(value1, value2);
}
// ********** convenience methods **********
/**
* Return the subject's JPA project.
* Allow subclasses to override this method, so we can still get the JPA
* project even when the subject is null.
*/
protected JpaProject getJpaProject() {
T subject = this.getSubject();
return (subject == null) ? null : subject.getJpaProject();
}
/**
* Return the subject's connection profile.
*/
protected final ConnectionProfile getConnectionProfile() {
JpaProject jpaProject = this.getJpaProject();
return (jpaProject == null) ? null : jpaProject.getConnectionProfile();
}
/**
* Return whether the subject's connection profile is active.
*/
protected final boolean connectionProfileIsActive() {
ConnectionProfile cp = this.getConnectionProfile();
return (cp != null) && cp.isActive();
}
/**
* Returns the subject's database.
*/
protected final Database getDatabase() {
ConnectionProfile cp = this.getConnectionProfile();
return (cp == null) ? null : cp.getDatabase();
}
// ********** connection listener callbacks **********
protected void repopulateComboBox() {
if ( ! this.comboBox.isDisposed()) {
this.repopulate();
}
}
protected final void databaseChanged(Database database) {
if ( ! this.comboBox.isDisposed()) {
this.databaseChanged_(database);
}
}
protected void databaseChanged_(@SuppressWarnings("unused") Database database) {
// do nothing by default
}
protected final void catalogChanged(Catalog catalog) {
if ( ! this.comboBox.isDisposed()) {
this.catalogChanged_(catalog);
}
}
protected void catalogChanged_(@SuppressWarnings("unused") Catalog catalog) {
// do nothing by default
}
protected final void schemaChanged(Schema schema) {
if ( ! this.comboBox.isDisposed()) {
this.schemaChanged_(schema);
}
}
protected void schemaChanged_(@SuppressWarnings("unused") Schema schema) {
// do nothing by default
}
protected final void sequenceChanged(Sequence sequence) {
if ( ! this.comboBox.isDisposed()) {
this.sequenceChanged_(sequence);
}
}
protected void sequenceChanged_(@SuppressWarnings("unused") Sequence sequence) {
// do nothing by default
}
protected final void tableChanged(Table table) {
if ( ! this.comboBox.isDisposed()) {
this.tableChanged_(table);
}
}
protected void tableChanged_(@SuppressWarnings("unused") Table table) {
// do nothing by default
}
protected final void columnChanged(Column column) {
if ( ! this.comboBox.isDisposed()) {
this.columnChanged_(column);
}
}
protected void columnChanged_(@SuppressWarnings("unused") Column column) {
// do nothing by default
}
protected final void foreignKeyChanged(ForeignKey foreignKey) {
if ( ! this.comboBox.isDisposed()) {
this.foreignKeyChanged_(foreignKey);
}
}
protected void foreignKeyChanged_(@SuppressWarnings("unused") ForeignKey foreignKey) {
// do nothing by default
}
@Override
protected void log(String flag, String message) {
if (flag.equals(Tracing.UI_DB) && Tracing.booleanDebugOption(Tracing.UI_DB)) {
this.log(message);
} else {
super.log(flag, message);
}
}
// ********** connection listener **********
protected class LocalConnectionListener implements ConnectionListener {
protected LocalConnectionListener() {
super();
}
public void opened(ConnectionProfile profile) {
this.log("opened: " + profile.getName());
DatabaseObjectCombo.this.repopulateComboBox();
}
public void modified(ConnectionProfile profile) {
this.log("modified: " + profile.getName());
DatabaseObjectCombo.this.repopulateComboBox();
}
public boolean okToClose(ConnectionProfile profile) {
this.log("OK to close: " + profile.getName());
return true;
}
public void aboutToClose(ConnectionProfile profile) {
this.log("about to close: " + profile.getName());
}
public void closed(ConnectionProfile profile) {
this.log("closed: " + profile.getName());
DatabaseObjectCombo.this.repopulateComboBox();
}
public void databaseChanged(ConnectionProfile profile, Database database) {
this.log("database changed: " + database.getName());
DatabaseObjectCombo.this.databaseChanged(database);
}
public void catalogChanged(ConnectionProfile profile, Catalog catalog) {
this.log("catalog changed: " + catalog.getName());
DatabaseObjectCombo.this.catalogChanged(catalog);
}
public void schemaChanged(ConnectionProfile profile, Schema schema) {
this.log("schema changed: " + schema.getName());
DatabaseObjectCombo.this.schemaChanged(schema);
}
public void sequenceChanged(ConnectionProfile profile, Sequence sequence) {
this.log("sequence changed: " + sequence.getName());
DatabaseObjectCombo.this.sequenceChanged(sequence);
}
public void tableChanged(ConnectionProfile profile, Table table) {
this.log("table changed: " + table.getName());
DatabaseObjectCombo.this.tableChanged(table);
}
public void columnChanged(ConnectionProfile profile, Column column) {
this.log("column changed: " + column.getName());
DatabaseObjectCombo.this.columnChanged(column);
}
public void foreignKeyChanged(ConnectionProfile profile, ForeignKey foreignKey) {
this.log("foreign key changed: " + foreignKey.getName());
DatabaseObjectCombo.this.foreignKeyChanged(foreignKey);
}
protected void log(String message) {
DatabaseObjectCombo.this.log(Tracing.UI_DB, message);
}
}
}