// $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.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Observable; import javax.faces.model.SelectItem; import org.apache.log4j.Logger; /** * Base class for beans the back UISelectOne and UISelectMany JSF components. * Maintains three "views" of each selection: * <dl> * <dt>key</dt> * <dd>A string that represents the a selection. Becomes the SelectItem.value * and the "value" attribute of a rendered "option" HTML element.</dd> * <dt>label</dt> * <dd>Human-readable label for a SelectItem.label object. Becomes the text of a * rendered "option" HTML element.</dd> * <dt>selection</dt> * <dd>The domain object represented by the SelectItem. (This is the object that * a JSF converter would otherwise provide.) * </dl> * Generates and provides a list of SelectItems that can be bound to a child * UISelectItem component's 'value' attribute. * * @motivation Guaranteed type consistency between set/get methods and * getSelectItems() method. * @motivation JSF converters appear to serialize the objects in from its * associated SelectItems. This is bad if the object is not * serializable or very large (e.g., a data model entity). This is * also bad if the object is a persisted Hibernate entity, which may * become invalid if the Hibernate session is closed, and the entity * has uninitialized lazy persistent sets that need to be acc * @motivation JSF converter definitions are a pain in the ass * @author <a mailto="andrew_tolopko@hms.harvard.edu">Andrew Tolopko</a> * @author <a mailto="john_sullivan@hms.harvard.edu">John Sullivan</a> */ public abstract class UISelectBean<T> extends Observable { // static members private static Logger log = Logger.getLogger(UISelectBean.class); public static final String EMPTY_KEY = ""; public static final String DEFAULT_EMPTY_LABEL = "<none>"; // instance data members protected boolean _isEmptyValueAllowed; List<SelectItem> _selectItems; protected Map<String,T> _key2Obj; // public constructors and methods public UISelectBean(Collection<T> objects, boolean isEmptyValueAllowed) { _isEmptyValueAllowed = isEmptyValueAllowed; setDomain(objects); } /** * Set the domain of items that can be selected from. Allows items to be * changed after UISelectBean is instantiated. */ public void setDomain(Collection<T> objects) { _selectItems = new ArrayList<SelectItem>(); _key2Obj = new HashMap<String,T>(); if (_isEmptyValueAllowed) { _selectItems.add(new SelectItem(EMPTY_KEY, getEmptyLabel())); _key2Obj.put(EMPTY_KEY, null); } for (T t : objects) { String key = _makeKey(t); _selectItems.add(new SelectItem(key, _makeLabel(t))); _key2Obj.put(key, t); } } /** * called by JSF UISelect component */ public List<SelectItem> getSelectItems() { return _selectItems; } /** * @motivation JSF EL does not have a size or length operator * @return the total number of SelectItems (<i>not</i> the number of * user-selected items) */ public int getSize() { return _selectItems.size(); } // private methods final protected String _makeKey(T t) { if (t == null) { return EMPTY_KEY; } String key = makeKey(t); if (key == null) { // misbehaved subclass! key = EMPTY_KEY; } return key; } final protected String _makeLabel(T t) { if (t == null) { return getEmptyLabel(); } String label = makeLabel(t); if (label == null) { // misbehaved subclass! label = getEmptyLabel(); } return label; } // protected methods (subclasses may override) /** * Override in subclass to provide a different key representation of domain objects (other than t.hasCode()). * @param t the domain object for which a key should be returned; will never be null * @return key representing the domain object (stringified hashcode) */ protected String makeKey(T t) { return Integer.toString(t.hashCode()); } /** * Override in subclass to provide a different label representation for domain objects (other than t.toString()). * @param t the domain object for which a label should be returned; will never be null * @return human-readable label representing the domain object (t.toString()) */ protected String makeLabel(T t) { return t.toString(); } protected String getEmptyLabel() { return DEFAULT_EMPTY_LABEL; } }