edu.harvard.med.screensaver.ui.arch.datatable.column.TableColumnManager.java Source code

Java tutorial

Introduction

Here is the source code for edu.harvard.med.screensaver.ui.arch.datatable.column.TableColumnManager.java

Source

// $HeadURL$
// $Id$
//
// Copyright  2006, 2010, 2011, 2012 by the President and Fellows of Harvard College.
//
// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.

package edu.harvard.med.screensaver.ui.arch.datatable.column;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import javax.faces.model.ListDataModel;
import javax.faces.model.SelectItem;

import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.apache.myfaces.custom.tree2.TreeModel;
import org.apache.myfaces.custom.tree2.TreeModelBase;
import org.apache.myfaces.custom.tree2.TreeNode;
import org.apache.myfaces.custom.tree2.TreeNodeBase;

import edu.harvard.med.screensaver.ScreensaverConstants;
import edu.harvard.med.screensaver.db.Criterion;
import edu.harvard.med.screensaver.db.SortDirection;
import edu.harvard.med.screensaver.model.users.ScreensaverUserRole;
import edu.harvard.med.screensaver.policy.CurrentScreensaverUser;
import edu.harvard.med.screensaver.ui.arch.datatable.ColumnVisibilityChangedEvent;
import edu.harvard.med.screensaver.ui.arch.datatable.SortChangedEvent;
import edu.harvard.med.screensaver.ui.arch.datatable.SortDirectionSelector;
import edu.harvard.med.screensaver.ui.arch.util.UISelectOneBean;
import edu.harvard.med.screensaver.ui.arch.view.aspects.UICommand;

/**
 * Notifies observers when set of available columns are changed, either from
 * setColumns() being called, or a column's setVisible() being called.
 *
 * @author drew
 */
public class TableColumnManager<R> extends Observable implements Observer {
    public static final String GROUP_NODE_DELIMITER = "::";

    // static members

    private static Logger log = Logger.getLogger(TableColumnManager.class);

    // instance data

    private CurrentScreensaverUser _currentScreensaverUser;
    private List<TableColumn<R, ?>> _columns = new ArrayList<TableColumn<R, ?>>();
    private List<TableColumn<R, ?>> _visibleColumns = new ArrayList<TableColumn<R, ?>>();
    private List<TableColumn<R, ?>> _sortableSearchableColumns = new ArrayList<TableColumn<R, ?>>();
    private ListDataModel _columnModel; // contains visible columns only
    private TreeModel _columnsSelectionTree;
    private UISelectOneBean<SortDirection> _sortDirectionSelector;
    private UISelectOneBean<TableColumn<R, ?>> _sortColumnSelector;
    private Map<TableColumn<R, ?>, List<TableColumn<R, ?>>> _compoundSortColumnsMap = new HashMap<TableColumn<R, ?>, List<TableColumn<R, ?>>>();
    private Map<String, TableColumn<R, ?>> _name2Column = new HashMap<String, TableColumn<R, ?>>();

    // For ReorderListWidget: List and arrays for the pick list
    private List<SelectItem> allItemsLeft;
    private List<SelectItem> allItemsRight;
    private List<SelectItem> defaultItemsRight; // the default columns displayed at the start
    private List<SelectItem> defaultItemsLeft; // the default columns not displayed at the start
    private String[] selectedItemsLeft = {};
    private String[] selectedItemsRight = {};
    private boolean _useReorderListWidget = false;
    // ReorderListWidget: End

    /**
     * @param columns
     * @param currentScreensaverUser
     * @param useReorderListWidget if true use the dual list based column selector with the ability to re-orde
     *        (do NOT use the tree based column selector)
     */
    public TableColumnManager(List<? extends TableColumn<R, ?>> columns,
            CurrentScreensaverUser currentScreensaverUser, boolean useReorderListWidget) {

        // For ReorderListWidget: Initialize the lists
        _useReorderListWidget = useReorderListWidget;
        if (isUseReorderListWidget()) {
            allItemsLeft = new ArrayList<SelectItem>();
            allItemsRight = new ArrayList<SelectItem>();
            defaultItemsRight = new ArrayList<SelectItem>();
            defaultItemsLeft = new ArrayList<SelectItem>();

            for (int i = 0; i < columns.size(); i++) {
                if (columns.get(i).isVisible()) {
                    allItemsRight.add(new SelectItem(columns.get(i).getName(), columns.get(i).getName()));
                    defaultItemsRight.add(new SelectItem(columns.get(i).getName(), columns.get(i).getName()));
                } else {
                    allItemsLeft.add(new SelectItem(columns.get(i).getName(), columns.get(i).getName()));
                    defaultItemsLeft.add(new SelectItem(columns.get(i).getName(), columns.get(i).getName()));
                }

            }
        }
        // For ReorderListWidget: End

        _currentScreensaverUser = currentScreensaverUser;
        setColumns(columns);
    }

    /**
     * Get the current sort column.
     *
     * @motivation allow sort column to be set from a drop-down list UI component
     *             (in addition to clicking on table column headers)
     * @return the current sort column
     */
    public TableColumn<R, ?> getSortColumn() {
        return getSortColumnSelector().getSelection();
    }

    public List<TableColumn<R, ?>> getSortColumns() {
        if (getSortColumnSelector().getSelection() == null) {
            return Collections.emptyList();
        }
        List<TableColumn<R, ?>> sortColumns = _compoundSortColumnsMap.get(getSortColumn());
        if (sortColumns == null) {
            sortColumns = new ArrayList<TableColumn<R, ?>>();
            sortColumns.add(getSortColumnSelector().getSelection());
        }
        return sortColumns;
    }

    public void addCompoundSortColumns(List<TableColumn<R, ?>> compoundSortColumns) {
        _compoundSortColumnsMap.put(compoundSortColumns.get(0), compoundSortColumns);
    }

    public void addCompoundSortColumns(TableColumn<R, ?>... compoundSortColumns) {
        addCompoundSortColumns(Arrays.asList(compoundSortColumns));
    }

    public void addAllCompoundSorts(List<List<TableColumn<R, ?>>> allCompoundSorts) {
        for (List<TableColumn<R, ?>> compoundSort : allCompoundSorts) {
            addCompoundSortColumns(compoundSort);
        }
    }

    @SuppressWarnings("unchecked")
    public TreeModel getColumnsTreeModel() {
        if (_columnsSelectionTree == null) {
            TreeNodeBase root = new TreeNodeBase("root", "Columns", false);
            Map<String, TreeNode> groups = new HashMap<String, TreeNode>();
            for (TableColumn<R, ?> column : _columns) {
                if (!isColumnRestricted(column)) {
                    TreeNode groupNode = getOrCreateGroupNode(root, groups, column.getGroup());
                    groupNode.getChildren().add(new SelectableColumnTreeNode<R>(column));
                }
            }
            _columnsSelectionTree = new TreeModelBase(root);
        }
        return _columnsSelectionTree;
    }

    public int getSortColumnIndex() {
        return getSortColumnSelector().getSelectionIndex();
    }

    /**
     * Get the index of the column currently being rendered by JSF.
     */
    public int getCurrentColumnIndex() {
        return getVisibleColumnModel().getRowIndex();
    }

    /**
     * Get the column currently being rendered by JSF.
     */
    @SuppressWarnings("unchecked")
    public TableColumn<R, ?> getCurrentColumn() {
        return (TableColumn<R, ?>) getVisibleColumnModel().getRowData();
    }

    public TableColumn<R, ?> getColumn(String columnName) {
        return _name2Column.get(columnName);
    }

    /**
     * @return the n'th visible column (zero-based)
     */
    public TableColumn<R, ?> getColumn(int i) {
        return getVisibleColumns().get(i);
    }

    /**
     * Set the current sort column.
     *
     * @motivation allow sort column to be set from a drop-down list UI component
     *             (in addition to clicking on table column headers)
     * @param newSortColumn the new current sort column
     */
    public void setSortColumn(TableColumn<R, ?> newSortColumn) {
        if (newSortColumn != null) {
            if (!newSortColumn.equals(getSortColumns().get(0))) {
                getSortColumnSelector().setSelection(newSortColumn);
            }
        }
    }

    /**
     * @motivation for use by dataTable JSF component.
     * @param sortColumnName the name of the new sort column
     */
    public void setSortColumnName(String sortColumnName) {
        if (sortColumnName != null) {
            setSortColumn(getColumn(sortColumnName));
        } else {
            setSortColumn(null);
        }
    }

    /**
     * @motivation for use by dataTable JSF component.
     * @return true the name of the current sort column
     */
    public String getSortColumnName() {
        if (getSortColumn() == null) {
            return null;
        }
        return getSortColumn().getName();
    }

    /**
     * @motivation for use by dataTable JSF component.
     * @param sortAscending true if new sort direction is ascending; false if
     *          descending
     */
    public void setSortAscending(boolean sortAscending) {
        if (sortAscending) {
            setSortDirection(SortDirection.ASCENDING);
        } else {
            setSortDirection(SortDirection.DESCENDING);
        }
    }

    /**
     * @motivation for use by dataTable JSF component.
     * @return true if current sort direction is ascending; false if descending
     */
    public boolean isSortAscending() {
        return getSortDirection().equals(SortDirection.ASCENDING);
    }

    /**
     * Get the current sort direction.
     *
     * @motivation allow sort direction to be set from a drop-down list UI
     *             component (in addition to clicking on table column headers)
     * @return the current sort column name
     */
    public SortDirection getSortDirection() {
        return getSortDirectionSelector().getSelection();
    }

    /**
     * Set the current sort direction.
     *
     * @motivation allow sort direction to be set from a drop-down list UI
     *             component (in addition to clicking on table column headers)
     * @param currentSortDirection the new current sort direction
     */
    public void setSortDirection(SortDirection currentSortDirection) {
        if (!getSortDirection().equals(currentSortDirection)) {
            getSortDirectionSelector().setSelection(currentSortDirection);
        }
    }

    /**
     * Get the JSF column model, containing visible columns.
     *
     * @return the data columns column model
     */
    public ListDataModel getVisibleColumnModel() {
        return _columnModel;
    }

    public void setColumns(List<? extends TableColumn<R, ?>> columns) {
        Set<TableColumn<R, ?>> oldColumns = new HashSet<TableColumn<R, ?>>(_columns);

        _columns.clear();
        _columns.addAll(columns);
        _columnsSelectionTree = null; // force re-create

        _name2Column.clear();
        for (TableColumn<R, ?> column : columns) {
            column.addObserver(this);
            if (_name2Column.containsKey(column.getName())) {
                throw new IllegalArgumentException("column " + column + " has non-unique name");
            }
            _name2Column.put(column.getName(), column);
        }

        updateVisibleColumns(new ColumnVisibilityChangedEvent(CollectionUtils.subtract(columns, oldColumns),
                CollectionUtils.subtract(oldColumns, columns)));
    }

    @SuppressWarnings("unchecked")
    public List<TableColumn<R, ?>> getAllColumns() {
        return _columns;
    }

    public List<TableColumn<R, ?>> getVisibleColumns() {
        return _visibleColumns;
    }

    public List<TableColumn<R, ?>> getSortableSearchableColumns() {
        return _sortableSearchableColumns;
    }

    public void setVisibilityOfColumnsInGroup(String columnGroupName, boolean isVisible) {
        for (TableColumn<R, ?> column : getAllColumns()) {
            if (column.getGroup().equals(columnGroupName)) {
                column.setVisible(isVisible);
            }
        }
    }

    public UISelectOneBean<TableColumn<R, ?>> getSortColumnSelector() {
        if (_sortColumnSelector == null) {
            _sortColumnSelector = new UISelectOneBean<TableColumn<R, ?>>(getSortableSearchableColumns()) {
                @Override
                protected String makeLabel(TableColumn<R, ?> t) {
                    return t.getName();
                }
            };
            _sortColumnSelector.addObserver(this);
        }
        return _sortColumnSelector;
    }

    public UISelectOneBean<SortDirection> getSortDirectionSelector() {
        if (_sortDirectionSelector == null) {
            _sortDirectionSelector = new SortDirectionSelector();
            _sortDirectionSelector.addObserver(this);
        }
        return _sortDirectionSelector;
    }

    // JSF application methods

    @UICommand
    public String updateColumnSelections() {
        if (isUseReorderListWidget()) {
            // For ReorderListWidget: To arrange the columns according to the order specified by user
            List<TableColumn<R, ?>> columns = getAllColumns();
            List<TableColumn<R, ?>> tempColumns = new ArrayList<TableColumn<R, ?>>();

            // Reset all columns to hidden
            for (int j = 0; j < columns.size(); j++) {
                columns.get(j).setVisible(false);
            }
            for (int i = 0; i < allItemsRight.size(); i++) {

                for (int j = 0; j < columns.size(); j++) {

                    // Add visible columns to the front of list. Will be used in updateVisibleColumns
                    // to obtain all visible columns
                    if (allItemsRight.get(i).getValue().equals(columns.get(j).getName())) {
                        columns.get(j).setVisible(true);
                        tempColumns.add(columns.get(j));

                        break;
                    }
                }
            }

            // add remaining (hidden) columns to the back
            for (int j = 0; j < columns.size(); j++) {
                if (!columns.get(j).isVisible()) {
                    tempColumns.add(columns.get(j));
                }
            }
            _columns = tempColumns;

            updateVisibleColumns(new ColumnVisibilityChangedEvent());
        }

        return ScreensaverConstants.REDISPLAY_PAGE_ACTION_RESULT;
    }

    @UICommand
    public String selectAllColumns() {
        // TODO
        return ScreensaverConstants.REDISPLAY_PAGE_ACTION_RESULT;
    }

    @UICommand
    public String unselectAllColumns() {
        // TODO
        return ScreensaverConstants.REDISPLAY_PAGE_ACTION_RESULT;
    }

    // Observer methods

    public void update(Observable o, Object arg) {
        if (o == _sortColumnSelector) {
            setChanged();
            notifyObservers(new SortChangedEvent<R>(getSortColumn()));
        } else if (o == _sortDirectionSelector) {
            setChanged();
            notifyObservers(new SortChangedEvent<R>(getSortDirection()));
        } else if (o instanceof TableColumn) {
            if (arg instanceof ColumnVisibilityChangedEvent) {
                log.debug("TableColumnManager notified of column visibility change: " + o);
                updateVisibleColumns((ColumnVisibilityChangedEvent) arg);
            } else if (arg instanceof Criterion) {
                // column's filtering criteria changed
                setChanged();
                notifyObservers(arg);
            }
        }
    }

    // private methods

    private void updateVisibleColumns(ColumnVisibilityChangedEvent event) {
        // for reorder mode, update should be done even if columnsAdded 
        // and columnsRemoved are unchanged
        if (event.getColumnsAdded().size() > 0 || event.getColumnsRemoved().size() > 0
                || isUseReorderListWidget()) {
            if (log.isDebugEnabled()) {
                log.debug("column selections changed: " + event);
            }

            // rebuild _visibleColumns, maintaining the fixed order of the columns
            // we ignore the event's take on added & removed columns, since we can determine this reliably by inspecting each column
            _visibleColumns.clear();
            _sortableSearchableColumns.clear();
            for (TableColumn<R, ?> column : getAllColumns()) {
                if (column.isVisible() && !isColumnRestricted(column)) {
                    _visibleColumns.add(column);
                    if (column.isSortableSearchable()) {
                        _sortableSearchableColumns.add(column);
                    }
                }
            }

            getSortColumnSelector().setDomain(_sortableSearchableColumns);
            _columnModel = new ListDataModel(_visibleColumns);
            setChanged();
            notifyObservers(event);
        }
    }

    private boolean isColumnRestricted(TableColumn<?, ?> column) {
        if (!column.isAdministrative()) {
            return false;
        }
        if (_currentScreensaverUser != null && _currentScreensaverUser.getScreensaverUser()
                .isUserInRole(ScreensaverUserRole.READ_EVERYTHING_ADMIN)) {
            return false;
        }
        return true;
    }

    private TreeNode getOrCreateGroupNode(TreeNodeBase root, Map<String, TreeNode> groups, String groupPath) {
        TreeNode groupNode = groups.get(groupPath);
        if (groupNode == null) {
            if (groupPath.equals(TableColumn.UNGROUPED)) {
                return root;
            }
            TreeNode parent;
            String groupName;
            int lastPathDelimPos = groupPath.lastIndexOf(GROUP_NODE_DELIMITER);
            if (lastPathDelimPos < 0) {
                // leaf group node
                parent = root;
                groupName = groupPath;
            } else {
                // internal group node
                parent = getOrCreateGroupNode(root, groups, groupPath.substring(0, lastPathDelimPos));
                groupName = groupPath.substring(lastPathDelimPos + GROUP_NODE_DELIMITER.length());
            }
            // TODO: remove special cases here by adding appropriate flag param 
            if (parent.getDescription().contains("Annotations")
                    || parent.getDescription().contains("Data Columns")) {
                groupNode = new SelectableColumnGroupTreeNode(groupName);
            } else {
                groupNode = new TreeNodeBase("group", groupName, false);
            }
            parent.getChildren().add(groupNode);
            groups.put(groupPath, groupNode);
        }
        return groupNode;
    }

    // For ReorderListWidget: Getters and setters

    public boolean isUseReorderListWidget() {
        return _useReorderListWidget;
    }

    public boolean isUseTreeWidget() {
        return !_useReorderListWidget;
    }

    public List<SelectItem> getAllItemsLeft() {
        return allItemsLeft;
    }

    public void setAllItemsLeft(List<SelectItem> allItemsLeft) {
        this.allItemsLeft = allItemsLeft;
    }

    public List<SelectItem> getAllItemsRight() {
        return allItemsRight;
    }

    public void setAllItemsRight(List<SelectItem> allItemsRight) {
        this.allItemsRight = allItemsRight;
    }

    public String[] getSelectedItemsLeft() {
        return selectedItemsLeft;
    }

    public void setSelectedItemsLeft(String[] selectedItemsLeft) {
        this.selectedItemsLeft = selectedItemsLeft;
    }

    public String[] getSelectedItemsRight() {
        return selectedItemsRight;
    }

    public void setSelectedItemsRight(String[] selectedItemsRight) {
        this.selectedItemsRight = selectedItemsRight;
    }

    public String leftToRight() {
        for (int i = 0; i < selectedItemsLeft.length; i++) {
            for (int j = 0; j < allItemsLeft.size(); j++) {
                if (selectedItemsLeft[i].equals(allItemsLeft.get(j).getValue())) {
                    allItemsRight.add(allItemsLeft.get(j));
                    allItemsLeft.remove(j);
                }
            }
        }

        return null;
    }

    public String allLeftToRight() {
        allItemsRight.addAll(allItemsLeft);
        allItemsLeft.clear();
        return null;
    }

    public String rightToLeft() {
        for (int i = 0; i < selectedItemsRight.length; i++) {
            for (int j = 0; j < allItemsRight.size(); j++) {
                if (selectedItemsRight[i].equals(allItemsRight.get(j).getValue())) {
                    allItemsLeft.add(allItemsRight.get(j));
                    allItemsRight.remove(j);
                }
            }
        }

        return null;
    }

    public String allRightToLeft() {
        allItemsLeft.addAll(allItemsRight);
        allItemsRight.clear();
        return null;
    }

    public String moveUp() {

        int j = 0;
        for (int i = 0; i < allItemsRight.size(); i++) {
            if (selectedItemsRight.length <= j)
                break;

            if (selectedItemsRight[j].equals(allItemsRight.get(i).getValue())) {
                j++;
                if (i != 0)
                    allItemsRight.add(i - 1, allItemsRight.remove(i));
            }
        }

        return ScreensaverConstants.REDISPLAY_PAGE_ACTION_RESULT;
    }

    public String moveDown() {

        int j = selectedItemsRight.length - 1;
        for (int i = allItemsRight.size() - 1; i >= 0; i--) {
            if (j < 0)
                break;

            if (selectedItemsRight[j].equals(allItemsRight.get(i).getValue())) {
                j--;
                if (i != allItemsRight.size() - 1)
                    allItemsRight.add(i, allItemsRight.remove(i + 1));
            }
        }

        return ScreensaverConstants.REDISPLAY_PAGE_ACTION_RESULT;
    }

    // JSF UI method to restore columns to the default settings
    public String updateDefaultColumns() {
        if (log.isDebugEnabled()) {
            log.debug("BII: column selections changed back to default");
        }

        // clear the current selection lists
        allItemsRight.clear();
        allItemsLeft.clear();

        // Set both selection list to default settings
        for (int i = 0; i < defaultItemsRight.size(); i++) {
            allItemsRight
                    .add(new SelectItem(defaultItemsRight.get(i).getValue(), defaultItemsRight.get(i).getLabel()));
        }
        for (int i = 0; i < defaultItemsLeft.size(); i++) {
            allItemsLeft
                    .add(new SelectItem(defaultItemsLeft.get(i).getValue(), defaultItemsLeft.get(i).getLabel()));
        }

        updateColumnSelections();

        return ScreensaverConstants.REDISPLAY_PAGE_ACTION_RESULT;
    }
    // For ReorderListWidget: End

}