org.jkiss.dbeaver.ui.dialogs.data.ComplexObjectEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.jkiss.dbeaver.ui.dialogs.data.ComplexObjectEditor.java

Source

/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2016 Serge Rieder (serge@jkiss.org)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (version 2)
 * as published by the Free Software Foundation.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jkiss.dbeaver.ui.dialogs.data;

import org.eclipse.jface.action.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.themes.ITheme;
import org.jkiss.code.NotNull;
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.DBeaverUI;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRRunnableWithResult;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
import org.jkiss.dbeaver.model.struct.DBSDataType;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
import org.jkiss.dbeaver.ui.DBeaverIcons;
import org.jkiss.dbeaver.ui.UIIcon;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.controls.resultset.ThemeConstants;
import org.jkiss.dbeaver.ui.data.*;
import org.jkiss.dbeaver.ui.data.registry.DataManagerRegistry;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.IdentityHashMap;
import java.util.Map;

/**
 * Structure object editor
 */
public class ComplexObjectEditor extends TreeViewer {

    private static final Log log = Log.getLog(ComplexObjectEditor.class);

    private static class ComplexElement {
        boolean created, modified;
        Object value;
    }

    private static class CompositeField extends ComplexElement {
        final DBSAttributeBase attribute;
        DBDValueHandler valueHandler;

        private CompositeField(DBPDataSource dataSource, DBSAttributeBase attribute, @Nullable Object value) {
            this.attribute = attribute;
            this.value = value;
            this.valueHandler = DBUtils.findValueHandler(dataSource, attribute);
        }
    }

    private static class ArrayInfo {
        private DBDValueHandler valueHandler;
        private DBSDataType componentType;
    }

    private static class ArrayItem extends ComplexElement {
        final ArrayInfo array;
        int index;

        private ArrayItem(ArrayInfo array, int index, Object value) {
            this.array = array;
            this.index = index;
            this.value = value;
        }
    }

    private final IValueController parentController;
    private final IValueEditor editor;
    private DBCExecutionContext executionContext;
    private final TreeEditor treeEditor;
    private IValueEditor curCellEditor;

    private Color backgroundAdded;
    private Color backgroundDeleted;
    private Color backgroundModified;

    private CopyAction copyNameAction;
    private CopyAction copyValueAction;
    private Action addElementAction;
    private Action removeElementAction;

    private Map<Object, ComplexElement[]> childrenMap = new IdentityHashMap<>();

    public ComplexObjectEditor(IValueController parentController, IValueEditor editor, int style) {
        super(parentController.getEditPlaceholder(), style | SWT.SINGLE | SWT.FULL_SELECTION);
        this.parentController = parentController;
        this.editor = editor;

        ITheme currentTheme = parentController.getValueSite().getWorkbenchWindow().getWorkbench().getThemeManager()
                .getCurrentTheme();
        this.backgroundAdded = currentTheme.getColorRegistry().get(ThemeConstants.COLOR_SQL_RESULT_CELL_NEW_BACK);
        this.backgroundDeleted = currentTheme.getColorRegistry()
                .get(ThemeConstants.COLOR_SQL_RESULT_CELL_DELETED_BACK);
        this.backgroundModified = currentTheme.getColorRegistry()
                .get(ThemeConstants.COLOR_SQL_RESULT_CELL_MODIFIED_BACK);

        final Tree treeControl = super.getTree();
        treeControl.setHeaderVisible(true);
        treeControl.setLinesVisible(true);

        treeControl.addControlListener(new ControlAdapter() {
            private boolean packing = false;

            @Override
            public void controlResized(ControlEvent e) {
                if (!packing) {
                    packing = true;
                    UIUtils.packColumns(treeControl, true, new float[] { 0.2f, 0.8f });
                    if (treeControl.getColumn(0).getWidth() < 100) {
                        treeControl.getColumn(0).setWidth(100);
                    }
                    treeControl.removeControlListener(this);
                }
            }
        });

        ColumnViewerToolTipSupport.enableFor(this, ToolTip.NO_RECREATE);

        {
            TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
            column.getColumn().setWidth(200);
            column.getColumn().setMoveable(true);
            column.getColumn().setText(CoreMessages.ui_properties_name);
            column.setLabelProvider(new PropsLabelProvider(true));
        }

        {
            TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
            column.getColumn().setWidth(120);
            column.getColumn().setMoveable(true);
            column.getColumn().setText(CoreMessages.ui_properties_value);
            column.setLabelProvider(new PropsLabelProvider(false));
        }

        treeEditor = new TreeEditor(treeControl);
        treeEditor.horizontalAlignment = SWT.RIGHT;
        treeEditor.verticalAlignment = SWT.CENTER;
        treeEditor.grabHorizontal = true;
        treeEditor.minimumWidth = 50;

        treeControl.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDoubleClick(MouseEvent e) {
                TreeItem item = treeControl.getItem(new Point(e.x, e.y));
                if (item != null && UIUtils.getColumnAtPos(item, e.x, e.y) == 1) {
                    showEditor(item, false);
                }
            }
        });

        treeControl.addTraverseListener(new TraverseListener() {
            @Override
            public void keyTraversed(TraverseEvent e) {
                if (e.detail == SWT.TRAVERSE_RETURN) {
                    final TreeItem[] selection = treeControl.getSelection();
                    if (selection.length == 0) {
                        return;
                    }
                    if (treeEditor.getEditor() != null && !treeEditor.getEditor().isDisposed()) {
                        // Give a chance to catch it in editor handler
                        e.doit = true;
                        return;
                    }
                    showEditor(selection[0], (e.stateMask & SWT.SHIFT) == SWT.SHIFT);
                    e.doit = false;
                    e.detail = SWT.TRAVERSE_NONE;
                }
            }
        });

        super.setContentProvider(new StructContentProvider());

        this.copyNameAction = new CopyAction(true);
        this.copyValueAction = new CopyAction(false);
        this.addElementAction = new AddElementAction();
        this.removeElementAction = new RemoveElementAction();

        addElementAction.setEnabled(true);
        removeElementAction.setEnabled(false);

        addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                final IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                if (selection == null || selection.isEmpty()) {
                    copyNameAction.setEnabled(false);
                    copyValueAction.setEnabled(false);
                    removeElementAction.setEnabled(false);
                    addElementAction.setEnabled(getInput() instanceof DBDCollection);
                } else {
                    copyNameAction.setEnabled(true);
                    copyValueAction.setEnabled(true);
                    final Object element = selection.getFirstElement();
                    if (element instanceof ArrayItem) {
                        removeElementAction.setEnabled(true);
                        addElementAction.setEnabled(true);
                    }
                }
            }
        });

        createContextMenu();
    }

    private void createContextMenu() {
        Control control = getControl();
        MenuManager menuMgr = new MenuManager();
        Menu menu = menuMgr.createContextMenu(control);
        menuMgr.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(IMenuManager manager) {
                if (!getSelection().isEmpty()) {
                    manager.add(copyNameAction);
                    manager.add(copyValueAction);
                    manager.add(new Separator());
                }
                try {
                    parentController.getValueManager().contributeActions(manager, parentController, editor);
                } catch (DBCException e) {
                    log.error(e);
                }
            }
        });
        menuMgr.setRemoveAllWhenShown(true);
        control.setMenu(menu);
    }

    @Override
    public DBDComplexValue getInput() {
        return (DBDComplexValue) super.getInput();
    }

    public void setModel(DBCExecutionContext executionContext, final DBDComplexValue value) {
        getTree().setRedraw(false);
        try {
            this.executionContext = executionContext;
            this.childrenMap.clear();
            setInput(value);
            expandToLevel(2);
        } finally {
            getTree().setRedraw(true);
        }
    }

    private void showEditor(final TreeItem item, boolean advanced) {
        // Clean up any previous editor control
        disposeOldEditor();
        if (item == null) {
            return;
        }

        try {
            IValueController valueController = new ComplexValueController((ComplexElement) item.getData(),
                    advanced ? IValueController.EditType.EDITOR : IValueController.EditType.INLINE);

            curCellEditor = valueController.getValueManager().createEditor(valueController);
            if (curCellEditor != null) {
                curCellEditor.createControl();
                if (curCellEditor instanceof IValueEditorStandalone) {
                    ((IValueEditorStandalone) curCellEditor).showValueEditor();
                } else if (curCellEditor.getControl() != null) {
                    treeEditor.setEditor(curCellEditor.getControl(), item, 1);
                }
                if (!advanced) {
                    curCellEditor.primeEditorValue(valueController.getValue());
                }
            }
        } catch (DBException e) {
            UIUtils.showErrorDialog(getControl().getShell(), "Cell editor", "Can't open cell editor", e);
        }
    }

    private void disposeOldEditor() {
        curCellEditor = null;
        Control oldEditor = treeEditor.getEditor();
        if (oldEditor != null)
            oldEditor.dispose();
    }

    public Object extractValue() {
        DBDComplexValue complexValue = getInput();
        final ComplexElement[] items = childrenMap.get(complexValue);
        if (complexValue instanceof DBDValueCloneable) {
            try {
                complexValue = (DBDComplexValue) ((DBDValueCloneable) complexValue)
                        .cloneValue(VoidProgressMonitor.INSTANCE);
            } catch (DBCException e) {
                log.error("Error cloning complex value", e);
            }
        }
        if (complexValue instanceof DBDComposite) {
            for (int i = 0; i < items.length; i++) {
                ((DBDComposite) complexValue).setAttributeValue(((CompositeField) items[i]).attribute,
                        items[i].value);
            }
        } else if (complexValue instanceof DBDCollection) {
            if (items != null) {
                final Object[] newValues = new Object[items.length];
                for (int i = 0; i < items.length; i++) {
                    newValues[i] = items[i].value;
                }
                ((DBDCollection) complexValue).setContents(newValues);
            }
        }
        return complexValue;
    }

    private String getColumnText(ComplexElement obj, int columnIndex, DBDDisplayFormat format) {
        if (obj instanceof CompositeField) {
            CompositeField field = (CompositeField) obj;
            if (columnIndex == 0) {
                return field.attribute.getName();
            }
            return getValueText(field.valueHandler, field.attribute, field.value, format);
        } else if (obj instanceof ArrayItem) {
            ArrayItem item = (ArrayItem) obj;
            if (columnIndex == 0) {
                return String.valueOf(item.index);
            }
            return getValueText(item.array.valueHandler, item.array.componentType, item.value, format);
        }
        return String.valueOf(columnIndex);
    }

    private String getValueText(@NotNull DBDValueHandler valueHandler, @NotNull DBSTypedObject type,
            @Nullable Object value, @NotNull DBDDisplayFormat format) {
        if (value instanceof DBDCollection) {
            return "[" + ((DBDCollection) value).getComponentType().getName() + " - "
                    + ((DBDCollection) value).getItemCount() + "]";
        } else if (value instanceof DBDComposite) {
            return "[" + ((DBDComposite) value).getDataType().getName() + "]";
        } else if (value instanceof DBDReference) {
            return "--> [" + ((DBDReference) value).getReferencedType().getName() + "]";
        } else {
            return valueHandler.getValueDisplayString(type, value, format);
        }
    }

    private class ComplexValueController implements IValueController, IMultiController {
        private final ComplexElement item;
        private final DBDValueHandler valueHandler;
        private final DBSTypedObject type;
        private final String name;
        private final Object value;
        private final EditType editType;

        public ComplexValueController(ComplexElement obj, EditType editType) throws DBCException {
            this.item = obj;
            if (this.item instanceof CompositeField) {
                CompositeField field = (CompositeField) this.item;
                valueHandler = field.valueHandler;
                type = field.attribute;
                name = field.attribute.getName();
                value = field.value;
            } else if (this.item instanceof ArrayItem) {
                ArrayItem arrayItem = (ArrayItem) this.item;
                valueHandler = arrayItem.array.valueHandler;
                type = arrayItem.array.componentType;
                name = type.getTypeName() + "[" + arrayItem.index + "]";
                value = arrayItem.value;
            } else {
                throw new DBCException("Unsupported complex object element: " + this.item);
            }
            this.editType = editType;
        }

        @NotNull
        @Override
        public DBCExecutionContext getExecutionContext() {
            return executionContext;
        }

        @Override
        public String getValueName() {
            return name;
        }

        @Override
        public DBSTypedObject getValueType() {
            return type;
        }

        @Nullable
        @Override
        public Object getValue() {
            return value;
        }

        @Override
        public void updateValue(Object value) {
            if (CommonUtils.equalObjects(this.item.value, value)) {
                return;
            }
            this.item.value = value;
            this.item.modified = true;
            refresh(this.item);
        }

        @Override
        public DBDValueHandler getValueHandler() {
            return valueHandler;
        }

        @Override
        public IValueManager getValueManager() {
            DBSTypedObject valueType = getValueType();
            return DataManagerRegistry.findValueManager(getExecutionContext().getDataSource().getContainer(),
                    valueType, getValueHandler().getValueObjectType(valueType));
        }

        @Override
        public EditType getEditType() {
            return editType;
        }

        @Override
        public boolean isReadOnly() {
            return parentController.isReadOnly();
        }

        @Override
        public IWorkbenchPartSite getValueSite() {
            return parentController.getValueSite();
        }

        @Override
        public Composite getEditPlaceholder() {
            return getTree();
        }

        @Override
        public IContributionManager getEditBar() {
            return null;
        }

        @Override
        public void showMessage(String message, boolean error) {

        }

        @Override
        public void closeInlineEditor() {
            disposeOldEditor();
        }

        @Override
        public void nextInlineEditor(boolean next) {
            disposeOldEditor();
        }
    }

    class StructContentProvider implements IStructuredContentProvider, ITreeContentProvider {
        public StructContentProvider() {
        }

        @Override
        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
        }

        @Override
        public void dispose() {
        }

        @Override
        public Object[] getElements(Object parent) {
            return getChildren(parent);
        }

        @Nullable
        @Override
        public Object getParent(Object child) {
            return null;
        }

        @Override
        public ComplexElement[] getChildren(Object parent) {
            ComplexElement[] children = childrenMap.get(parent);
            if (children != null) {
                return children;
            }

            if (parent instanceof DBDComposite) {
                DBDComposite structure = (DBDComposite) parent;
                try {
                    DBSAttributeBase[] attributes = structure.getAttributes();
                    children = new CompositeField[attributes.length];
                    for (int i = 0; i < attributes.length; i++) {
                        DBSAttributeBase attr = attributes[i];
                        Object value = structure.getAttributeValue(attr);
                        children[i] = new CompositeField(structure.getDataType().getDataSource(), attr, value);
                    }
                } catch (DBException e) {
                    log.error("Error getting structure meta data", e);
                }
            } else if (parent instanceof DBDCollection) {
                DBDCollection array = (DBDCollection) parent;
                ArrayInfo arrayInfo = makeArrayInfo(array);

                children = new ArrayItem[array.getItemCount()];
                for (int i = 0; i < children.length; i++) {
                    children[i] = new ArrayItem(arrayInfo, i, array.getItem(i));
                }
            } else if (parent instanceof DBDReference) {
                final DBDReference reference = (DBDReference) parent;
                DBRRunnableWithResult<Object> runnable = new DBRRunnableWithResult<Object>() {
                    @Override
                    public void run(DBRProgressMonitor monitor)
                            throws InvocationTargetException, InterruptedException {
                        try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.UTIL,
                                "Read reference value")) {
                            result = reference.getReferencedObject(session);
                        } catch (DBCException e) {
                            throw new InvocationTargetException(e);
                        }
                    }
                };
                DBeaverUI.runInUI(DBeaverUI.getActiveWorkbenchWindow(), runnable);
                children = getChildren(runnable.getResult());
            } else if (parent instanceof CompositeField) {
                Object value = ((CompositeField) parent).value;
                if (isComplexType(value)) {
                    children = getChildren(value);
                }
            } else if (parent instanceof ArrayItem) {
                Object value = ((ArrayItem) parent).value;
                if (isComplexType(value)) {
                    children = getChildren(value);
                }
            }
            if (children != null) {
                childrenMap.put(parent, children);
            }
            return children;
        }

        private boolean isComplexType(Object value) {
            return value instanceof DBDComplexValue;
        }

        @Override
        public boolean hasChildren(Object parent) {
            return parent instanceof DBDComposite || parent instanceof DBDCollection
                    || parent instanceof DBDReference
                    || (parent instanceof CompositeField && hasChildren(((CompositeField) parent).value))
                    || (parent instanceof ArrayItem && hasChildren(((ArrayItem) parent).value));
        }
    }

    @NotNull
    private ArrayInfo makeArrayInfo(DBDCollection array) {
        ArrayInfo arrayInfo = new ArrayInfo();
        arrayInfo.componentType = array.getComponentType();
        arrayInfo.valueHandler = DBUtils.findValueHandler(arrayInfo.componentType.getDataSource(),
                arrayInfo.componentType);
        return arrayInfo;
    }

    private void shiftArrayItems(ComplexElement[] arrayItems, int startIndex, int inc) {
        for (int i = startIndex; i < arrayItems.length; i++) {
            ((ArrayItem) arrayItems[i]).index += inc;
        }
    }

    private class PropsLabelProvider extends CellLabelProvider {
        private final boolean isName;

        public PropsLabelProvider(boolean isName) {
            this.isName = isName;
        }

        public String getText(ComplexElement obj, int columnIndex) {
            return getColumnText(obj, columnIndex, DBDDisplayFormat.UI);
        }

        @Override
        public String getToolTipText(Object obj) {
            if (obj instanceof CompositeField) {
                return ((CompositeField) obj).attribute.getName() + " "
                        + ((CompositeField) obj).attribute.getTypeName();
            }
            return null;
        }

        @Override
        public void update(ViewerCell cell) {
            ComplexElement element = (ComplexElement) cell.getElement();
            cell.setText(getText(element, cell.getColumnIndex()));
            if (element.created) {
                cell.setBackground(backgroundAdded);
            } else if (element.modified) {
                cell.setBackground(backgroundModified);
            } else {
                cell.setBackground(null);
            }
        }

    }

    private class CopyAction extends Action {
        private final boolean isName;

        public CopyAction(boolean isName) {
            super(CoreMessages.controls_itemlist_action_copy + " " + getTree().getColumn(isName ? 0 : 1).getText());
            this.isName = isName;
        }

        @Override
        public void run() {
            final IStructuredSelection selection = getStructuredSelection();
            if (!selection.isEmpty()) {
                String text = getColumnText((ComplexElement) selection.getFirstElement(), isName ? 0 : 1,
                        DBDDisplayFormat.NATIVE);
                if (text != null) {
                    UIUtils.setClipboardContents(getTree().getDisplay(), TextTransfer.getInstance(), text);
                }
            }
        }
    }

    private class AddElementAction extends Action {
        public AddElementAction() {
            super("Add element", DBeaverIcons.getImageDescriptor(UIIcon.ROW_ADD));
        }

        @Override
        public void run() {
            DBDCollection collection = (DBDCollection) getInput();
            ComplexElement[] arrayItems = childrenMap.get(collection);
            if (arrayItems == null) {
                log.error("Can't find children items for add");
                return;
            }
            final IStructuredSelection selection = getStructuredSelection();
            ArrayItem newItem;
            if (selection.isEmpty()) {
                newItem = new ArrayItem(makeArrayInfo(collection), 0, null);
            } else {
                ArrayItem curItem = (ArrayItem) selection.getFirstElement();
                newItem = new ArrayItem(curItem.array, curItem.index + 1, null);
            }
            shiftArrayItems(arrayItems, newItem.index, 1);
            arrayItems = ArrayUtils.insertArea(ComplexElement.class, arrayItems, newItem.index,
                    new ComplexElement[] { newItem });
            childrenMap.put(collection, arrayItems);
            refresh();

            final Widget treeItem = findItem(newItem);
            if (treeItem != null) {
                showEditor((TreeItem) treeItem, false);
            }
        }
    }

    private class RemoveElementAction extends Action {
        public RemoveElementAction() {
            super("Remove element", DBeaverIcons.getImageDescriptor(UIIcon.ROW_DELETE));
        }

        @Override
        public void run() {
            final IStructuredSelection selection = getStructuredSelection();
            if (selection.isEmpty()) {
                return;
            }

            DBDCollection collection = (DBDCollection) getInput();
            ComplexElement[] arrayItems = childrenMap.get(collection);
            if (arrayItems == null) {
                log.error("Can't find children items for delete");
                return;
            }
            ArrayItem item = (ArrayItem) selection.getFirstElement();
            shiftArrayItems(arrayItems, item.index, -1);
            arrayItems = ArrayUtils.remove(ComplexElement.class, arrayItems, item);
            childrenMap.put(collection, arrayItems);
            refresh();
        }
    }

    public void contributeActions(IContributionManager manager) {
        manager.add(addElementAction);
        manager.add(removeElementAction);
    }

}