// $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.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import javax.faces.convert.Converter; import javax.faces.model.SelectItem; import org.apache.log4j.Logger; import edu.harvard.med.screensaver.db.Criterion; import edu.harvard.med.screensaver.db.SortDirection; import edu.harvard.med.screensaver.ui.arch.datatable.ColumnVisibilityChangedEvent; import edu.harvard.med.screensaver.ui.arch.util.converter.NoOpStringConverter; /** * @param R the row type * @param T the column data type */ public abstract class TableColumn<R,T> extends Observable implements Observer { // static members private static Logger log = Logger.getLogger(TableColumn.class); public static final String UNGROUPED = ""; // instance data members private Map<SortDirection,Comparator<R>> _comparators = new HashMap<SortDirection,Comparator<R>>(); private ColumnType _columnType; private String _name; private String _description; private String _group; private boolean _isAdministrative; private boolean _isVisible; private boolean _isNumeric; private boolean _isMultiValued; private List<Criterion<T>> _criteria = new ArrayList<Criterion<T>>(); private Converter _converter = NoOpStringConverter.getInstance(); private List<SelectItem> _selectItems = null; // public constructors and methods public TableColumn(String name, String description, ColumnType columnType, String group) { this(name, description, columnType, group, false); } public TableColumn(String name, String description, ColumnType columnType, String group, boolean isMultiValued) { _name = name; _description = description; _columnType = columnType; _group = group; _isNumeric = columnType.isNumeric(); _converter = columnType.getConverter(); _isVisible = true; _isMultiValued = isMultiValued; addCriterion(new Criterion<T>(_columnType.getDefaultOperator(), null)); } public ColumnType getColumnType() { return _columnType; } /** * Get the name of the column. * * @return the name of the column */ public String getName() { return _name; } /** * Get the descriptive text for the column. Used for mouse-over quick-help. * * @return the descriptive text for the column */ public String getDescription() { return _description; } public Converter getConverter() { return _converter; } protected void setConverter(Converter converter) { _converter = converter; } /** * Get a comparator for sorting the column for the specified sortDirection and * that is null-safe. * * @return a comparator for sorting the column */ final public Comparator<R> getComparator(final SortDirection sortDirection) { if (_comparators.get(sortDirection) == null) { _comparators.put(sortDirection, new Comparator<R>() { public int compare(R o1, R o2) { int result = getAscendingComparator().compare(o1, o2); if (sortDirection.equals(SortDirection.DESCENDING)) { result *= -1; } return result; } }); } return _comparators.get(sortDirection); } /** * Get a comparator for sorting the column that sorts its values in ascending * order. It is acceptable if the implementation instantiates a new Comparator * on each call, since TableColumn will only call this method once for a given * instance. * * @return a comparator for sorting the column */ protected Comparator<R> getAscendingComparator() { return new Comparator<R>() { @SuppressWarnings("unchecked") public int compare(R o1, R o2) { Object v1 = getCellValue(o1); Object v2 = getCellValue(o2); if (v1 == null) { if (v2 == null) { return 0; } return -1; } if (v2 == null) { return 1; } if (v1 instanceof Comparable) { return ((Comparable) v1).compareTo(v2); } return v1.toString() .compareTo(v2.toString()); } }; } public List<Criterion<T>> getCriteria() { return _criteria; } public Criterion<T> getCriterion() { if (_criteria.size() > 0) { return _criteria.get(0); } return null; } public void update(Observable o, Object arg) { // notify observers that a criterion in this column has changed setChanged(); notifyObservers(o); } /** * @return the TableColumn, for chaining additional {@link #addCriterion(Criterion)} call */ public TableColumn<R,T> addCriterion(Criterion<T> criterion) { _criteria.add(criterion); criterion.addObserver(this); // notify observers that a criterion has been added to this column setChanged(); notifyObservers(criterion); return this; } public void removeCriterion(Criterion<T> criterion) { criterion.deleteObserver(this); boolean removed = _criteria.remove(criterion); if (removed) { // notify observers that a criterion has been removed from this column setChanged(); notifyObservers(criterion); // TODO: should indicate that it was removed } } /** * Remove all filtering criteria from this column. * @return the TableColumn, for chaining a {@link #addCriterion(Criterion)} call */ public TableColumn<R,T> clearCriteria() { for (Iterator<Criterion<T>> iter = _criteria.iterator(); iter.hasNext();) { // note: we can't just call removeCriterion(), as this would cause a // ConcurrentModificationException on _criteria.remove() Criterion<T> criterion = (Criterion<T>) iter.next(); criterion.deleteObserver(this); iter.remove(); setChanged(); notifyObservers(criterion); } return this; } /** * Replace all existing filtering criteria with the single, default criterion. * @return the cleared Criterion, for chaining Criterion mutator method calls */ public Criterion<T> resetCriteria() { clearCriteria(); addCriterion(new Criterion<T>(getColumnType().getDefaultOperator(), null)); return getCriterion(); } /** * @return true if has at least 1 defined criterion */ public boolean hasCriteria() { for (Criterion criterion : _criteria) { if (!criterion.isUndefined()) { return true; } } return false; } /** * Get the value to be displayed for the current column and cell. * * @param row the row displayed in the current cell (the row index) * @return the value to be displayed for the current cell */ abstract public T getCellValue(R row); /** * Set the new value of the row for the current column and cell. * * @param row the row displayed in the current cell (the row index) * @param value the new value */ public void setCellValue(R row, T value) {} /** * Get whether this table column is editable by the user. If it is, you must * implement {@link #setCellValue(Object, Object)}, if submitted values are to * update your data model. Also, {@link #isCommandLink()} should return false * if this method returns true. */ public boolean isEditable() { return false; } final public void setVisible(boolean isVisible) { if (isVisible != _isVisible) { _isVisible = isVisible; setChanged(); ColumnVisibilityChangedEvent event = new ColumnVisibilityChangedEvent(); if (isVisible) { event.added(this); } else { event.removed(this); } notifyObservers(event); } } final public boolean isVisible() { return _isVisible; } /** * Return true whenever the cell values for the column with the specified name * should be a hyperlink. * * @return true whenever the cell values for the column should be a hyperlink. */ public boolean isCommandLink() { return false; } /** * Perform the action for clicking on the current cell. Return the navigation * rule to go along with the action for clicking on the current cell. This * method is only called when {@link #isCommandLink()} is true. * * @param row the row displayed in the current cell (the row index) * @return the navigation rule to go along with the action for clicking on the * current cell */ public Object cellAction(R row) { return null; } /** * JavaBean property wrapper for {@link #isCommandLink()}. * * @motivation isCommandLink() translates to just 'commandLink' JavaBean * property name, which is not as nice as 'isCommandLink' */ final public boolean getIsCommandLink() { return isCommandLink(); } public boolean isNumeric() { return _isNumeric; } // @Override // public boolean equals(Object o) // { // if (this == o) { // return true; // } // if (o instanceof TableColumn) { // if (getName().equals(((TableColumn) o).getName())) { // return true; // } // } // return false; // } // // @Override // public int hashCode() // { // return getName().hashCode(); // } public boolean isMultiValued() { return _isMultiValued; } protected void setMultiValued(boolean multiValued) { _isMultiValued = multiValued; } @Override public String toString() { return "TableColumn:" + getName(); } public String getGroup() { return _group; } public boolean isAdministrative() { return _isAdministrative; } public void setAdministrative(boolean isAdministrative) { _isAdministrative = isAdministrative; } public boolean isSortableSearchable() { return true; } @Override public boolean equals(Object other) { if (other == null) { return false; } if (!(other instanceof TableColumn)) { return false; } return _name.equals(((TableColumn) other).getName()); } @Override public int hashCode() { return _name.hashCode(); } /** * Return a list of valid options to be used on input * * @return a list of select items. */ public List<SelectItem> getSelectItems() { return _selectItems; } public void setSelectItems(List<SelectItem> selectItems) { _selectItems = selectItems; } }