/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* Licensed 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.
*/
package org.jkiss.dbeaver.ui.data.editors;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IWorkbenchWindow;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.core.CoreMessages;
import org.jkiss.dbeaver.core.DBeaverCore;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRRunnableWithProgress;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.runtime.jobs.DataSourceJob;
import org.jkiss.dbeaver.ui.DBeaverIcons;
import org.jkiss.dbeaver.ui.UIIcon;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.actions.navigator.NavigatorHandlerObjectOpen;
import org.jkiss.dbeaver.ui.data.IAttributeController;
import org.jkiss.dbeaver.ui.data.IValueController;
import org.jkiss.dbeaver.ui.data.IValueEditor;
import org.jkiss.dbeaver.ui.editors.data.DatabaseDataEditor;
import org.jkiss.dbeaver.ui.editors.object.struct.EditDictionaryPage;
import org.jkiss.utils.CommonUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
/**
* ReferenceValueEditor
*
* @author Serge Rider
*/
public class ReferenceValueEditor {
private static final Log log = Log.getLog(ReferenceValueEditor.class);
private IValueController valueController;
private IValueEditor valueEditor;
private DBSEntityReferrer refConstraint;
private Table editorSelector;
private SelectorLoaderJob loaderJob = null;
public ReferenceValueEditor(IValueController valueController, IValueEditor valueEditor) {
this.valueController = valueController;
this.valueEditor = valueEditor;
}
public boolean isReferenceValue()
{
return getEnumerableConstraint() != null;
}
@Nullable
private DBSEntityReferrer getEnumerableConstraint()
{
if (valueController instanceof IAttributeController) {
return getEnumerableConstraint(((IAttributeController) valueController).getBinding());
}
return null;
}
public static DBSEntityReferrer getEnumerableConstraint(DBDAttributeBinding binding) {
try {
DBSEntityAttribute entityAttribute = binding.getEntityAttribute();
if (entityAttribute != null) {
List<DBSEntityReferrer> refs = DBUtils.getAttributeReferrers(new VoidProgressMonitor(), entityAttribute);
DBSEntityReferrer constraint = refs.isEmpty() ? null : refs.get(0);
if (constraint instanceof DBSEntityAssociation &&
((DBSEntityAssociation)constraint).getReferencedConstraint() instanceof DBSConstraintEnumerable)
{
final DBSConstraintEnumerable refConstraint = (DBSConstraintEnumerable) ((DBSEntityAssociation) constraint).getReferencedConstraint();
if (refConstraint != null && refConstraint.supportsEnumeration()) {
return constraint;
}
}
}
} catch (DBException e) {
log.error(e);
}
return null;
}
public boolean createEditorSelector(final Composite parent)
{
if (!(valueController instanceof IAttributeController) || valueController.isReadOnly()) {
return false;
}
refConstraint = getEnumerableConstraint();
if (refConstraint == null) {
return false;
}
if (refConstraint instanceof DBSEntityAssociation) {
final DBSEntityAssociation association = (DBSEntityAssociation)refConstraint;
if (association.getReferencedConstraint() != null) {
final DBSEntity refTable = association.getReferencedConstraint().getParentObject();
Composite labelGroup = UIUtils.createPlaceholder(parent, 2);
labelGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
Link dictLabel = UIUtils.createLink(
labelGroup,
NLS.bind(CoreMessages.dialog_value_view_label_dictionary, refTable.getName()), new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Open
final IWorkbenchWindow window = valueController.getValueSite().getWorkbenchWindow();
DBeaverUI.runInUI(window, new DBRRunnableWithProgress() {
@Override
public void run(DBRProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
DBNDatabaseNode tableNode = DBeaverCore.getInstance().getNavigatorModel().getNodeByObject(
monitor,
refTable,
true
);
if (tableNode != null) {
NavigatorHandlerObjectOpen.openEntityEditor(tableNode, DatabaseDataEditor.class.getName(), window);
}
}
});
}
});
dictLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
Link hintLabel = UIUtils.createLink(labelGroup, "(<a>Define Description</a>)", new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
EditDictionaryPage editDictionaryPage = new EditDictionaryPage("Dictionary structure", refTable);
if (editDictionaryPage.edit(parent.getShell())) {
loaderJob.schedule();
}
}
});
hintLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.HORIZONTAL_ALIGN_END));
}
}
editorSelector = new Table(parent, SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
editorSelector.setLinesVisible(true);
editorSelector.setHeaderVisible(true);
GridData gd = new GridData(GridData.FILL_BOTH);
gd.heightHint = 150;
//gd.widthHint = 300;
//gd.grabExcessVerticalSpace = true;
//gd.grabExcessHorizontalSpace = true;
editorSelector.setLayoutData(gd);
UIUtils.createTableColumn(editorSelector, SWT.LEFT, CoreMessages.dialog_value_view_column_value);
UIUtils.createTableColumn(editorSelector, SWT.LEFT, CoreMessages.dialog_value_view_column_description);
UIUtils.packColumns(editorSelector);
editorSelector.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e)
{
TableItem[] selection = editorSelector.getSelection();
if (selection != null && selection.length > 0) {
Object value = selection[0].getData();
//editorControl.setText(selection[0].getText());
try {
valueEditor.primeEditorValue(value);
} catch (DBException e1) {
log.error(e1);
}
}
}
});
Control control = valueEditor.getControl();
ModifyListener modifyListener = new ModifyListener() {
@Override
public void modifyText(ModifyEvent e)
{
Object curEditorValue;
try {
curEditorValue = valueEditor.extractEditorValue();
} catch (DBException e1) {
log.error(e1);
return;
}
// Try to select current value in the table
final String curTextValue = valueController.getValueHandler().getValueDisplayString(
((IAttributeController) valueController).getBinding(),
curEditorValue,
DBDDisplayFormat.UI);
boolean valueFound = false;
for (TableItem item : editorSelector.getItems()) {
if (item.getText(0).equals(curTextValue)) {
editorSelector.select(editorSelector.indexOf(item));
editorSelector.showItem(item);
valueFound = true;
break;
}
}
if (!valueFound) {
// Read dictionary
if (loaderJob.getState() == Job.RUNNING) {
// Cancel it and create new one
loaderJob.cancel();
loaderJob = new SelectorLoaderJob();
}
loaderJob.setPattern(curEditorValue);
if (loaderJob.getState() != Job.WAITING) {
loaderJob.schedule(100);
}
}
}
};
if (control instanceof Text) {
((Text)control).addModifyListener(modifyListener);
} else if (control instanceof StyledText) {
((StyledText)control).addModifyListener(modifyListener);
}
loaderJob = new SelectorLoaderJob();
final Object curValue = valueController.getValue();
if (curValue instanceof Number) {
loaderJob.setPattern(curValue);
}
loaderJob.schedule(500);
return true;
}
private void updateDictionarySelector(Map<Object, String> keyValues, DBSEntityAttributeRef keyColumn, DBDValueHandler keyHandler) {
if (editorSelector == null || editorSelector.isDisposed()) {
return;
}
editorSelector.setRedraw(false);
try {
editorSelector.removeAll();
for (Map.Entry<Object, String> entry : keyValues.entrySet()) {
TableItem discItem = new TableItem(editorSelector, SWT.NONE);
discItem.setText(0,
keyHandler.getValueDisplayString(
keyColumn.getAttribute(),
entry.getKey(),
DBDDisplayFormat.UI));
discItem.setText(1, entry.getValue());
discItem.setData(entry.getKey());
}
Control editorControl = valueEditor.getControl();
if (editorControl != null && !editorControl.isDisposed()) {
try {
Object curValue = valueEditor.extractEditorValue();
final String curTextValue = valueController.getValueHandler().getValueDisplayString(
((IAttributeController) valueController).getBinding(),
curValue,
DBDDisplayFormat.UI);
TableItem curItem = null;
for (TableItem item : editorSelector.getItems()) {
if (item.getText(0).equals(curTextValue)) {
curItem = item;
break;
}
}
if (curItem != null) {
editorSelector.setSelection(curItem);
editorSelector.showItem(curItem);
}
} catch (DBException e) {
log.error(e);
}
}
UIUtils.maxTableColumnsWidth(editorSelector);
} finally {
editorSelector.setRedraw(true);
}
}
private class SelectorLoaderJob extends DataSourceJob {
private Object pattern;
private SelectorLoaderJob()
{
super(
CoreMessages.dialog_value_view_job_selector_name + valueController.getValueName() + " possible values",
DBeaverIcons.getImageDescriptor(UIIcon.SQL_EXECUTE),
valueController.getExecutionContext());
setUser(false);
}
void setPattern(@Nullable Object pattern)
{
this.pattern = pattern;
}
@Override
protected IStatus run(DBRProgressMonitor monitor)
{
if (editorSelector.isDisposed()) {
return Status.OK_STATUS;
}
final Map<Object, String> keyValues = new TreeMap<>();
try {
IAttributeController attributeController = (IAttributeController)valueController;
final DBSEntityAttribute tableColumn = attributeController.getBinding().getEntityAttribute();
if (tableColumn == null) {
return Status.OK_STATUS;
}
final DBSEntityAttributeRef fkColumn = DBUtils.getConstraintAttribute(monitor, refConstraint, tableColumn);
if (fkColumn == null) {
return Status.OK_STATUS;
}
DBSEntityAssociation association;
if (refConstraint instanceof DBSEntityAssociation) {
association = (DBSEntityAssociation)refConstraint;
} else {
return Status.OK_STATUS;
}
final DBSEntityAttribute refColumn = DBUtils.getReferenceAttribute(monitor, association, tableColumn);
if (refColumn == null) {
return Status.OK_STATUS;
}
List<DBDAttributeValue> precedingKeys = null;
List<? extends DBSEntityAttributeRef> allColumns = CommonUtils.safeList(refConstraint.getAttributeReferences(monitor));
if (allColumns.size() > 1 && allColumns.get(0) != fkColumn) {
// Our column is not a first on in foreign key.
// So, fill uo preceeding keys
List<DBDAttributeBinding> rowAttributes = attributeController.getRowController().getRowAttributes();
precedingKeys = new ArrayList<>();
for (DBSEntityAttributeRef precColumn : allColumns) {
if (precColumn == fkColumn) {
// Enough
break;
}
DBSEntityAttribute precAttribute = precColumn.getAttribute();
if (precAttribute != null) {
DBDAttributeBinding rowAttr = DBUtils.findBinding(rowAttributes, precAttribute);
if (rowAttr != null) {
Object precValue = attributeController.getRowController().getAttributeValue(rowAttr);
precedingKeys.add(new DBDAttributeValue(precAttribute, precValue));
}
}
}
}
final DBSEntityAttribute fkAttribute = fkColumn.getAttribute();
final DBSEntityConstraint refConstraint = association.getReferencedConstraint();
final DBSConstraintEnumerable enumConstraint = (DBSConstraintEnumerable) refConstraint;
if (fkAttribute != null && enumConstraint != null) {
try (DBCSession session = getExecutionContext().openSession(
monitor,
DBCExecutionPurpose.UTIL,
NLS.bind(CoreMessages.dialog_value_view_context_name, fkAttribute.getName()))) {
Collection<DBDLabelValuePair> enumValues = enumConstraint.getKeyEnumeration(
session,
refColumn,
pattern,
precedingKeys,
200);
for (DBDLabelValuePair pair : enumValues) {
keyValues.put(pair.getValue(), pair.getLabel());
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
final DBDValueHandler colHandler = DBUtils.findValueHandler(session, fkAttribute);
DBeaverUI.syncExec(new Runnable() {
@Override
public void run() {
updateDictionarySelector(keyValues, fkColumn, colHandler);
}
});
}
}
} catch (DBException e) {
// error
// just ignore
log.warn(e);
}
return Status.OK_STATUS;
}
}
}