// SelectionSet.java
import java.util.*;
/**
* a "set" container which notifies listeners of membership changes
* for example as a central container for coordinated selection
* between a group of viewer/listeners.<br>
*
* this class is somewhat similar to javax.swing.DefaultListSelectionModel
* except that its for sets instead of lists. in other words, this class
* makes up for swing's lack of a DefaultSetSelectionModel class.<br>
*
* editing methods all take a "byWhom" parameter taken to be the
* object performing the editing. this value is used to catch
* illegal states when more than one object attempts to edit the set
* at the same time. the "byWhom" parameter is also the object
* sent to any registered SelectionSetListeners as the "source"
* parameter.<br>
*
* another purpose for having an editing mode is so that listener
* notification can be delayed until all edits are completed.<br>
*
* all editing methods may be called atomically where "atomically"
* means that the there is no current editor. in these cases
* all listeners are notified immediately rather then only when
* endEditing() is called. this is mostly meant as a convienience
* for cases when only a single editing operation is needed.<br>
*
* @author Melinda Green
*/
public class SelectionSet {
private Set selectedObjects = new HashSet();
private List selectionSetListeners = new ArrayList();
private Object currentEditor = null;
private Class contentType;
/**
* constructs a SelectionSet capable of holding objects
* of the given type.
*/
public SelectionSet(Class contentType) {
this.contentType = contentType;
}
/**
* no-arg constructor for convienience. the resulting
* selection set will be capable of holding objects
* of any type.
*/
public SelectionSet() {
this(Object.class);
}
//
// Editing mode control methods
//
/**
* reserves the selection set for editing only by the given object.
* @param byWhom the editor-to-be
* @param clearFirst an optional flag which when true will remove any
* previous content.
* @throws IllegalStateException if another object has reserved the
* selection set.
*/
public void beginEditing(Object byWhom, boolean clearFirst) throws IllegalStateException {
if (currentEditor != null)
throw new IllegalStateException("SelectionSet: already being edited");
currentEditor = byWhom;
if(clearFirst)
clear(byWhom);
}
/**
* convenience method of the two-argument version which always
* clears the selection set contents.
*/
public void beginEditing(Object byWhom) throws IllegalStateException {
beginEditing(byWhom, true);
}
/**
* releases a previously reserved selection set.
* @throws IllegalStateException the selection set
* is not already reserved by the given object.
*/
public void endEditing(Object byWhom) throws IllegalStateException {
if (currentEditor != byWhom)
throw new IllegalStateException("SelectionSet: not owner");
Object oldEditor = currentEditor;
currentEditor = null;
fireSelectionChanged(oldEditor); // notify all listeners
}
//
// Editing methods
//
public void addElement(Object element, Object byWhom) throws IllegalStateException, IllegalArgumentException {
preEdit(byWhom);
testType(element);
selectedObjects.add(element);
postEdit(byWhom);
}
public void clear(Object byWhom) throws IllegalStateException {
preEdit(byWhom);
selectedObjects.clear();
postEdit(byWhom);
}
public Object getCurrentEditor() {
return currentEditor;
}
/**
* convienience method sets all selected elements in one shot
* and notifies any selection listeners.<br>
*/
public void setElements(Object newElements[], Object byWhom) throws IllegalStateException, IllegalArgumentException {
preEdit(byWhom);
selectedObjects.clear();
for(int i=0; i<newElements.length; i++) {
testType(newElements[i]);
selectedObjects.add(newElements[i]);
}
postEdit(byWhom);
}
/**
* convienience method sets the list to contain only the given element
* and notifies listeners.<br>
*/
public void setElements(Object element, Object byWhom) throws IllegalStateException {
// any argument exceptions are generated by array version called below
setElements(new Object[] { element }, byWhom);
}
public void removeElement(Object element, Object byWhom) throws IllegalStateException, IllegalArgumentException {
preEdit(byWhom);
testType(element);
selectedObjects.remove(element);
postEdit(byWhom);
}
/**
* adds the given element if not already contained,
* removes it if it is. possibly useful for Window's style
* <code><ctrl>+<left click></code> toggling.
*/
public void toggle(Object element, Object byWhom) throws IllegalStateException, IllegalArgumentException {
preEdit(byWhom);
testType(element);
if(selectedObjects.contains(element))
selectedObjects.remove(element);
else
selectedObjects.add(element);
postEdit(byWhom);
}
//
// Atomic editing and type support.
// callable by subclasses when adding new editing methods.
//
/**
* called at the beginning of all editing methods capable of
* being called atomically.
*/
protected void preEdit(Object byWhom) {
if (currentEditor != null && currentEditor != byWhom)
throw new IllegalStateException("SelectionSet: not owner");
}
/**
* called at the end of all editing methods capable of
* being called atomically.
*/
protected void postEdit(Object byWhom) {
if(currentEditor == null)
fireSelectionChanged(byWhom);
}
/**
* @throws an IllegalArgumentException if the given object
* is null or the class of the given object is not assignment
* compatable with the type of this selection set's content type.
*/
protected void testType(Object obj) throws IllegalArgumentException {
if(obj == null)
throw new IllegalArgumentException("SelectionSet: attempt to add null element");
if( ! contentType.isAssignableFrom(obj.getClass()))
throw new IllegalArgumentException(
"SelectionSet: " + obj.getClass().getName() +
" is not compatable with " + contentType.getName());
}
//
// Selection query methods
//
public int getNumElements() {
return selectedObjects.size();
}
public Object[] getElements() {
return selectedObjects.toArray();
}
public boolean contains(Object obj) {
return selectedObjects.contains(obj);
}
//
// Selection Listener Methods
//
public void addSelectionSetListener(SelectionSetListener listener) {
selectionSetListeners.add(listener);
}
public void removeSelectionSetListener(SelectionSetListener listener) {
selectionSetListeners.remove(listener);
}
protected void fireSelectionChanged(Object byWhom) {
for(Iterator it=selectionSetListeners.iterator(); it.hasNext(); )
((SelectionSetListener)it.next()).selectionSetChanged(this, byWhom);
}
} // end class SelectionSet