com.centurylink.mdw.plugin.designer.properties.editor.TableEditor.java Source code

Java tutorial

Introduction

Here is the source code for com.centurylink.mdw.plugin.designer.properties.editor.TableEditor.java

Source

/*
 * Copyright (C) 2017 CenturyLink, Inc.
 *
 * 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 com.centurylink.mdw.plugin.designer.properties.editor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.ICellEditorListener;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
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.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

import com.centurylink.jface.viewers.TreeComboCellEditor;
import com.centurylink.jface.viewers.TreeComboCellEditor.SelectionModifier;
import com.centurylink.mdw.common.utilities.StringHelper;
import com.centurylink.mdw.model.value.attribute.AssetVersionSpec;
import com.centurylink.mdw.model.value.attribute.RuleSetVO;
import com.centurylink.mdw.plugin.MdwPlugin;
import com.centurylink.mdw.plugin.designer.model.Activity;
import com.centurylink.mdw.plugin.designer.model.WorkflowAsset;
import com.centurylink.mdw.plugin.designer.model.WorkflowElement;
import com.centurylink.mdw.plugin.designer.model.WorkflowPackage;
import com.centurylink.mdw.plugin.designer.model.WorkflowProcess;
import com.centurylink.swt.widgets.CTreeCombo;
import com.centurylink.swt.widgets.CTreeComboItem;
import com.qwest.mbeng.MbengNode;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class TableEditor extends PropertyEditor {
    public static final String TYPE_TABLE = "TABLE";
    public static final String TYPE_MAPPING = "MAPPING";

    private char rowDelimiter = ';';

    public char getRowDelimiter() {
        return rowDelimiter;
    }

    public void setRowDelimiter(char c) {
        this.rowDelimiter = c;
    }

    private char columnDelimiter = ',';

    public char getColumnDelimiter() {
        return columnDelimiter;
    }

    public void setColumnDelimiter(char c) {
        this.columnDelimiter = c;
    }

    public int horizontalSpan;

    public int getHorizontalSpan() {
        return horizontalSpan;
    }

    public void setHorizontalSpan(int span) {
        this.horizontalSpan = span;
    }

    private List<ColumnSpec> columnSpecs;
    private String[] columnProps;
    private TableViewer tableViewer;

    public TableViewer getTableViewer() {
        return tableViewer;
    }

    private Composite buttonComposite;
    private Button addButton;

    public Button getAddButton() {
        return addButton;
    }

    private Button deleteButton;

    public Button getDeleteButton() {
        return deleteButton;
    }

    private boolean fillWidth; // if true then last column will expand to fill
                               // extra space

    public boolean isFillWidth() {
        return fillWidth;
    }

    public void setFillWidth(boolean fill) {
        this.fillWidth = fill;
    }

    private IStructuredContentProvider contentProvider;

    public IStructuredContentProvider getContentProvider() {
        return contentProvider;
    }

    public void setContentProvider(IStructuredContentProvider cp) {
        this.contentProvider = cp;
    }

    private ITableLabelProvider labelProvider;

    public ITableLabelProvider getLabelProvider() {
        return labelProvider;
    }

    public void setLabelProvider(ITableLabelProvider lp) {
        this.labelProvider = lp;
    }

    private ICellModifier cellModifier;

    public ICellModifier getCellModifier() {
        return cellModifier;
    }

    public void setCellModifier(ICellModifier cm) {
        this.cellModifier = cm;
    }

    private TableModelUpdater modelUpdater;

    public TableModelUpdater getModelUpdater() {
        return modelUpdater;
    }

    public void setModelUpdater(TableModelUpdater mu) {
        this.modelUpdater = mu;
    }

    public Table getTable() {
        return (Table) getWidget();
    }

    private List tableValue;

    public List getTableValue() {
        return tableValue;
    }

    public void setTableValue(List value) {
        this.tableValue = value;
    }

    // instead of mbeng
    private String attributeName;

    public String getAttributeName() {
        return attributeName;
    }

    public void setAttributeName(String name) {
        this.attributeName = name;
    }

    private boolean suppressAddDeleteEvenForEditable;

    public boolean isSuppressAddDeleteEvenForEditable() {
        return suppressAddDeleteEvenForEditable;
    }

    public TableEditor(WorkflowElement canvasSelection, MbengNode mbengNode) {
        super(canvasSelection, mbengNode);
        if (mbengNode.getAttribute("MODIFYONLY") != null) {
            suppressAddDeleteEvenForEditable = Boolean.parseBoolean(mbengNode.getAttribute("MODIFYONLY"));
        }

        columnSpecs = new ArrayList<ColumnSpec>();
        columnSpecs.add(getDummyColumn());

        for (MbengNode childNode = mbengNode.getFirstChild(); childNode != null; childNode = childNode
                .getNextSibling()) {
            String type = childNode.getName();
            String label = childNode.getAttribute("LABEL");
            String property = childNode.getAttribute("NAME");
            if (property == null)
                property = "columnValue_" + columnSpecs.size();
            ColumnSpec colSpec = new ColumnSpec(type, label, property);
            if (type.equals(TYPE_CHECKBOX)) {
                colSpec.width = 75;
            } else if (type.equals(TYPE_COMBO)) {
                colSpec.source = childNode.getAttribute("SOURCE");
                if ("Process".equals(colSpec.source)) {
                    colSpec.type = WorkflowAssetEditor.TYPE_ASSET;
                    columnSpecs.get(columnSpecs.size() - 1).width = 150;
                    colSpec.width = 300;
                    colSpec.readOnly = false;
                    colSpec.assetTypes = new String[] { "Process" };
                } else if ("RuleSets".equals(colSpec.source)) {
                    colSpec.type = WorkflowAssetEditor.TYPE_ASSET;
                    colSpec.width = Integer.parseInt(childNode.getAttribute("VW"));
                    colSpec.readOnly = false;
                    String typeAttr = childNode.getAttribute("TYPE");
                    List<String> assetTypes = null;
                    if (typeAttr != null) {
                        assetTypes = new ArrayList<String>();
                        for (String assetType : typeAttr.split(","))
                            assetTypes.add(assetType);
                    }

                    colSpec.assetTypes = assetTypes.toArray(new String[0]);
                } else if ("ProcessVersion".equals(colSpec.source) || "AssetVersion".equals(colSpec.source)) {
                    // this field is suppressed now (populated through asset
                    // picker)
                    colSpec.hidden = true;
                    colSpec.options = new String[] { "" };
                } else {
                    List<String> valueOptions = new ArrayList<String>();
                    for (MbengNode optionNode = childNode
                            .getFirstChild(); optionNode != null; optionNode = optionNode.getNextSibling()) {
                        valueOptions.add(optionNode.getValue());
                    }
                    colSpec.options = valueOptions.toArray(new String[0]);
                }
            }

            if (childNode.getAttribute("READONLY") != null) {
                colSpec.readOnly = Boolean.parseBoolean(childNode.getAttribute("READONLY"));
            }
            if (childNode.getAttribute("VW") != null) {
                colSpec.width = Integer.parseInt(childNode.getAttribute("VW"));
            }

            colSpec.defaultValue = childNode.getAttribute("DEFAULT");

            columnSpecs.add(colSpec);
        }

        setColumnSpecs(columnSpecs);
    }

    public TableEditor(WorkflowElement canvasSelection, String type) {
        super(canvasSelection, type);
    }

    public void setColumnSpecs(List<ColumnSpec> columnSpecs) {
        this.columnSpecs = columnSpecs;
        columnProps = new String[columnSpecs.size()];
        for (int i = 0; i < columnSpecs.size(); i++) {
            columnProps[i] = columnSpecs.get(i).property;
        }
    }

    public List<ColumnSpec> getColumnSpecs() {
        return columnSpecs;
    }

    /**
     * Populates the table widget from the model
     */
    public void setValue(Activity activity) {
        super.setValue(activity);

        if (contentProvider instanceof DefaultContentProvider) {
            List<String[]> entries = StringHelper.parseTable(getValue(), columnDelimiter, rowDelimiter,
                    columnSpecs.size());
            tableValue = new ArrayList<DefaultRowImpl>();
            for (int i = 0; i < entries.size(); i++) {
                tableValue.add(new DefaultRowImpl(entries.get(i), true));
            }
        }

        tableViewer.setInput(tableValue);
    }

    public void setValue(WorkflowAsset workflowAsset) {
        super.setValue(workflowAsset);

        if (contentProvider instanceof DefaultContentProvider) {
            List<String[]> entries = StringHelper.parseTable(getValue(), columnDelimiter, rowDelimiter,
                    columnSpecs.size());
            tableValue = new ArrayList<DefaultRowImpl>();
            for (int i = 0; i < entries.size(); i++) {
                tableValue.add(new DefaultRowImpl(entries.get(i), true));
            }
        }
        tableViewer.setInput(tableValue);
    }

    public void setValue(List value) {
        tableValue = value;
        tableViewer.setInput(tableValue);
    }

    public void setValue(String stringVal) {
        List<DefaultRowImpl> listVal = new ArrayList<DefaultRowImpl>();
        if (stringVal != null) {
            StringTokenizer rowTokenizer = new StringTokenizer(stringVal, String.valueOf(rowDelimiter));
            while (rowTokenizer.hasMoreTokens()) {
                String row = rowTokenizer.nextToken();
                StringTokenizer columnTokenizer = new StringTokenizer(row, String.valueOf(columnDelimiter));
                int numCols = columnTokenizer.countTokens();
                String[] columns = new String[numCols];
                for (int i = 0; i < numCols; i++)
                    columns[i] = columnTokenizer.nextToken();
                listVal.add(new DefaultRowImpl(columns));
            }
        }
        setValue(listVal);
    }

    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (addButton != null)
            addButton.setEnabled(enabled);
        if (deleteButton != null)
            deleteButton.setEnabled(enabled);
    }

    private boolean editable = true;

    public void setEditable(boolean editable) {
        this.editable = editable;
        if (addButton != null)
            addButton.setEnabled(editable);
        if (deleteButton != null)
            deleteButton.setEnabled(editable);
    }

    public void disposeWidget() {
        super.disposeWidget();
        if (buttonComposite != null)
            buttonComposite.dispose();
    }

    /**
     * @see com.centurylink.mdw.plugin.designer.properties.editor.PropertyEditor#render(org.eclipse.swt.widgets.Composite)
     */
    @Override
    public void render(Composite parent) {
        render(parent, !isReadOnly() && !suppressAddDeleteEvenForEditable);
    }

    @Override
    public String getLabel() {
        if (getSection() != null)
            return null; // suppress label for dedicated section
        else
            return super.getLabel();
    }

    public void render(Composite parent, boolean includeAddDeleteButtons) {
        if (contentProvider == null)
            contentProvider = new DefaultContentProvider();
        if (labelProvider == null)
            labelProvider = new DefaultLabelProvider();
        if (!isReadOnly() && cellModifier == null)
            cellModifier = new DefaultCellModifier();
        if (modelUpdater == null)
            modelUpdater = new DefaultModelUpdater();

        if (getLabel() != null)
            createLabel(parent);

        setWidget(createTable(parent));
        tableViewer = createTableViewer(getTable());
        tableViewer.setContentProvider(contentProvider);
        tableViewer.setLabelProvider(labelProvider);

        if (includeAddDeleteButtons) {
            createButtons(parent);
        }
    }

    private Table createTable(Composite parent) {
        int style = SWT.SINGLE | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.HIDE_SELECTION;

        final Table table = new Table(parent, style);

        GridData gridData = new GridData(GridData.FILL_BOTH);
        if (horizontalSpan == 0)
            gridData.horizontalSpan = getLabel() == null ? 3 : 2;
        else
            gridData.horizontalSpan = horizontalSpan;
        if (getType().equals(TYPE_TABLE))
            gridData.heightHint = 100;
        if (getHeight() != SWT.DEFAULT)
            gridData.heightHint = getHeight();
        if (getWidth() != DEFAULT_VALUE_WIDTH)
            gridData.widthHint = getWidth();
        table.setLayoutData(gridData);
        table.setLinesVisible(true);
        table.setHeaderVisible(true);

        for (int i = 0; i < columnSpecs.size(); i++) {
            ColumnSpec colSpec = columnSpecs.get(i);
            int colStyle = SWT.LEFT | colSpec.style;
            if (colSpec.readOnly)
                colStyle = colStyle | SWT.READ_ONLY;
            TableColumn column = new TableColumn(table, colStyle, i);
            column.setText(colSpec.label);
            if (colSpec.hidden) {
                column.setWidth(0);
                column.setResizable(false);
            } else {
                column.setWidth(colSpec.width);
                column.setResizable(colSpec.resizable);
            }
            if (colSpec.height != ColumnSpec.DEFAULT_ROW_HEIGHT) {
                table.addListener(SWT.MeasureItem, new RowHeightListener(colSpec.height));
            }
        }

        if (isFillWidth()) {
            table.addControlListener(new ControlAdapter() {
                @Override
                public void controlResized(ControlEvent e) {
                    int tableWidth = table.getBounds().width;
                    int cumulative = 0;
                    TableColumn[] tableColumns = table.getColumns();
                    for (int i = 0; i < tableColumns.length; i++) {
                        if (i == tableColumns.length - 1)
                            tableColumns[i].setWidth(tableWidth - cumulative - 5);
                        cumulative += tableColumns[i].getWidth();
                    }
                }
            });
        }

        table.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                fireValueChanged(e.item.getData(), false);
            }
        });

        // double-click handler for asset rows
        int assetColIdx = -1;
        for (int i = 0; i < columnSpecs.size(); i++) {
            if (WorkflowAssetEditor.TYPE_ASSET.equals(columnSpecs.get(i).type))
                assetColIdx = i;
        }
        if (assetColIdx != -1) {
            final int colIdx = assetColIdx;
            addValueChangeListener(new ValueChangeListener() {
                public void propertyValueChanged(Object newValue) {
                    if (newValue instanceof DefaultRowImpl) {
                        // is the case for double-click
                        DefaultRowImpl row = (DefaultRowImpl) newValue;
                        String name = row.getColumnValues()[colIdx];
                        String ver = "0";
                        if (columnSpecs.size() > colIdx + 1
                                && ("ProcessVersion".equals(columnSpecs.get(colIdx + 1).source)
                                        || "AssetVersion".equals(columnSpecs.get(colIdx + 1).source)))
                            ver = row.getColumnValues()[colIdx + 1];
                        if (name != null && ver != null) {
                            AssetVersionSpec spec = new AssetVersionSpec(name, ver);
                            AssetLocator.Type type = AssetLocator.Type.Asset;
                            if ("Process".equals(columnSpecs.get(colIdx).source))
                                type = AssetLocator.Type.Process;
                            else if (RuleSetVO.TASK.equals(columnSpecs.get(colIdx).source))
                                type = AssetLocator.Type.TaskTemplate;
                            AssetLocator locator = new AssetLocator(getElement(), type);
                            locator.openAsset(spec);
                        }
                    }
                }
            });
        }

        return table;
    }

    private TableViewer createTableViewer(Table table) {
        TableViewer tableViewer = new TableViewer(table);
        tableViewer.setUseHashlookup(true);

        tableViewer.setColumnProperties(columnProps);

        if (!isReadOnly()) {
            CellEditor[] cellEditors = new CellEditor[columnSpecs.size()];
            for (int i = 0; i < columnSpecs.size(); i++) {
                ColumnSpec colSpec = columnSpecs.get(i);
                CellEditor cellEditor = null;
                if (!colSpec.hidden) {
                    if (colSpec.type.equals(TYPE_TEXT)) {
                        if (colSpec.style != 0)
                            cellEditor = new TextCellEditor(table, colSpec.style);
                        else
                            cellEditor = new TextCellEditor(table);
                    } else if (colSpec.type.equals(TYPE_CHECKBOX)) {
                        cellEditor = new CheckboxCellEditor(table);
                    } else if (colSpec.type.equals(TYPE_COMBO)) {
                        int style = SWT.None;
                        if (colSpec.readOnly)
                            style = style | SWT.READ_ONLY;
                        cellEditor = new ComboBoxCellEditor(table, colSpec.options, style);
                        if (!colSpec.readOnly)
                            cellEditor.addListener(
                                    new EditableComboCellEditorListener((ComboBoxCellEditor) cellEditor, i));
                    } else if (colSpec.type.equals(WorkflowAssetEditor.TYPE_ASSET)) {
                        int style = SWT.None;
                        if (colSpec.readOnly)
                            style = style | SWT.READ_ONLY;

                        cellEditor = new TreeComboCellEditor(table, style) {
                            @Override
                            protected void doSetValue(Object value) {
                                Object val = getValue();
                                if (val instanceof CTreeComboItem) {
                                    CTreeComboItem selItem = (CTreeComboItem) getValue();
                                    if (selItem != null) {
                                        super.doSetValue(selItem.getText());
                                        return;
                                    }
                                }

                                if (value instanceof String) {
                                    String strVal = (String) value;
                                    if (strVal.indexOf('/') > 0) {
                                        super.doSetValue(strVal.substring(strVal.indexOf('/') + 1));
                                        return;
                                    }
                                }

                                super.doSetValue(value);
                            }
                        };
                        ((TreeComboCellEditor) cellEditor).setSelectionModifier(new SelectionModifier() {
                            public String modify(CTreeComboItem selection) {
                                if (selection.getParentItem() != null) {
                                    WorkflowPackage pkg = getProject()
                                            .getPackage(selection.getParentItem().getText());
                                    if (pkg == null || pkg.isDefaultPackage())
                                        return selection.getText();
                                    else
                                        return pkg.getName() + "/" + selection.getText();
                                } else {
                                    // ignore packages
                                    return null;
                                }
                            }
                        });

                        // populate the treecombo
                        if (colSpec.source.equals("Process")) {
                            List<WorkflowPackage> packages = getProject().getTopLevelUserVisiblePackages();
                            for (WorkflowPackage pkg : packages) {
                                CTreeComboItem packageItem = ((TreeComboCellEditor) cellEditor)
                                        .addItem(pkg.getName());
                                packageItem.setText(pkg.getName());
                                packageItem.setImage(pkg.getIconImage());
                                for (WorkflowProcess process : pkg.getProcesses()) {
                                    CTreeComboItem processItem = new CTreeComboItem(packageItem, SWT.NONE);
                                    processItem.setText(process.getLabel());
                                    processItem.setImage(process.getIconImage());
                                }
                            }
                        } else {
                            List<WorkflowAsset> assets = getProject()
                                    .getAssetList(Arrays.asList(colSpec.assetTypes));
                            Map<String, CTreeComboItem> packageItems = new HashMap<String, CTreeComboItem>();
                            for (WorkflowAsset asset : assets) {
                                String pkgName = asset.getPackage().getName();
                                CTreeComboItem packageItem = packageItems.get(pkgName);
                                if (packageItem == null) {
                                    packageItem = ((TreeComboCellEditor) cellEditor).addItem(pkgName);
                                    packageItem.setImage(asset.getPackage().getIconImage());
                                    packageItems.put(pkgName, packageItem);
                                }
                                CTreeComboItem assetItem = new CTreeComboItem(packageItem, SWT.NONE);
                                assetItem.setText(asset.getLabel());
                                assetItem.setImage(asset.getIconImage());
                            }
                        }
                    }
                    if (colSpec.listener != null)
                        cellEditor.addListener(colSpec.listener);
                    cellEditors[i] = cellEditor;
                }
            }
            tableViewer.setCellEditors(cellEditors);
            tableViewer.setCellModifier(cellModifier);
        }

        return tableViewer;
    }

    private void createButtons(Composite parent) {
        buttonComposite = new Composite(parent, SWT.NULL);
        GridLayout gl = new GridLayout();
        gl.numColumns = horizontalSpan == 4 ? 2 : 1;
        buttonComposite.setLayout(gl);

        // add
        addButton = new Button(buttonComposite, SWT.PUSH | SWT.CENTER);
        addButton.setText("Add");
        GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
        gridData.widthHint = 60;
        addButton.setLayoutData(gridData);
        addButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                Object row = modelUpdater.create();
                tableViewer.add(row);
                tableValue.add(row);
                modelUpdater.updateModelValue(getTableValue());
                fireValueChanged(tableValue);
            }
        });

        // delete
        deleteButton = new Button(buttonComposite, SWT.PUSH | SWT.CENTER);
        deleteButton.setText("Delete");
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
        gridData.widthHint = 60;
        deleteButton.setLayoutData(gridData);
        deleteButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                Object row = ((IStructuredSelection) tableViewer.getSelection()).getFirstElement();
                if (row != null) {
                    tableViewer.remove(row);
                    tableValue.remove(row);
                    modelUpdater.updateModelValue(getTableValue());
                    fireValueChanged(tableValue);
                }
            }
        });
    }

    ColumnSpec getDummyColumn() {
        // add a dummy column to work around Eclipse bug 43910
        ColumnSpec dummyCol = new ColumnSpec("TEXT", "", "dummy");
        dummyCol.width = 0;
        dummyCol.readOnly = true;
        dummyCol.resizable = false;
        return dummyCol;
    }

    /**
     * Default column provider populates cells based on delimited attribute
     * values
     */
    public class DefaultContentProvider implements IStructuredContentProvider {
        public Object[] getElements(Object inputElement) {
            List<DefaultRowImpl> rows = (List<DefaultRowImpl>) inputElement;
            return rows.toArray(new DefaultRowImpl[0]);
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

        public void dispose() {
        }
    }

    public class DefaultRowImpl {
        private String[] columnValues;

        public String[] getColumnValues() {
            return columnValues;
        }

        private boolean padded;

        public boolean isPadded() {
            return padded;
        }

        public void setPadded(boolean padded) {
            this.padded = padded;
        }

        public DefaultRowImpl() {
            this(false);
        }

        public DefaultRowImpl(boolean padded) {
            this.padded = padded;
            String[] colVals = null;

            if (padded) {
                // add a value for the dummy column
                colVals = new String[columnSpecs.size() + 1];
                for (int i = 0; i < columnSpecs.size(); i++) {
                    String defaultVal = columnSpecs.get(i).defaultValue;
                    if (i == 1)
                        colVals[i] = defaultVal == null ? "New Item" : defaultVal;
                    else
                        colVals[i] = defaultVal == null ? "" : defaultVal;
                }
            } else {
                colVals = new String[columnSpecs.size()];
                for (int i = 0; i < columnSpecs.size(); i++) {
                    String defaultVal = columnSpecs.get(i).defaultValue;
                    if (i == 0)
                        colVals[i] = defaultVal == null ? "New Item" : defaultVal;
                    else
                        colVals[i] = defaultVal == null ? "" : defaultVal;
                }
            }

            this.columnValues = colVals;
        }

        public DefaultRowImpl(String[] columnValues) {
            this(columnValues, false);
        }

        public DefaultRowImpl(String[] columnValues, boolean padded) {
            this.padded = padded;
            String[] colVals = null;

            if (padded) {
                // add a value for the dummy column
                colVals = new String[columnValues.length + 1];
                colVals[0] = "";
                for (int i = 0; i < columnValues.length; i++)
                    colVals[i + 1] = columnValues[i];
            } else {
                colVals = new String[columnValues.length];
                for (int i = 0; i < columnValues.length; i++)
                    colVals[i] = columnValues[i];
            }

            this.columnValues = colVals;
        }

        public Object getColumnValue(int col) {
            String colType = columnSpecs.get(col).type;
            if (col > columnValues.length - 1) {
                if (colType.equals(TYPE_CHECKBOX))
                    return new Boolean(false);
                else if (colType.equals(TYPE_COMBO))
                    return new Integer(-1);
                else
                    return "";
            }

            String strVal = columnValues[col];
            if (colType.equals(TYPE_CHECKBOX)) {
                return new Boolean(strVal);
            }
            if (colType.equals(TYPE_COMBO)) {
                String[] options = columnSpecs.get(col).options;
                for (int i = 0; i < options.length; i++) {
                    if (options[i].equals(strVal))
                        return new Integer(i);
                }
                return new Integer(-1);
            } else {
                return strVal;
            }
        }

        public void setColumnValue(int col, Object value) {
            if (col > columnValues.length - 1) {
                String[] newColVals = new String[columnValues.length + 1];
                for (int i = 0; i < columnValues.length; i++)
                    newColVals[i] = columnValues[i];
                newColVals[columnValues.length] = "";
                columnValues = newColVals;
            }

            String colType = columnSpecs.get(col).type;
            if (colType.equals(TYPE_CHECKBOX)) {
                columnValues[col] = value.toString();
            } else if (colType.equals(TYPE_COMBO)) {
                columnValues[col] = columnSpecs.get(col).options[((Integer) value).intValue()];
            } else if (colType.equals(WorkflowAssetEditor.TYPE_ASSET)) {
                if (value instanceof String) {
                    // ** set asset
                    AssetVersionSpec spec = AssetVersionSpec.parse((String) value);
                    if (spec.getPackageName() != null && getProject().checkRequiredVersion(5, 5))
                        columnValues[col] = spec.getPackageName() + "/" + spec.getName();
                    else
                        columnValues[col] = spec.getName();

                    if (columnSpecs.size() > col + 1 && ("ProcessVersion".equals(columnSpecs.get(col + 1).source)
                            || "AssetVersion".equals(columnSpecs.get(col + 1).source))) {
                        // set the version column
                        String versionAttr = null;
                        if (getProject().checkRequiredVersion(5, 5)) {
                            boolean wasTyped = spec.getVersion().equals("0") || spec.getVersion().startsWith("[");
                            if (MdwPlugin.getSettings().isInferSmartSubprocVersionSpec() && !wasTyped) {
                                try {
                                    versionAttr = AssetVersionSpec.getDefaultSmartVersionSpec(spec.getVersion());
                                } catch (NumberFormatException ex) {
                                    versionAttr = spec.getVersion();
                                }
                            } else {
                                versionAttr = spec.getVersion();
                            }
                        } else {
                            versionAttr = spec.getVersion(); // pre-5.5
                                                             // compatibility
                        }
                        columnValues[col + 1] = versionAttr;
                    }
                } else if (value instanceof CTreeComboItem) {
                    // can happen but ignored because above is triggered by loss
                    // of focus
                }
            } else {
                if (value == null)
                    columnValues[col] = "";
                else
                    columnValues[col] = ((String) value).trim();
            }

            tableViewer.update(this, null);
        }
    }

    public interface TableModelUpdater {
        /**
         * Creates a new row instance
         * 
         * @return the newly-created row object
         */
        public Object create();

        /**
         * Saves the tableValue back to the model
         */
        public void updateModelValue(List tableValue);
    }

    public class DefaultModelUpdater implements TableModelUpdater {
        public Object create() {
            return new DefaultRowImpl(true);
        }

        public void updateModelValue(List tableValue) {
            String serialized = serialize(tableValue);
            if (getMbengNode() != null)
                getElement().setAttribute(getMbengNode().getAttribute("NAME"), serialized);
            else if (getAttributeName() != null)
                getElement().setAttribute(getAttributeName(), serialized);
        }

        protected String serialize(List tableValue) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < tableValue.size(); i++) {
                if (i > 0)
                    sb.append(rowDelimiter);
                DefaultRowImpl row = (DefaultRowImpl) tableValue.get(i);
                for (int j = 0; j < row.columnValues.length; j++) {
                    if (row.isPadded()) {
                        if (j == 0)
                            continue; // ignore dummy column
                        if (j > 1)
                            sb.append(columnDelimiter);
                    } else {
                        if (j > 0)
                            sb.append(columnDelimiter);
                    }
                    if (row.columnValues[j] != null)
                        sb.append(StringHelper.escapeWithBackslash(row.columnValues[j], ",;"));
                }
            }
            return sb.toString();
        }
    }

    public class DefaultLabelProvider extends LabelProvider implements ITableLabelProvider {
        private Map<ImageDescriptor, Image> imageCache = new HashMap<ImageDescriptor, Image>();

        public Image getColumnImage(Object element, int columnIndex) {
            DefaultRowImpl row = (DefaultRowImpl) element;
            ColumnSpec colspec = columnSpecs.get(columnIndex);
            if (colspec.type.equals(TYPE_CHECKBOX)) {
                ImageDescriptor descriptor = null;
                Boolean value = (Boolean) row.getColumnValue(columnIndex);
                if (value.booleanValue()) {
                    descriptor = MdwPlugin.getImageDescriptor("icons/checked.gif");
                } else {
                    descriptor = MdwPlugin.getImageDescriptor("icons/unchecked.gif");
                }
                Image image = (Image) imageCache.get(descriptor);
                if (image == null) {
                    image = descriptor.createImage();
                    imageCache.put(descriptor, image);
                }
                return image;
            } else if (colspec.type.equals(WorkflowAssetEditor.TYPE_ASSET)) {
                if (!"".equals(getColumnText(element, columnIndex))) {
                    ImageDescriptor descriptor = MdwPlugin.getImageDescriptor(
                            "Process".equals(colspec.source) ? "icons/process.gif" : "icons/doc.gif");
                    Image image = (Image) imageCache.get(descriptor);
                    if (image == null) {
                        image = descriptor.createImage();
                        imageCache.put(descriptor, image);
                    }
                    return image;
                }
                return null;
            } else {
                return null;
            }
        }

        public String getColumnText(Object element, int columnIndex) {
            DefaultRowImpl row = (DefaultRowImpl) element;
            ColumnSpec colspec = columnSpecs.get(columnIndex);
            if (colspec.type.equals(TYPE_CHECKBOX)) {
                return null;
            } else if (colspec.type.equals(TYPE_COMBO)) {
                int optionIdx = (Integer) row.getColumnValue(columnIndex);
                if (colspec.readOnly || optionIdx >= 0) {
                    return colspec.options[optionIdx];
                } else {
                    // non-readonly and option not present
                    return row.columnValues[columnIndex];
                }
            } else if (colspec.type.equals(WorkflowAssetEditor.TYPE_ASSET)) {
                // ** display asset
                String stringVal = (String) row.getColumnValue(columnIndex);
                // can happen that value was set with version due to friendly
                // display
                AssetVersionSpec spec = AssetVersionSpec.parse(stringVal);
                if (spec.getVersion() != null)
                    stringVal = (spec.getQualifiedName()); // strip off version

                if (stringVal != null && stringVal.length() > 0) {
                    if (row.columnValues.length > columnIndex + 1
                            && ("ProcessVersion".equals(columnSpecs.get(columnIndex + 1).source)
                                    || "AssetVersion".equals(columnSpecs.get(columnIndex + 1).source))) {
                        stringVal += " v" + row.columnValues[columnIndex + 1];
                    } else {
                        stringVal += " v0";
                    }

                    AssetLocator.Type type = AssetLocator.Type.Asset;
                    if ("Process".equals(colspec.source))
                        type = AssetLocator.Type.Process;
                    else if (RuleSetVO.TASK.equals(colspec.source))
                        type = AssetLocator.Type.TaskTemplate;
                    AssetLocator assetLocator = new AssetLocator(getElement(), type);
                    WorkflowElement workflowAsset = assetLocator.assetFromAttr(stringVal);
                    if (workflowAsset == null)
                        return stringVal;
                    TreeComboCellEditor tcce = (TreeComboCellEditor) tableViewer.getCellEditors()[columnIndex];
                    CTreeCombo treeCombo = tcce.getTreeCombo();
                    if (workflowAsset != null) {
                        // preselect in tree combo
                        CTreeComboItem selItem = null;
                        for (CTreeComboItem pkgItem : treeCombo.getItems()) {
                            if (pkgItem.getText().equals(workflowAsset.getPackage().getName())) {
                                for (CTreeComboItem assetItem : pkgItem.getItems()) {
                                    if (assetItem.getText().equals(workflowAsset.getName())
                                            || assetItem.getText().equals(workflowAsset.getLabel())) {
                                        selItem = assetItem;
                                        break;
                                    }
                                }
                                break;
                            }
                        }
                        if (selItem == null) {
                            return workflowAsset.getLabel();
                        } else {
                            treeCombo.select(selItem);
                            return selItem.getText();
                        }
                    }
                }

                if (stringVal != null && stringVal.indexOf('/') > 0)
                    return stringVal.substring(stringVal.indexOf('/') + 1);
                else
                    return stringVal;
            } else {
                return (String) row.getColumnValue(columnIndex);
            }
        }
    }

    public class DefaultCellModifier implements ICellModifier {
        public boolean canModify(Object element, String property) {
            if (!editable)
                return false;
            ColumnSpec colSpec = columnSpecs.get(getColumnIndex(property));
            return colSpec.type.equals(TYPE_COMBO) || !colSpec.readOnly;
        }

        public Object getValue(Object element, String property) {
            DefaultRowImpl row = (DefaultRowImpl) element;
            int colIndex = getColumnIndex(property);
            return row.getColumnValue(colIndex);
        }

        public void modify(Object element, String property, Object value) {
            TableItem item = (TableItem) element;
            DefaultRowImpl row = (DefaultRowImpl) item.getData();
            int colIndex = getColumnIndex(property);
            row.setColumnValue(colIndex, value);
            modelUpdater.updateModelValue(getTableValue());
            fireValueChanged(tableValue);
        }

        protected int getColumnIndex(String property) {
            for (int i = 0; i < columnSpecs.size(); i++) {
                ColumnSpec colSpec = columnSpecs.get(i);
                if (colSpec.property.equals(property)) {
                    return i;
                }
            }
            return -1;
        }
    }

    class RowHeightListener implements Listener {
        int height = ColumnSpec.DEFAULT_ROW_HEIGHT;

        public RowHeightListener(int height) {
            this.height = height;
        }

        public void handleEvent(Event event) {
            if (event.type == SWT.MeasureItem) {
                event.height = this.height;
            }
        }
    }

    /**
     * Workaround for Eclipse bug:
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=67069
     */
    class EditableComboCellEditorListener implements ICellEditorListener {
        private ComboBoxCellEditor editor;
        int colSpecIndex;

        public EditableComboCellEditorListener(ComboBoxCellEditor editor, int colSpecIndex) {
            this.editor = editor;
            this.colSpecIndex = colSpecIndex;
        }

        public void applyEditorValue() {
            String newText = ((CCombo) editor.getControl()).getText();

            ColumnSpec colSpec = columnSpecs.get(colSpecIndex);
            for (int i = 0; i < colSpec.options.length; i++) {
                if (colSpec.options[i].equals(newText))
                    return;
            }
            // not found, so add the string to the list and select it
            String[] items = editor.getItems();
            String[] options = new String[items.length + 1];
            for (int i = 0; i < items.length; i++)
                options[i] = items[i];
            options[options.length - 1] = newText;
            editor.setItems(options);
            editor.setValue(options.length - 1);
            colSpec.options = options;
        }

        public void cancelEditor() {
        }

        public void editorValueChanged(boolean oldValidState, boolean newValidState) {
        }
    }
}