[201159] model rework
diff --git a/jpa/plugins/org.eclipse.jpt.utility/META-INF/MANIFEST.MF b/jpa/plugins/org.eclipse.jpt.utility/META-INF/MANIFEST.MF
index f43d949..2eedcd5 100644
--- a/jpa/plugins/org.eclipse.jpt.utility/META-INF/MANIFEST.MF
+++ b/jpa/plugins/org.eclipse.jpt.utility/META-INF/MANIFEST.MF
@@ -13,5 +13,6 @@
org.eclipse.jpt.utility.internal.model.value,
org.eclipse.jpt.utility.internal.model.value.prefs,
org.eclipse.jpt.utility.internal.model.value.swing,
- org.eclipse.jpt.utility.internal.node
+ org.eclipse.jpt.utility.internal.node,
+ org.eclipse.jpt.utility.internal.swing
Bundle-RequiredExecutionEnvironment: J2SE-1.5
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleStringMatcher.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleStringMatcher.java
new file mode 100644
index 0000000..122cd68
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleStringMatcher.java
@@ -0,0 +1,261 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal;
+
+import java.io.Serializable;
+import java.util.regex.Pattern;
+
+// TODO the regex code is not very fast - we could probably do better,
+// hand-coding the matching algorithm (eclipse StringMatcher?)
+/**
+ * This class implements a simple string-matching algorithm that is a little
+ * more user-friendly than standard regular expressions. Instantiate a
+ * string matcher with a filter pattern and then you can use the matcher
+ * to determine whether another string (or object) matches the pattern.
+ * You can also specify whether the matching should be case-sensitive.
+ *
+ * The pattern can contain two "meta-characters":
+ * '*' will match any set of zero or more characters
+ * '?' will match any single character
+ *
+ * Subclasses can override #prefix() and/or #suffix() to change what
+ * strings are prepended or appended to the original pattern string.
+ * This can offer a slight performance improvement over concatenating
+ * strings before calling #setPatternString(String).
+ * By default, a '*' is appended to every string.
+ *
+ * This class also uses the string-matching algorithm to "filter" objects
+ * (and, as a result, also implements the Filter interface).
+ * A string converter is used to determine what string aspect of the
+ * object is compared to the pattern. By default the string returned
+ * by the object's #toString() method is passed to the pattern matcher.
+ */
+public class SimpleStringMatcher
+ implements StringMatcher, Filter, Serializable
+{
+
+ /** An adapter that converts the objects into strings to be matched with the pattern. */
+ private StringConverter stringConverter;
+
+ /** The string used to construct the regular expression pattern. */
+ private String patternString;
+
+ /** Whether the matcher ignores case - the default is true. */
+ private boolean ignoresCase;
+
+ /** The regular expression pattern built from the pattern string. */
+ private Pattern pattern;
+
+ /** A list of the meta-characters we need to escape if found in the pattern string. */
+ public static final char[] REG_EX_META_CHARS = { '(', '[', '{', '\\', '^', '$', '|', ')', '?', '*', '+', '.' };
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a string matcher with an pattern that will match
+ * any string and ignore case.
+ */
+ public SimpleStringMatcher() {
+ this("*");
+ }
+
+ /**
+ * Construct a string matcher with the specified pattern
+ * that will ignore case.
+ */
+ public SimpleStringMatcher(String patternString) {
+ this(patternString, true);
+ }
+
+ /**
+ * Construct a string matcher with the specified pattern that will
+ * ignore case as specified.
+ */
+ public SimpleStringMatcher(String patternString, boolean ignoresCase) {
+ super();
+ this.patternString = patternString;
+ this.ignoresCase = ignoresCase;
+ this.initialize();
+ }
+
+
+ // ********** initialization **********
+
+ protected void initialize() {
+ this.stringConverter = StringConverter.Default.instance();
+ this.rebuildPattern();
+ }
+
+ /**
+ * Given the current pattern string and case-sensitivity setting,
+ * re-build the regular expression pattern.
+ */
+ protected synchronized void rebuildPattern() {
+ this.pattern = this.buildPattern();
+ }
+
+ /**
+ * Given the current pattern string and case-sensitivity setting,
+ * build and return a regular expression pattern that can be used
+ * to match strings.
+ */
+ protected Pattern buildPattern() {
+ int patternFlags = 0x0;
+ if (this.ignoresCase) {
+ patternFlags = Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE;
+ }
+ return Pattern.compile(this.convertToRegEx(this.patternString), patternFlags);
+ }
+
+
+ // ********** StringMatcher implementation **********
+
+ /**
+ * @see StringMatcher#setPatternString(String)
+ */
+ public synchronized void setPatternString(String patternString) {
+ this.patternString = patternString;
+ this.rebuildPattern();
+ }
+
+ /**
+ * Return whether the specified string matches the pattern.
+ */
+ public synchronized boolean matches(String string) {
+ return this.pattern.matcher(string).matches();
+ }
+
+
+ // ********** Filter implementation **********
+
+ public synchronized boolean accept(Object o) {
+ return this.matches(this.stringConverter.convertToString(o));
+ }
+
+
+ // ********** accessors **********
+
+ /**
+ * Return the string converter used to convert the objects
+ * passed to the matcher into strings.
+ */
+ public synchronized StringConverter getStringConverter() {
+ return this.stringConverter;
+ }
+
+ /**
+ * Set the string converter used to convert the objects
+ * passed to the matcher into strings.
+ */
+ public synchronized void setStringConverter(StringConverter stringConverter) {
+ this.stringConverter = stringConverter;
+ }
+
+ /**
+ * Return the original pattern string.
+ */
+ public synchronized String getPatternString() {
+ return this.patternString;
+ }
+
+ /**
+ * Return whether the matcher ignores case.
+ */
+ public synchronized boolean ignoresCase() {
+ return this.ignoresCase;
+ }
+
+ /**
+ * Set whether the matcher ignores case.
+ */
+ public synchronized void setIgnoresCase(boolean ignoresCase) {
+ this.ignoresCase = ignoresCase;
+ this.rebuildPattern();
+ }
+
+ /**
+ * Return the regular expression pattern.
+ */
+ public synchronized Pattern getPattern() {
+ return this.pattern;
+ }
+
+
+ // ********** other public API **********
+
+ /**
+ * Return the regular expression corresponding to
+ * the original pattern string.
+ */
+ public synchronized String regularExpression() {
+ return this.convertToRegEx(this.patternString);
+ }
+
+
+ // ********** converting **********
+
+ /**
+ * Convert the specified string to a regular expression.
+ */
+ protected String convertToRegEx(String string) {
+ StringBuffer sb = new StringBuffer(string.length() + 10);
+ this.convertToRegExOn(this.prefix(), sb);
+ this.convertToRegExOn(string, sb);
+ this.convertToRegExOn(this.suffix(), sb);
+ return sb.toString();
+ }
+
+ /**
+ * Return any prefix that should be prepended to the original
+ * string. By default, there is no prefix.
+ */
+ protected String prefix() {
+ return "";
+ }
+
+ /**
+ * Return any suffix that should be appended to the original
+ * string. Since this class is typically used in UI situation where
+ * the user is typing in a pattern used to filter a list, the default
+ * suffix is a wildcard character.
+ */
+ protected String suffix() {
+ return "*";
+ }
+
+ /**
+ * Convert the specified string to a regular expression.
+ */
+ protected void convertToRegExOn(String string, StringBuffer sb) {
+ char[] charArray = string.toCharArray();
+ int length = charArray.length;
+ for (int i = 0; i < length; i++) {
+ char c = charArray[i];
+ // convert user-friendly meta-chars into regex meta-chars
+ if (c == '*') {
+ sb.append(".*");
+ continue;
+ }
+ if (c == '?') {
+ sb.append('.');
+ continue;
+ }
+ // escape regex meta-chars
+ if (CollectionTools.contains(REG_EX_META_CHARS, c)) {
+ sb.append('\\');
+ }
+ sb.append(c);
+ }
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/StringMatcher.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/StringMatcher.java
new file mode 100644
index 0000000..5dd8f28
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/StringMatcher.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal;
+
+/**
+ * This interface defines a simple API for allowing "pluggable"
+ * string matchers that can be configured with a pattern string
+ * then used to determine what strings match the pattern.
+ */
+public interface StringMatcher {
+
+ /**
+ * Set the pattern string used to determine future
+ * matches. The format and semantics of the pattern
+ * string are determined by the contract between the
+ * client and the server.
+ */
+ void setPatternString(String patternString);
+
+ /**
+ * Return whether the specified string matches the
+ * established pattern string. The semantics of a match
+ * is determined by the contract between the
+ * client and the server.
+ */
+ boolean matches(String string);
+
+
+ StringMatcher NULL_INSTANCE =
+ new StringMatcher() {
+ public void setPatternString(String patternString) {
+ // ignore the pattern string
+ }
+ public boolean matches(String string) {
+ // everything is a match
+ return true;
+ }
+ @Override
+ public String toString() {
+ return "NullStringMatcher";
+ }
+ };
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/AbstractTreeModel.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/AbstractTreeModel.java
new file mode 100644
index 0000000..2229fa8
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/AbstractTreeModel.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.io.Serializable;
+
+import javax.swing.event.EventListenerList;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.TreeModel;
+
+/**
+ * Abstract class that should have been provided by the JDK
+ * (à la javax.swing.AbstractListModel). This class provides:
+ * - support for a collection of listeners
+ * - a number of convenience methods for firing events for those listeners
+ */
+public abstract class AbstractTreeModel implements TreeModel, Serializable {
+
+ /** Our listeners. */
+ protected EventListenerList listenerList;
+
+
+ // ********** constructors/initialization **********
+
+ protected AbstractTreeModel() {
+ super();
+ this.initialize();
+ }
+
+ protected void initialize() {
+ this.listenerList = new EventListenerList();
+ }
+
+
+ // ********** partial TreeModel implementation **********
+
+ public void addTreeModelListener(TreeModelListener l) {
+ this.listenerList.add(TreeModelListener.class, l);
+ }
+
+ public void removeTreeModelListener(TreeModelListener l) {
+ this.listenerList.remove(TreeModelListener.class, l);
+ }
+
+
+ // ********** queries **********
+
+ /**
+ * Return the model's current collection of listeners.
+ * (There seems to be a pattern of making this type of method public;
+ * although it should probably be protected....)
+ */
+ public TreeModelListener[] getTreeModelListeners() {
+ return this.listenerList.getListeners(TreeModelListener.class);
+ }
+
+ /**
+ * Return whether this model has no listeners.
+ */
+ protected boolean hasNoTreeModelListeners() {
+ return this.listenerList.getListenerCount(TreeModelListener.class) == 0;
+ }
+
+ /**
+ * Return whether this model has any listeners.
+ */
+ protected boolean hasTreeModelListeners() {
+ return ! this.hasNoTreeModelListeners();
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Notify listeners of a model change.
+ * A significant property of the nodes changed, but the nodes themselves
+ * are still the same objects.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeNodesChanged(Object[] path, int[] childIndices, Object[] children) {
+ // guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+ // process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length-2; i>=0; i-=2) {
+ if (listeners[i]==TreeModelListener.class) {
+ // lazily create the event
+ if (e == null) {
+ e = new TreeModelEvent(this, path, childIndices, children);
+ }
+ ((TreeModelListener) listeners[i+1]).treeNodesChanged(e);
+ }
+ }
+ }
+
+
+ /**
+ * Notify listeners of a model change.
+ * A significant property of the node changed, but the node itself is the same object.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeNodeChanged(Object[] path, int childIndex, Object child) {
+ this.fireTreeNodesChanged(path, new int[] {childIndex}, new Object[] {child});
+ }
+
+ /**
+ * Notify listeners of a model change.
+ * A significant property of the root changed, but the root itself is the same object.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeRootChanged(Object root) {
+ this.fireTreeNodesChanged(new Object[] {root}, null, null);
+ }
+
+ /**
+ * Notify listeners of a model change.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeNodesInserted(Object[] path, int[] childIndices, Object[] children) {
+ // guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+ // process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length-2; i>=0; i-=2) {
+ if (listeners[i]==TreeModelListener.class) {
+ // lazily create the event
+ if (e == null) {
+ e = new TreeModelEvent(this, path, childIndices, children);
+ }
+ ((TreeModelListener) listeners[i+1]).treeNodesInserted(e);
+ }
+ }
+ }
+
+ /**
+ * Notify listeners of a model change.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeNodeInserted(Object[] path, int childIndex, Object child) {
+ this.fireTreeNodesInserted(path, new int[] {childIndex}, new Object[] {child});
+ }
+
+ /**
+ * Notify listeners of a model change.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeNodesRemoved(Object[] path, int[] childIndices, Object[] children) {
+ // guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+ // process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length-2; i>=0; i-=2) {
+ if (listeners[i]==TreeModelListener.class) {
+ // lazily create the event
+ if (e == null) {
+ e = new TreeModelEvent(this, path, childIndices, children);
+ }
+ ((TreeModelListener) listeners[i+1]).treeNodesRemoved(e);
+ }
+ }
+ }
+
+ /**
+ * Notify listeners of a model change.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeNodeRemoved(Object[] path, int childIndex, Object child) {
+ this.fireTreeNodesRemoved(path, new int[] {childIndex}, new Object[] {child});
+ }
+
+ /**
+ * Notify listeners of a model change.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeStructureChanged(Object[] path) {
+ // guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+ // process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length-2; i>=0; i-=2) {
+ if (listeners[i]==TreeModelListener.class) {
+ // lazily create the event
+ if (e == null) {
+ e = new TreeModelEvent(this, path);
+ }
+ ((TreeModelListener) listeners[i+1]).treeStructureChanged(e);
+ }
+ }
+ }
+
+ /**
+ * Notify listeners of a model change.
+ * @see javax.swing.event.TreeModelEvent
+ * @see javax.swing.event.TreeModelListener
+ */
+ protected void fireTreeRootReplaced(Object newRoot) {
+ this.fireTreeStructureChanged(new Object[] {newRoot});
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/CheckBoxModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/CheckBoxModelAdapter.java
new file mode 100644
index 0000000..7978ec7
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/CheckBoxModelAdapter.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+
+/**
+ * This javax.swing.ButtonModel can be used to keep a listener
+ * (e.g. a JCheckBox) in synch with a PropertyValueModel that
+ * holds a boolean.
+ *
+ * Maybe not the richest class in our toolbox, but it was the
+ * victim of refactoring.... ~bjv
+ */
+public class CheckBoxModelAdapter extends ToggleButtonModelAdapter {
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the boolean holder is required.
+ */
+ public CheckBoxModelAdapter(PropertyValueModel booleanHolder, boolean defaultValue) {
+ super(booleanHolder, defaultValue);
+ }
+
+ /**
+ * Constructor - the boolean holder is required.
+ * The default value will be false.
+ */
+ public CheckBoxModelAdapter(PropertyValueModel booleanHolder) {
+ super(booleanHolder);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ColumnAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ColumnAdapter.java
new file mode 100644
index 0000000..62bcde0
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ColumnAdapter.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+
+
+/**
+ * This adapter is used by the table model adapter to
+ * convert a model object into the models used for each of
+ * the cells for the object's corresponding row in the table.
+ */
+public interface ColumnAdapter {
+ /**
+ * Return the number of columns in the table.
+ * Typically this is static.
+ */
+ int getColumnCount();
+
+ /**
+ * Return the name of the specified column.
+ */
+ String getColumnName(int index);
+
+ /**
+ * Return the class of the specified column.
+ */
+ Class<?> getColumnClass(int index);
+
+ /**
+ * Return whether the specified column is editable.
+ * Typically this is the same for every row.
+ */
+ boolean isColumnEditable(int index);
+
+ /**
+ * Return the cell models for the specified subject
+ * that corresponds to a single row in the table.
+ */
+ PropertyValueModel[] cellModels(Object subject);
+
+}
\ No newline at end of file
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ComboBoxModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ComboBoxModelAdapter.java
new file mode 100644
index 0000000..11187ae
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ComboBoxModelAdapter.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import javax.swing.ComboBoxModel;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.ComboBoxModel can be used to keep a ListDataListener
+ * (e.g. a JComboBox) in synch with a ListValueModel (or a CollectionValueModel).
+ * For combo boxes, the model object that holds the current selection is
+ * typically a different model object than the one that holds the collection
+ * of choices.
+ *
+ * For example, a MWReference (the selectionOwner) has an attribute
+ * "sourceTable" (the collectionOwner)
+ * which holds on to a collection of MWDatabaseFields. When the selection
+ * is changed this model will keep the listeners aware of the changes.
+ * The inherited list model will keep its listeners aware of changes to the
+ * collection model
+ *
+ * In addition to the collection holder required by the superclass,
+ * an instance of this ComboBoxModel must be supplied with a
+ * selection holder, which is a PropertyValueModel that provides access
+ * to the selection (typically a PropertyAspectAdapter).
+ */
+public class ComboBoxModelAdapter extends ListModelAdapter implements ComboBoxModel {
+
+ protected PropertyValueModel selectionHolder;
+ protected PropertyChangeListener selectionListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the list holder and selection holder are required;
+ */
+ public ComboBoxModelAdapter(ListValueModel listHolder, PropertyValueModel selectionHolder) {
+ super(listHolder);
+ if (selectionHolder == null) {
+ throw new NullPointerException();
+ }
+ this.selectionHolder = selectionHolder;
+ }
+
+ /**
+ * Constructor - the collection holder and selection holder are required;
+ */
+ public ComboBoxModelAdapter(CollectionValueModel collectionHolder, PropertyValueModel selectionHolder) {
+ super(collectionHolder);
+ if (selectionHolder == null) {
+ throw new NullPointerException();
+ }
+ this.selectionHolder = selectionHolder;
+ }
+
+
+ // ********** initialization **********
+
+ /**
+ * Extend to build the selection listener.
+ */
+ @Override
+ protected void initialize() {
+ super.initialize();
+ this.selectionListener = this.buildSelectionListener();
+ }
+
+ protected PropertyChangeListener buildSelectionListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ // notify listeners that the selection has changed
+ ComboBoxModelAdapter.this.fireSelectionChanged();
+ }
+ @Override
+ public String toString() {
+ return "selection listener";
+ }
+ };
+ }
+
+
+ // ********** ComboBoxModel implementation **********
+
+ public Object getSelectedItem() {
+ return this.selectionHolder.getValue();
+ }
+
+ public void setSelectedItem(Object selectedItem) {
+ this.selectionHolder.setValue(selectedItem);
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Extend to engage the selection holder.
+ */
+ @Override
+ protected void engageModel() {
+ super.engageModel();
+ this.selectionHolder.addPropertyChangeListener(ValueModel.VALUE, this.selectionListener);
+ }
+
+ /**
+ * Extend to disengage the selection holder.
+ */
+ @Override
+ protected void disengageModel() {
+ this.selectionHolder.removePropertyChangeListener(ValueModel.VALUE, this.selectionListener);
+ super.disengageModel();
+ }
+
+ /**
+ * Notify the listeners that the selection has changed.
+ */
+ protected void fireSelectionChanged() {
+ // I guess this will work...
+ this.fireContentsChanged(this, -1, -1);
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.selectionHolder + ":" + this.listHolder);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/DateSpinnerModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/DateSpinnerModelAdapter.java
new file mode 100644
index 0000000..308b748
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/DateSpinnerModelAdapter.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.swing.SpinnerDateModel;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.SpinnerDateModel can be used to keep a ChangeListener
+ * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a date.
+ *
+ * This class must be a sub-class of SpinnerDateModel because of some
+ * crappy jdk code.... ~bjv
+ * @see javax.swing.JSpinner#createEditor(javax.swing.SpinnerModel)
+ *
+ * If this class needs to be modified, it would behoove us to review the
+ * other, similar classes:
+ * @see ListSpinnerModelAdapter
+ * @see NumberSpinnerModelAdapter
+ */
+public class DateSpinnerModelAdapter extends SpinnerDateModel {
+
+ /**
+ * The default spinner value; used when the underlying model date value is null.
+ * The default is the current date.
+ */
+ private Date defaultValue;
+
+ /** A value model on the underlying date. */
+ private PropertyValueModel dateHolder;
+
+ /** A listener that allows us to synchronize with changes made to the underlying date. */
+ private PropertyChangeListener dateChangeListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the date holder is required.
+ * The default spinner value is the current date.
+ */
+ public DateSpinnerModelAdapter(PropertyValueModel dateHolder) {
+ this(dateHolder, new Date());
+ }
+
+ /**
+ * Constructor - the date holder and default value are required.
+ */
+ public DateSpinnerModelAdapter(PropertyValueModel dateHolder, Date defaultValue) {
+ this(dateHolder, null, null, Calendar.DAY_OF_MONTH, defaultValue);
+ }
+
+ /**
+ * Constructor - the date holder is required.
+ * The default spinner value is the current date.
+ */
+ public DateSpinnerModelAdapter(PropertyValueModel dateHolder, Comparable start, Comparable end, int calendarField) {
+ this(dateHolder, start, end, calendarField, new Date());
+ }
+
+ /**
+ * Constructor - the date holder is required.
+ */
+ public DateSpinnerModelAdapter(PropertyValueModel dateHolder, Comparable start, Comparable end, int calendarField, Date defaultValue) {
+ super(dateHolder.getValue() == null ? defaultValue : (Date) dateHolder.getValue(), start, end, calendarField);
+ this.dateHolder = dateHolder;
+ this.dateChangeListener = this.buildDateChangeListener();
+ // postpone listening to the underlying date
+ // until we have listeners ourselves...
+ this.defaultValue = defaultValue;
+ }
+
+
+ // ********** initialization **********
+
+ private PropertyChangeListener buildDateChangeListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ DateSpinnerModelAdapter.this.synchronize(e.newValue());
+ }
+ @Override
+ public String toString() {
+ return "date listener";
+ }
+ };
+ }
+
+
+ // ********** SpinnerModel implementation **********
+
+ /**
+ * Extend to check whether this method is being called before we
+ * have any listeners.
+ * This is necessary because some crappy jdk code gets the value
+ * from the model *before* listening to the model. ~bjv
+ * @see javax.swing.JSpinner.DefaultEditor(javax.swing.JSpinner)
+ */
+ @Override
+ public Object getValue() {
+ if (this.getChangeListeners().length == 0) {
+ // sorry about this "lateral" call to super ~bjv
+ super.setValue(this.spinnerValueOf(this.dateHolder.getValue()));
+ }
+ return super.getValue();
+ }
+
+ /**
+ * Extend to update the underlying date directly.
+ * The resulting event will be ignored: @see #synchronize(Object).
+ */
+ @Override
+ public void setValue(Object value) {
+ super.setValue(value);
+ this.dateHolder.setValue(value);
+ }
+
+ /**
+ * Extend to start listening to the underlying date if necessary.
+ */
+ @Override
+ public void addChangeListener(ChangeListener listener) {
+ if (this.getChangeListeners().length == 0) {
+ this.dateHolder.addPropertyChangeListener(ValueModel.VALUE, this.dateChangeListener);
+ this.synchronize(this.dateHolder.getValue());
+ }
+ super.addChangeListener(listener);
+ }
+
+ /**
+ * Extend to stop listening to the underlying date if appropriate.
+ */
+ @Override
+ public void removeChangeListener(ChangeListener listener) {
+ super.removeChangeListener(listener);
+ if (this.getChangeListeners().length == 0) {
+ this.dateHolder.removePropertyChangeListener(ValueModel.VALUE, this.dateChangeListener);
+ }
+ }
+
+
+ // ********** queries **********
+
+ protected Date getDefaultValue() {
+ return this.defaultValue;
+ }
+
+ /**
+ * Convert to a non-null value.
+ */
+ protected Object spinnerValueOf(Object value) {
+ return (value == null) ? this.getDefaultValue() : value;
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Set the spinner value if it has changed.
+ */
+ void synchronize(Object value) {
+ Object newValue = this.spinnerValueOf(value);
+ // check to see whether the spinner date has already been synchronized
+ // (via #setValue())
+ if ( ! this.getValue().equals(newValue)) {
+ this.setValue(newValue);
+ }
+ }
+
+
+ // ********** standard methods **********
+
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.dateHolder);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/DocumentAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/DocumentAdapter.java
new file mode 100644
index 0000000..394da65
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/DocumentAdapter.java
@@ -0,0 +1,368 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.io.Serializable;
+import java.util.EventObject;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.EventListenerList;
+import javax.swing.event.UndoableEditEvent;
+import javax.swing.event.UndoableEditListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.PlainDocument;
+import javax.swing.text.Position;
+import javax.swing.text.Segment;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.text.Document can be used to keep a DocumentListener
+ * (e.g. a JTextField) in synch with a PropertyValueModel that holds a string.
+ *
+ * NB: This model should only be used for "small" documents;
+ * i.e. documents used by text fields, not text panes.
+ * @see #synchronizeDelegate(String)
+ */
+public class DocumentAdapter implements Document, Serializable {
+
+ /** The delegate document whose behavior we "enhance". */
+ protected Document delegate;
+
+ /** A listener that allows us to forward any changes made to the delegate document. */
+ protected CombinedListener delegateListener;
+
+ /** A value model on the underlying model string. */
+ protected PropertyValueModel stringHolder;
+
+ /** A listener that allows us to synchronize with changes made to the underlying model string. */
+ protected PropertyChangeListener stringListener;
+
+ /** The event listener list for the document. */
+ protected EventListenerList listenerList = new EventListenerList();
+
+
+ // ********** constructors **********
+
+ /**
+ * Default constructor - initialize stuff.
+ */
+ private DocumentAdapter() {
+ super();
+ this.initialize();
+ }
+
+ /**
+ * Constructor - the string holder is required.
+ * Wrap the specified document.
+ */
+ public DocumentAdapter(PropertyValueModel stringHolder, Document delegate) {
+ this();
+ if (stringHolder == null || delegate == null) {
+ throw new NullPointerException();
+ }
+ this.stringHolder = stringHolder;
+ // postpone listening to the underlying model string
+ // until we have listeners ourselves...
+ this.delegate = delegate;
+ }
+
+ /**
+ * Constructor - the string holder is required.
+ * Wrap a plain document.
+ */
+ public DocumentAdapter(PropertyValueModel stringHolder) {
+ this(stringHolder, new PlainDocument());
+ }
+
+
+ // ********** initialization **********
+
+ protected void initialize() {
+ this.stringListener = this.buildStringListener();
+ this.delegateListener = this.buildDelegateListener();
+ }
+
+ protected PropertyChangeListener buildStringListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ DocumentAdapter.this.stringChanged(e);
+ }
+ @Override
+ public String toString() {
+ return "string listener";
+ }
+ };
+ }
+
+ protected CombinedListener buildDelegateListener() {
+ return new InternalListener();
+ }
+
+
+ // ********** Document implementation **********
+
+ public int getLength() {
+ return this.delegate.getLength();
+ }
+
+ /**
+ * Extend to start listening to the underlying models if necessary.
+ */
+ public void addDocumentListener(DocumentListener listener) {
+ if (this.listenerList.getListenerCount(DocumentListener.class) == 0) {
+ this.delegate.addDocumentListener(this.delegateListener);
+ this.engageStringHolder();
+ }
+ this.listenerList.add(DocumentListener.class, listener);
+ }
+
+ /**
+ * Extend to stop listening to the underlying models if appropriate.
+ */
+ public void removeDocumentListener(DocumentListener listener) {
+ this.listenerList.remove(DocumentListener.class, listener);
+ if (this.listenerList.getListenerCount(DocumentListener.class) == 0) {
+ this.disengageStringHolder();
+ this.delegate.removeDocumentListener(this.delegateListener);
+ }
+ }
+
+ /**
+ * Extend to start listening to the delegate document if necessary.
+ */
+ public void addUndoableEditListener(UndoableEditListener listener) {
+ if (this.listenerList.getListenerCount(UndoableEditListener.class) == 0) {
+ this.delegate.addUndoableEditListener(this.delegateListener);
+ }
+ this.listenerList.add(UndoableEditListener.class, listener);
+ }
+
+ /**
+ * Extend to stop listening to the delegate document if appropriate.
+ */
+ public void removeUndoableEditListener(UndoableEditListener listener) {
+ this.listenerList.remove(UndoableEditListener.class, listener);
+ if (this.listenerList.getListenerCount(UndoableEditListener.class) == 0) {
+ this.delegate.removeUndoableEditListener(this.delegateListener);
+ }
+ }
+
+ public Object getProperty(Object key) {
+ return this.delegate.getProperty(key);
+ }
+
+ public void putProperty(Object key, Object value) {
+ this.delegate.putProperty(key, value);
+ }
+
+ /**
+ * Extend to update the underlying model string directly.
+ * The resulting event will be ignored: @see #synchronizeDelegate(String).
+ */
+ public void remove(int offset, int len) throws BadLocationException {
+ this.delegate.remove(offset, len);
+ this.stringHolder.setValue(this.delegate.getText(0, this.delegate.getLength()));
+ }
+
+ /**
+ * Extend to update the underlying model string directly.
+ * The resulting event will be ignored: @see #synchronizeDelegate(String).
+ */
+ public void insertString(int offset, String insertedString, AttributeSet a) throws BadLocationException {
+ this.delegate.insertString(offset, insertedString, a);
+ this.stringHolder.setValue(this.delegate.getText(0, this.delegate.getLength()));
+ }
+
+ public String getText(int offset, int length) throws BadLocationException {
+ return this.delegate.getText(offset, length);
+ }
+
+ public void getText(int offset, int length, Segment txt) throws BadLocationException {
+ this.delegate.getText(offset, length, txt);
+ }
+
+ public Position getStartPosition() {
+ return this.delegate.getStartPosition();
+ }
+
+ public Position getEndPosition() {
+ return this.delegate.getEndPosition();
+ }
+
+ public Position createPosition(int offs) throws BadLocationException {
+ return this.delegate.createPosition(offs);
+ }
+
+ public Element[] getRootElements() {
+ return this.delegate.getRootElements();
+ }
+
+ public Element getDefaultRootElement() {
+ return this.delegate.getDefaultRootElement();
+ }
+
+ public void render(Runnable r) {
+ this.delegate.render(r);
+ }
+
+
+ // ********** queries **********
+
+ public DocumentListener[] getDocumentListeners() {
+ return this.listenerList.getListeners(DocumentListener.class);
+ }
+
+ public UndoableEditListener[] getUndoableEditListeners() {
+ return this.listenerList.getListeners(UndoableEditListener.class);
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * A third party has modified the underlying model string.
+ * Synchronize the delegate document accordingly.
+ */
+ protected void stringChanged(PropertyChangeEvent e) {
+ this.synchronizeDelegate((String) e.newValue());
+ }
+
+ /**
+ * Replace the document's entire text string with the new string.
+ */
+ protected void synchronizeDelegate(String s) {
+ try {
+ int len = this.delegate.getLength();
+ // check to see whether the delegate has already been synchronized
+ // (via #insertString() or #remove())
+ if ( ! this.delegate.getText(0, len).equals(s)) {
+ this.delegate.remove(0, len);
+ this.delegate.insertString(0, s, null);
+ }
+ } catch (BadLocationException ex) {
+ throw new IllegalStateException(ex.getMessage()); // this should not happen...
+ }
+ }
+
+ protected void engageStringHolder() {
+ this.stringHolder.addPropertyChangeListener(ValueModel.VALUE, this.stringListener);
+ this.synchronizeDelegate((String) this.stringHolder.getValue());
+ }
+
+ protected void disengageStringHolder() {
+ this.stringHolder.removePropertyChangeListener(ValueModel.VALUE, this.stringListener);
+ }
+
+ protected void delegateChangedUpdate(DocumentEvent e) {
+ // no need to lazy-initialize the event;
+ // we wouldn't get here if we did not have listeners...
+ DocumentEvent ee = new InternalDocumentEvent(this, e);
+ DocumentListener[] listeners = this.getDocumentListeners();
+ for (int i = listeners.length; i-- > 0; ) {
+ listeners[i].changedUpdate(ee);
+ }
+ }
+
+ protected void delegateInsertUpdate(DocumentEvent e) {
+ // no need to lazy-initialize the event;
+ // we wouldn't get here if we did not have listeners...
+ DocumentEvent ee = new InternalDocumentEvent(this, e);
+ DocumentListener[] listeners = this.getDocumentListeners();
+ for (int i = listeners.length; i-- > 0; ) {
+ listeners[i].insertUpdate(ee);
+ }
+ }
+
+ protected void delegateRemoveUpdate(DocumentEvent e) {
+ // no need to lazy-initialize the event;
+ // we wouldn't get here if we did not have listeners...
+ DocumentEvent ee = new InternalDocumentEvent(this, e);
+ DocumentListener[] listeners = this.getDocumentListeners();
+ for (int i = listeners.length; i-- > 0; ) {
+ listeners[i].removeUpdate(ee);
+ }
+ }
+
+ protected void delegateUndoableEditHappened(UndoableEditEvent e) {
+ // no need to lazy-initialize the event;
+ // we wouldn't get here if we did not have listeners...
+ UndoableEditEvent ee = new UndoableEditEvent(this, e.getEdit());
+ UndoableEditListener[] listeners = this.getUndoableEditListeners();
+ for (int i = listeners.length; i-- > 0; ) {
+ listeners[i].undoableEditHappened(ee);
+ }
+ }
+
+ // ********** standard methods **********
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.stringHolder);
+ }
+
+
+// ********** inner class **********
+
+ protected interface CombinedListener extends DocumentListener, UndoableEditListener {
+ // just consolidate the two interfaces
+ }
+
+ protected class InternalListener implements CombinedListener {
+ public void changedUpdate(DocumentEvent e) {
+ DocumentAdapter.this.delegateChangedUpdate(e);
+ }
+ public void insertUpdate(DocumentEvent e) {
+ DocumentAdapter.this.delegateInsertUpdate(e);
+ }
+ public void removeUpdate(DocumentEvent e) {
+ DocumentAdapter.this.delegateRemoveUpdate(e);
+ }
+ public void undoableEditHappened(UndoableEditEvent e) {
+ DocumentAdapter.this.delegateUndoableEditHappened(e);
+ }
+ }
+
+ protected static class InternalDocumentEvent
+ extends EventObject
+ implements DocumentEvent
+ {
+ protected DocumentEvent delegate;
+
+ protected InternalDocumentEvent(Document document, DocumentEvent delegate) {
+ super(document);
+ this.delegate = delegate;
+ }
+ public ElementChange getChange(Element elem) {
+ return this.delegate.getChange(elem);
+ }
+ public Document getDocument() {
+ return (Document) this.source;
+ }
+ public int getLength() {
+ return this.delegate.getLength();
+ }
+ public int getOffset() {
+ return this.delegate.getOffset();
+ }
+ public EventType getType() {
+ return this.delegate.getType();
+ }
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ListSpinnerModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ListSpinnerModelAdapter.java
new file mode 100644
index 0000000..2e1559c
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ListSpinnerModelAdapter.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.SpinnerListModel;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.SpinnerListModel can be used to keep a ChangeListener
+ * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a value
+ * in the list.
+ *
+ * This class must be a sub-class of SpinnerListModel because of some
+ * crappy jdk code.... ~bjv
+ * @see javax.swing.JSpinner#createEditor(javax.swing.SpinnerModel)
+ *
+ * NB: This model should only be used for values that have a reasonably
+ * inexpensive #equals() implementation.
+ * @see #synchronize(Object)
+ *
+ * If this class needs to be modified, it would behoove us to review the
+ * other, similar classes:
+ * @see DateSpinnerModelAdapter
+ * @see NumberSpinnerModelAdapter
+ */
+public class ListSpinnerModelAdapter extends SpinnerListModel {
+
+ /**
+ * The default spinner value; used when the underlying model value is null.
+ * The default is the first item on the list.
+ */
+ private Object defaultValue;
+
+ /** A value model on the underlying value. */
+ private PropertyValueModel valueHolder;
+
+ /** A listener that allows us to synchronize with changes made to the underlying value. */
+ private PropertyChangeListener valueChangeListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the value holder is required.
+ * Use the model value itself as the default spinner value.
+ */
+ public ListSpinnerModelAdapter(PropertyValueModel valueHolder) {
+ this(valueHolder, valueHolder.getValue());
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ */
+ public ListSpinnerModelAdapter(PropertyValueModel valueHolder, Object defaultValue) {
+ this(valueHolder, new Object[] {defaultValue}, defaultValue);
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ * Use the first item in the list of values as the default spinner value.
+ */
+ public ListSpinnerModelAdapter(PropertyValueModel valueHolder, Object[] values) {
+ this(valueHolder, values, values[0]);
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ */
+ public ListSpinnerModelAdapter(PropertyValueModel valueHolder, Object[] values, Object defaultValue) {
+ this(valueHolder, Arrays.asList(values), defaultValue);
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ * Use the first item in the list of values as the default spinner value.
+ */
+ public ListSpinnerModelAdapter(PropertyValueModel valueHolder, List values) {
+ this(valueHolder, values, values.get(0));
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ */
+ public ListSpinnerModelAdapter(PropertyValueModel valueHolder, List values, Object defaultValue) {
+ super(values);
+ this.valueHolder = valueHolder;
+ this.valueChangeListener = this.buildValueChangeListener();
+ // postpone listening to the underlying value
+ // until we have listeners ourselves...
+ this.defaultValue = defaultValue;
+ }
+
+
+ // ********** initialization **********
+
+ private PropertyChangeListener buildValueChangeListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ ListSpinnerModelAdapter.this.synchronize(e.newValue());
+ }
+ @Override
+ public String toString() {
+ return "value listener";
+ }
+ };
+ }
+
+
+ // ********** SpinnerModel implementation **********
+
+ /**
+ * Extend to check whether this method is being called before we
+ * have any listeners.
+ * This is necessary because some crappy jdk code gets the value
+ * from the model *before* listening to the model. ~bjv
+ * @see javax.swing.JSpinner.DefaultEditor(javax.swing.JSpinner)
+ */
+ @Override
+ public Object getValue() {
+ if (this.getChangeListeners().length == 0) {
+ // sorry about this "lateral" call to super ~bjv
+ super.setValue(this.spinnerValueOf(this.valueHolder.getValue()));
+ }
+ return super.getValue();
+ }
+
+ /**
+ * Extend to update the underlying value directly.
+ * The resulting event will be ignored: @see #synchronize(Object).
+ */
+ @Override
+ public void setValue(Object value) {
+ super.setValue(value);
+ this.valueHolder.setValue(value);
+ }
+
+ /**
+ * Extend to start listening to the underlying value if necessary.
+ */
+ @Override
+ public void addChangeListener(ChangeListener listener) {
+ if (this.getChangeListeners().length == 0) {
+ this.valueHolder.addPropertyChangeListener(ValueModel.VALUE, this.valueChangeListener);
+ this.synchronize(this.valueHolder.getValue());
+ }
+ super.addChangeListener(listener);
+ }
+
+ /**
+ * Extend to stop listening to the underlying value if appropriate.
+ */
+ @Override
+ public void removeChangeListener(ChangeListener listener) {
+ super.removeChangeListener(listener);
+ if (this.getChangeListeners().length == 0) {
+ this.valueHolder.removePropertyChangeListener(ValueModel.VALUE, this.valueChangeListener);
+ }
+ }
+
+
+ // ********** queries **********
+
+ protected Object getDefaultValue() {
+ return this.defaultValue;
+ }
+
+ /**
+ * Convert to a non-null value.
+ */
+ protected Object spinnerValueOf(Object value) {
+ return (value == null) ? this.getDefaultValue() : value;
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Set the spinner value if it has changed.
+ */
+ void synchronize(Object value) {
+ Object newValue = this.spinnerValueOf(value);
+ // check to see whether the spinner value has already been synchronized
+ // (via #setValue())
+ if ( ! this.getValue().equals(newValue)) {
+ this.setValue(newValue);
+ }
+ }
+
+
+ // ********** standard methods **********
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.valueHolder);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java
new file mode 100644
index 0000000..4173425
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.SpinnerNumberModel can be used to keep a ChangeListener
+ * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a number.
+ *
+ * This class must be a sub-class of SpinnerNumberModel because of some
+ * crappy jdk code.... ~bjv
+ * @see javax.swing.JSpinner#createEditor(javax.swing.SpinnerModel)
+ *
+ * If this class needs to be modified, it would behoove us to review the
+ * other, similar classes:
+ * @see DateSpinnerModelAdapter
+ * @see ListSpinnerModelAdapter
+ */
+public class NumberSpinnerModelAdapter extends SpinnerNumberModel {
+
+ /**
+ * The default spinner value; used when the
+ * underlying model number value is null.
+ */
+ private Number defaultValue;
+
+ /** A value model on the underlying number. */
+ private PropertyValueModel numberHolder;
+
+ /**
+ * A listener that allows us to synchronize with
+ * changes made to the underlying number.
+ */
+ private PropertyChangeListener numberChangeListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the number holder is required.
+ * The default spinner value is zero.
+ * The step size is one.
+ */
+ public NumberSpinnerModelAdapter(PropertyValueModel numberHolder) {
+ this(numberHolder, 0);
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ * The step size is one.
+ */
+ public NumberSpinnerModelAdapter(PropertyValueModel numberHolder, int defaultValue) {
+ this(numberHolder, null, null, new Integer(1), new Integer(defaultValue));
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ * Use the minimum value as the default spinner value.
+ */
+ public NumberSpinnerModelAdapter(PropertyValueModel numberHolder, int minimum, int maximum, int stepSize) {
+ this(numberHolder, minimum, maximum, stepSize, minimum);
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ */
+ public NumberSpinnerModelAdapter(PropertyValueModel numberHolder, int minimum, int maximum, int stepSize, int defaultValue) {
+ this(numberHolder, new Integer(minimum), new Integer(maximum), new Integer(stepSize), new Integer(defaultValue));
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ * Use the minimum value as the default spinner value.
+ */
+ public NumberSpinnerModelAdapter(PropertyValueModel numberHolder, double value, double minimum, double maximum, double stepSize) {
+ this(numberHolder, value, minimum, maximum, stepSize, minimum);
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ */
+ public NumberSpinnerModelAdapter(PropertyValueModel numberHolder, double value, double minimum, double maximum, double stepSize, double defaultValue) {
+ this(numberHolder, new Double(minimum), new Double(maximum), new Double(stepSize), new Double(defaultValue));
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ */
+ public NumberSpinnerModelAdapter(PropertyValueModel numberHolder, Comparable minimum, Comparable maximum, Number stepSize, Number defaultValue) {
+ super(numberHolder.getValue() == null ? defaultValue : (Number) numberHolder.getValue(), minimum, maximum, stepSize);
+ this.numberHolder = numberHolder;
+ this.numberChangeListener = this.buildNumberChangeListener();
+ // postpone listening to the underlying number
+ // until we have listeners ourselves...
+ this.defaultValue = defaultValue;
+ }
+
+
+ // ********** initialization **********
+
+ private PropertyChangeListener buildNumberChangeListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ NumberSpinnerModelAdapter.this.synchronize(e.newValue());
+ }
+ @Override
+ public String toString() {
+ return "number listener";
+ }
+ };
+ }
+
+
+ // ********** SpinnerModel implementation **********
+
+ /**
+ * Extend to check whether this method is being called before we
+ * have any listeners.
+ * This is necessary because some crappy jdk code gets the value
+ * from the model *before* listening to the model. ~bjv
+ * @see javax.swing.JSpinner.DefaultEditor(javax.swing.JSpinner)
+ */
+ @Override
+ public Object getValue() {
+ if (this.getChangeListeners().length == 0) {
+ // sorry about this "lateral" call to super ~bjv
+ super.setValue(this.spinnerValueOf(this.numberHolder.getValue()));
+ }
+ return super.getValue();
+ }
+
+ /**
+ * Extend to update the underlying number directly.
+ * The resulting event will be ignored: @see #synchronizeDelegate(Object).
+ */
+ @Override
+ public void setValue(Object value) {
+ super.setValue(value);
+ this.numberHolder.setValue(value);
+ }
+
+ /**
+ * Extend to start listening to the underlying number if necessary.
+ */
+ @Override
+ public void addChangeListener(ChangeListener listener) {
+ if (this.getChangeListeners().length == 0) {
+ this.numberHolder.addPropertyChangeListener(ValueModel.VALUE, this.numberChangeListener);
+ this.synchronize(this.numberHolder.getValue());
+ }
+ super.addChangeListener(listener);
+ }
+
+ /**
+ * Extend to stop listening to the underlying number if appropriate.
+ */
+ @Override
+ public void removeChangeListener(ChangeListener listener) {
+ super.removeChangeListener(listener);
+ if (this.getChangeListeners().length == 0) {
+ this.numberHolder.removePropertyChangeListener(ValueModel.VALUE, this.numberChangeListener);
+ }
+ }
+
+
+ // ********** queries **********
+
+ protected Number getDefaultValue() {
+ return this.defaultValue;
+ }
+
+ /**
+ * Convert to a non-null value.
+ */
+ protected Object spinnerValueOf(Object value) {
+ return (value == null) ? this.getDefaultValue() : value;
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Set the spinner value if it has changed.
+ */
+ void synchronize(Object value) {
+ Object newValue = this.spinnerValueOf(value);
+ // check to see whether the date has already been synchronized
+ // (via #setValue())
+ if ( ! this.getValue().equals(newValue)) {
+ this.setValue(newValue);
+ }
+ }
+
+
+ // ********** standard methods **********
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.numberHolder);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ObjectListSelectionModel.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ObjectListSelectionModel.java
new file mode 100644
index 0000000..89b5aa3
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ObjectListSelectionModel.java
@@ -0,0 +1,400 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.ListModel;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionListener;
+
+import org.eclipse.jpt.utility.internal.CollectionTools;
+
+/**
+ * This ListSelectionModel is aware of the ListModel and
+ * provides convenience methods to access and set the
+ * selected *objects*, as opposed to the selected *indexes*.
+ */
+public class ObjectListSelectionModel extends DefaultListSelectionModel {
+ /** The list model referenced by the list selection model. */
+ private ListModel listModel;
+ /** A listener that allows us to clear the selection when the list model has changed. */
+ private ListDataListener listDataListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Default constructor - private.
+ */
+ private ObjectListSelectionModel() {
+ super();
+ }
+
+ /**
+ * Construct a list selection model for the specified list model.
+ */
+ public ObjectListSelectionModel(ListModel listModel) {
+ this();
+ this.listModel = listModel;
+ this.listDataListener = this.buildListDataListener();
+ }
+
+
+ // ********** initialization **********
+
+ private ListDataListener buildListDataListener() {
+ return new ListDataListener() {
+ public void intervalAdded(ListDataEvent e) {
+ // this does not affect the selection
+ }
+ public void intervalRemoved(ListDataEvent e) {
+ // this does not affect the selection
+ }
+ public void contentsChanged(ListDataEvent e) {
+ ObjectListSelectionModel.this.listModelContentsChanged(e);
+ }
+ @Override
+ public String toString() {
+ return "list data listener";
+ }
+ };
+ }
+
+ /**
+ * Typically, the selection does not need to be cleared when the
+ * contents of the list have changed. Most of the time this just
+ * means an item has changed in a way that affects its display string
+ * or icon. We typically only use the class for edits involving
+ * single selection.
+ * A subclass can override this method if the selection
+ * should be cleared because a change could mean the selection is invalid.
+ */
+ protected void listModelContentsChanged(ListDataEvent e) {
+ /**this.clearSelection();*/
+ }
+
+
+ // ********** ListSelectionModel implementation **********
+
+ @Override
+ public void addListSelectionListener(ListSelectionListener l) {
+ if (this.hasNoListSelectionListeners()) {
+ this.listModel.addListDataListener(this.listDataListener);
+ }
+ super.addListSelectionListener(l);
+ }
+
+ @Override
+ public void removeListSelectionListener(ListSelectionListener l) {
+ super.removeListSelectionListener(l);
+ if (this.hasNoListSelectionListeners()) {
+ this.listModel.removeListDataListener(this.listDataListener);
+ }
+ }
+
+
+ // ********** queries **********
+
+ /**
+ * Return whether this model has no listeners.
+ */
+ protected boolean hasNoListSelectionListeners() { // private-protected
+ return this.getListSelectionListeners().length == 0;
+ }
+
+ /**
+ * Return the list model referenced by the list selection model.
+ */
+ public ListModel getListModel() {
+ return this.listModel;
+ }
+
+ public int getSelectedValuesSize() {
+ int min = this.getMinSelectionIndex();
+ int max = this.getMaxSelectionIndex();
+
+ if ((min < 0) || (max < 0)) {
+ return 0;
+ }
+
+ int n = 0;
+ int count = this.getListModel().getSize();
+ for (int i = min; i <= max; i++) {
+ if (this.isSelectedIndex(i) && (i < count)) {
+ n++;
+ }
+ }
+ return n;
+ }
+
+ /**
+ * Return the first selected value.
+ * Return null if the selection is empty.
+ */
+ public Object getSelectedValue() {
+ int index = this.getMinSelectionIndex();
+ if (index == -1) {
+ return null;
+ }
+ if (this.getListModel().getSize() <= index) {
+ return null;
+ }
+ return this.getListModel().getElementAt(index);
+ }
+
+ /**
+ * Return an array of the selected values.
+ */
+ public Object[] getSelectedValues() {
+ int min = this.getMinSelectionIndex();
+ int max = this.getMaxSelectionIndex();
+
+ if ((min < 0) || (max < 0)) {
+ return new Object[0];
+ }
+
+ int maxSize = (max - min) + 1;
+ Object[] temp = new Object[maxSize];
+ int n = 0;
+ int count = this.getListModel().getSize();
+ for (int i = min; i <= max; i++) {
+ if (this.isSelectedIndex(i) && (i < count)) {
+ temp[n++] = this.getListModel().getElementAt(i);
+ }
+ }
+ if (n == maxSize) {
+ // all the elements in the range were selected
+ return temp;
+ }
+ // only some of the elements in the range were selected
+ Object[] result = new Object[n];
+ System.arraycopy(temp, 0, result, 0, n);
+ return result;
+ }
+
+ /**
+ * Set the selected value.
+ */
+ public void setSelectedValue(Object object) {
+ this.setSelectedValues(CollectionTools.singletonIterator(object));
+ }
+
+ /**
+ * Set the current set of selected objects to the specified objects.
+ * @see javax.swing.ListSelectionModel#setSelectionInterval(int, int)
+ */
+ public void setSelectedValues(Iterator objects) {
+ this.setValueIsAdjusting(true);
+ this.clearSelection();
+ this.addSelectedValuesInternal(objects);
+ this.setValueIsAdjusting(false);
+ }
+
+ /**
+ * Set the current set of selected objects to the specified objects.
+ * @see javax.swing.ListSelectionModel#setSelectionInterval(int, int)
+ */
+ public void setSelectedValues(Collection objects) {
+ this.setSelectedValues(objects.iterator());
+ }
+
+ /**
+ * Set the current set of selected objects to the specified objects.
+ * @see javax.swing.ListSelectionModel#setSelectionInterval(int, int)
+ */
+ public void setSelectedValues(Object[] objects) {
+ this.setSelectedValues(CollectionTools.iterator(objects));
+ }
+
+ /**
+ * Add the specified object to the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int)
+ */
+ public void addSelectedValue(Object object) {
+ this.addSelectedValues(CollectionTools.singletonIterator(object));
+ }
+
+ /**
+ * Add the specified objects to the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int)
+ */
+ public void addSelectedValues(Iterator objects) {
+ this.setValueIsAdjusting(true);
+ this.addSelectedValuesInternal(objects);
+ this.setValueIsAdjusting(false);
+ }
+
+ /**
+ * Add the specified objects to the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int)
+ */
+ public void addSelectedValues(Collection objects) {
+ this.addSelectedValues(objects.iterator());
+ }
+
+ /**
+ * Add the specified objects to the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int)
+ */
+ public void addSelectedValues(Object[] objects) {
+ this.addSelectedValues(CollectionTools.iterator(objects));
+ }
+
+ /**
+ * Remove the specified object from the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int)
+ */
+ public void removeSelectedValue(Object object) {
+ this.removeSelectedValues(CollectionTools.singletonIterator(object));
+ }
+
+ /**
+ * Remove the specified objects from the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int)
+ */
+ public void removeSelectedValues(Iterator objects) {
+ this.setValueIsAdjusting(true);
+ ListModel lm = this.getListModel();
+ int lmSize = lm.getSize();
+ while (objects.hasNext()) {
+ int index = this.indexOf(objects.next(), lm, lmSize);
+ this.removeSelectionInterval(index, index);
+ }
+ this.setValueIsAdjusting(false);
+ }
+
+ /**
+ * Remove the specified objects from the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int)
+ */
+ public void removeSelectedValues(Collection objects) {
+ this.removeSelectedValues(objects.iterator());
+ }
+
+ /**
+ * Remove the specified objects from the current set of selected objects.
+ * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int)
+ */
+ public void removeSelectedValues(Object[] objects) {
+ this.removeSelectedValues(CollectionTools.iterator(objects));
+ }
+
+ /**
+ * @see javax.swing.ListSelectionModel#getAnchorSelectionIndex()
+ * Return null if the anchor selection is empty.
+ */
+ public Object getAnchorSelectedValue() {
+ int index = this.getAnchorSelectionIndex();
+ if (index == -1) {
+ return null;
+ }
+ return this.getListModel().getElementAt(index);
+ }
+
+ /**
+ * @see javax.swing.ListSelectionModel#setAnchorSelectionIndex(int)
+ */
+ public void setAnchorSelectedValue(Object object) {
+ this.setAnchorSelectionIndex(this.indexOf(object));
+ }
+
+ /**
+ * @see javax.swing.ListSelectionModel#getLeadSelectionIndex()
+ * Return null if the lead selection is empty.
+ */
+ public Object getLeadSelectedValue() {
+ int index = this.getLeadSelectionIndex();
+ if (index == -1) {
+ return null;
+ }
+ return this.getListModel().getElementAt(index);
+ }
+
+ /**
+ * @see javax.swing.ListSelectionModel#setLeadSelectionIndex(int)
+ */
+ public void setLeadSelectedValue(Object object) {
+ this.setLeadSelectionIndex(this.indexOf(object));
+ }
+
+ /**
+ * @see javax.swing.ListSelectionModel#getMaxSelectionIndex()
+ * Return null if the max selection is empty.
+ */
+ public Object getMaxSelectedValue() {
+ int index = this.getMaxSelectionIndex();
+ if (index == -1) {
+ return null;
+ }
+ return this.getListModel().getElementAt(index);
+ }
+
+ /**
+ * @see javax.swing.ListSelectionModel#getMinSelectionIndex()
+ * Return null if the min selection is empty.
+ */
+ public Object getMinSelectedValue() {
+ int index = this.getMinSelectionIndex();
+ if (index == -1) {
+ return null;
+ }
+ return this.getListModel().getElementAt(index);
+ }
+
+ /**
+ * @see javax.swing.ListSelectionModel#isSelectedIndex(int)
+ */
+ public boolean valueIsSelected(Object object) {
+ return this.isSelectedIndex(this.indexOf(object));
+ }
+
+ /**
+ * Add the specified objects to the current set of selected objects,
+ * without wrapping the actions in "adjusting" events.
+ */
+ private void addSelectedValuesInternal(Iterator objects) {
+ ListModel lm = this.getListModel();
+ int listModelSize = lm.getSize();
+ while (objects.hasNext()) {
+ int index = this.indexOf(objects.next(), lm, listModelSize);
+ this.addSelectionInterval(index, index);
+ }
+ }
+
+ /**
+ * Return the index in the list model of the specified object.
+ * Return -1 if the object is not in the list model.
+ */
+ private int indexOf(Object object) {
+ ListModel lm = this.getListModel();
+ return this.indexOf(object, lm, lm.getSize());
+ }
+
+ /**
+ * Return the index in the list model of the specified object.
+ * Return -1 if the object is not in the list model.
+ */
+ // we're just jerking around with performance optimizations here
+ // (in memory of Phil...);
+ // call this method inside loops that do not modify the listModel
+ private int indexOf(Object object, ListModel lm, int listModelSize) {
+ for (int i = listModelSize; i-- > 0; ) {
+ if (lm.getElementAt(i) == object) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/PrimitiveListTreeModel.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/PrimitiveListTreeModel.java
new file mode 100644
index 0000000..ca797bb
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/PrimitiveListTreeModel.java
@@ -0,0 +1,240 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.ListIterator;
+
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import org.eclipse.jpt.utility.internal.model.event.ListChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.ListChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This TreeModel implementation provides a tree with a "null" root that
+ * has a set of "primitive" children. These "primitive" children do not have
+ * children themselves, making the tree a maximum of 2 levels deep.
+ * This model automatically synchronizes the root's children with a
+ * ListValueModel that holds a collection of primitive (non-model) objects
+ * (e.g. Strings).
+ *
+ * This is useful for providing an "editable" list of primitives. Since the JDK
+ * does not provide us with an editable listbox, we must use an editable tree.
+ * We wrap everything in DefaultMutableTreeNodes.
+ *
+ * Subclasses must implement #primitiveChanged(int, Object) and update
+ * the model appropriately. This method is called when the user edits the
+ * list directly and presses <Enter>.
+ *
+ * The JTree using this model must be configured as "editable":
+ * tree.setEditable(true);
+ */
+// TODO convert to use an adapter instead of requiring subclass
+public abstract class PrimitiveListTreeModel extends DefaultTreeModel {
+
+ /** a model on the list of primitives */
+ private ListValueModel listHolder;
+
+ /** a listener that handles the adding, removing, and replacing of the primitives */
+ private ListChangeListener listChangeListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Default constructor - initialize
+ */
+ private PrimitiveListTreeModel() {
+ super(new DefaultMutableTreeNode(null, true)); // the root can have children
+ this.initialize();
+ }
+
+ /**
+ * Public constructor - the list holder is required
+ */
+ public PrimitiveListTreeModel(ListValueModel listHolder) {
+ this();
+ if (listHolder == null) {
+ throw new NullPointerException();
+ }
+ this.listHolder = listHolder;
+ // postpone listening to the model until we have listeners ourselves
+ }
+
+
+ // ********** initialization **********
+
+ private void initialize() {
+ this.listChangeListener = new PrimitiveListChangeListener();
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Subclasses should override this method to update the
+ * model appropriately. The primitive at the specified index was
+ * edited directly by the user and the new value is as specified.
+ * Convert the value appropriately and place it in the model.
+ */
+ protected abstract void primitiveChanged(int index, Object newValue);
+
+
+ // ********** TreeModel implementation **********
+
+ /**
+ * Override to change the underlying model instead of changing the node directly.
+ */
+ @Override
+ public void valueForPathChanged(TreePath path, Object newValue) {
+ TreeNode node = (TreeNode) path.getLastPathComponent();
+ int index = ((TreeNode) this.getRoot()).getIndex(node);
+ this.primitiveChanged(index, newValue);
+ }
+
+ /**
+ * Extend to start listening to the underlying model if necessary.
+ */
+ @Override
+ public void addTreeModelListener(TreeModelListener l) {
+ if (this.getTreeModelListeners().length == 0) {
+ this.listHolder.addListChangeListener(ValueModel.VALUE, this.listChangeListener);
+ this.synchronizeList();
+ }
+ super.addTreeModelListener(l);
+ }
+
+ /**
+ * Extend to stop listening to the underlying model if appropriate.
+ */
+ @Override
+ public void removeTreeModelListener(TreeModelListener l) {
+ super.removeTreeModelListener(l);
+ if (this.getTreeModelListeners().length == 0) {
+ this.listHolder.removeListChangeListener(ValueModel.VALUE, this.listChangeListener);
+ }
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Synchronize our list of nodes with the list of primitives
+ */
+ void synchronizeList() {
+ this.clearList();
+ this.buildList();
+ }
+
+ void clearList() {
+ int childcount = this.root.getChildCount();
+ for (int i = childcount - 1; i >= 0; i--) {
+ this.removeNodeFromParent((MutableTreeNode)this.root.getChildAt(i));
+ }
+ }
+
+ private void buildList() {
+ for (Iterator stream = (Iterator) this.listHolder.getValue(); stream.hasNext(); ) {
+ this.addPrimitive(stream.next());
+ }
+ }
+
+ /**
+ * Add the specified primitive to the end of the list.
+ */
+ private void addPrimitive(Object primitive) {
+ this.insertPrimitive(this.root.getChildCount(), primitive);
+ }
+
+ /**
+ * Create a node for the specified primitive
+ * and insert it as a child of the root.
+ */
+ void insertPrimitive(int index, Object primitive) {
+ DefaultMutableTreeNode node = new DefaultMutableTreeNode(primitive, false); // don't allow children on the child node
+ this.insertNodeInto(node, (MutableTreeNode) this.root, index);
+ }
+
+ /**
+ * Remove node at the specified index.
+ */
+ MutableTreeNode removeNode(int index) {
+ MutableTreeNode node = (MutableTreeNode) this.root.getChildAt(index);
+ this.removeNodeFromParent(node);
+ return node;
+ }
+
+ /**
+ * Replace the user object of the node at childIndex.
+ */
+ void replacePrimitive(int index, Object primitive) {
+ MutableTreeNode node = (MutableTreeNode) this.root.getChildAt(index);
+ node.setUserObject(primitive);
+ this.nodeChanged(node);
+ }
+
+
+ // ********** inner class **********
+
+ private class PrimitiveListChangeListener implements ListChangeListener {
+ PrimitiveListChangeListener() {
+ super();
+ }
+
+ public void itemsAdded(ListChangeEvent e) {
+ int i = e.index();
+ for (ListIterator stream = e.items(); stream.hasNext(); ) {
+ PrimitiveListTreeModel.this.insertPrimitive(i++, stream.next());
+ }
+ }
+
+ public void itemsRemoved(ListChangeEvent e) {
+ for (int i = 0; i < e.itemsSize(); i++) {
+ PrimitiveListTreeModel.this.removeNode(e.index());
+ }
+ }
+
+ public void itemsReplaced(ListChangeEvent e) {
+ int i = e.index();
+ for (ListIterator stream = e.items(); stream.hasNext(); ) {
+ PrimitiveListTreeModel.this.replacePrimitive(i++, stream.next());
+ }
+ }
+
+ public void itemsMoved(ListChangeEvent e) {
+ ArrayList<MutableTreeNode> temp = new ArrayList<MutableTreeNode>(e.moveLength());
+ for (int i = 0; i < e.moveLength(); i++) {
+ temp.add(PrimitiveListTreeModel.this.removeNode(e.sourceIndex()));
+ }
+ int i = e.targetIndex();
+ for (MutableTreeNode node : temp) {
+ PrimitiveListTreeModel.this.insertPrimitive(i++, node);
+ }
+ }
+
+ public void listCleared(ListChangeEvent e) {
+ PrimitiveListTreeModel.this.clearList();
+ }
+
+ public void listChanged(ListChangeEvent e) {
+ PrimitiveListTreeModel.this.synchronizeList();
+ }
+
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/RadioButtonModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/RadioButtonModelAdapter.java
new file mode 100644
index 0000000..9dc34ae
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/RadioButtonModelAdapter.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import org.eclipse.jpt.utility.internal.BidiFilter;
+import org.eclipse.jpt.utility.internal.BidiTransformer;
+import org.eclipse.jpt.utility.internal.model.value.FilteringPropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.TransformationPropertyValueModel;
+
+/**
+ * This javax.swing.ButtonModel can be used to keep a listener
+ * (e.g. a JRadioButton) in synch with a (typically shared)
+ * PropertyValueModel that holds one value out of a set of values.
+ *
+ * NOTE: Do *not* use this model with a ButtonGroup, since the
+ * shared value holder and the wrappers built by this adapter will
+ * keep the appropriate radio button checked. Also, this allows
+ * us to uncheck all the radio buttons in a group when the shared
+ * value is null.
+ */
+public class RadioButtonModelAdapter extends ToggleButtonModelAdapter {
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the value holder is required.
+ */
+ public RadioButtonModelAdapter(PropertyValueModel valueHolder, Object buttonValue, boolean defaultValue) {
+ super(buildBooleanHolder(valueHolder, buttonValue), defaultValue);
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ * The default value will be false.
+ */
+ public RadioButtonModelAdapter(PropertyValueModel valueHolder, Object buttonValue) {
+ super(buildBooleanHolder(valueHolder, buttonValue));
+ }
+
+
+ // ********** static methods **********
+
+ /**
+ * Build up a set of wrappers that will convert the
+ * specified value holder and button value to/from a boolean.
+ *
+ * If the value holder's value matches the button value,
+ * the wrapper will return true. Likewise, if the value holder's
+ * value is set to true, the wrapper will set the value holder's
+ * value to the button value.
+ */
+ public static PropertyValueModel buildBooleanHolder(PropertyValueModel valueHolder, Object buttonValue) {
+ PropertyValueModel filteringPVM = new FilteringPropertyValueModel(valueHolder, new RadioButtonFilter(buttonValue));
+ return new TransformationPropertyValueModel(filteringPVM, new RadioButtonTransformer(buttonValue));
+ }
+
+
+ // ********** overrides **********
+
+ /**
+ * The user cannot de-select a radio button - the user
+ * can only *select* a radio button. Only the model can
+ * cause a radio button to be de-selected. We use the
+ * ARMED flag to indicate whether we are being de-selected
+ * by the user.
+ */
+ @Override
+ public void setSelected(boolean b) {
+ // do not allow the user to de-select a radio button
+ // radio buttons can
+ if ((b == false) && this.isArmed()) {
+ return;
+ }
+ super.setSelected(b);
+ }
+
+
+ // ********** inner classes **********
+
+ /**
+ * This filter will only pass through a new value to the wrapped
+ * value holder when it matches the configured button value.
+ */
+ public static class RadioButtonFilter implements BidiFilter {
+ private Object buttonValue;
+
+ public RadioButtonFilter(Object buttonValue) {
+ super();
+ this.buttonValue = buttonValue;
+ }
+
+ /**
+ * always return the wrapped value
+ */
+ public boolean accept(Object value) {
+ return true;
+ }
+
+ /**
+ * pass through the value to the wrapped property value model
+ * *only* when it matches our button value
+ */
+ public boolean reverseAccept(Object value) {
+ return value == this.buttonValue;
+ }
+
+ }
+
+ /**
+ * This transformer will convert the wrapped value to Boolean.TRUE
+ * when it matches the configured button value.
+ */
+ public static class RadioButtonTransformer implements BidiTransformer {
+ private Object buttonValue;
+
+ public RadioButtonTransformer(Object buttonValue) {
+ super();
+ this.buttonValue = buttonValue;
+ }
+
+ /**
+ * if the wrapped value matches our button value return true,
+ * if it is some other value return false;
+ * but if it is null simply pass it through because it will cause the
+ * button model's default value to be used
+ */
+ public Object transform(Object value) {
+ return (value == null) ? null : Boolean.valueOf(value == this.buttonValue);
+ }
+
+ /**
+ * if the new value is true, pass through the our button value;
+ * otherwise pass through null
+ */
+ public Object reverseTransform(Object value) {
+ return (((Boolean) value).booleanValue()) ? this.buttonValue : null;
+ }
+
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/SpinnerModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/SpinnerModelAdapter.java
new file mode 100644
index 0000000..9e8f805
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/SpinnerModelAdapter.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import javax.swing.AbstractSpinnerModel;
+import javax.swing.SpinnerModel;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.SpinnerModel can be used to keep a ChangeListener
+ * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a value.
+ *
+ * Note: it is likely you want to use one of the following classes instead of
+ * this one:
+ * DateSpinnerModelAdapter
+ * NumberSpinnerModelAdapter
+ * ListSpinnerModelAdapter
+ *
+ * NB: This model should only be used for values that have a fairly
+ * inexpensive #equals() implementation.
+ * @see #synchronizeDelegate(Object)
+ */
+public class SpinnerModelAdapter extends AbstractSpinnerModel {
+
+ /** The delegate spinner model whose behavior we "enhance". */
+ protected SpinnerModel delegate;
+
+ /** A listener that allows us to forward any changes made to the delegate spinner model. */
+ protected ChangeListener delegateListener;
+
+ /** A value model on the underlying value. */
+ protected PropertyValueModel valueHolder;
+
+ /** A listener that allows us to synchronize with changes made to the underlying value. */
+ protected PropertyChangeListener valueListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Default constructor - initialize stuff.
+ */
+ private SpinnerModelAdapter() {
+ super();
+ this.initialize();
+ }
+
+ /**
+ * Constructor - the value holder and delegate are required.
+ */
+ public SpinnerModelAdapter(PropertyValueModel valueHolder, SpinnerModel delegate) {
+ this();
+ if (valueHolder == null || delegate == null) {
+ throw new NullPointerException();
+ }
+ this.valueHolder = valueHolder;
+ this.delegate = delegate;
+ // postpone listening to the underlying value
+ // until we have listeners ourselves...
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ * This will wrap a simple number spinner model.
+ */
+ public SpinnerModelAdapter(PropertyValueModel valueHolder) {
+ this(valueHolder, new SpinnerNumberModel());
+ }
+
+
+ // ********** initialization **********
+
+ protected void initialize() {
+ this.valueListener = this.buildValueListener();
+ this.delegateListener = this.buildDelegateListener();
+ }
+
+ protected PropertyChangeListener buildValueListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ SpinnerModelAdapter.this.valueChanged(e);
+ }
+ @Override
+ public String toString() {
+ return "value listener";
+ }
+ };
+ }
+
+ /**
+ * expand access a bit for inner class
+ */
+ @Override
+ protected void fireStateChanged() {
+ super.fireStateChanged();
+ }
+
+ protected ChangeListener buildDelegateListener() {
+ return new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ // forward the event, with this as the source
+ SpinnerModelAdapter.this.fireStateChanged();
+ }
+ @Override
+ public String toString() {
+ return "delegate listener";
+ }
+ };
+ }
+
+
+ // ********** SpinnerModel implementation **********
+
+ public Object getValue() {
+ return this.delegate.getValue();
+ }
+
+ /**
+ * Extend to update the underlying value directly.
+ * The resulting event will be ignored: @see #synchronizeDelegate(Object).
+ */
+ public void setValue(Object value) {
+ this.delegate.setValue(value);
+ this.valueHolder.setValue(value);
+ }
+
+ public Object getNextValue() {
+ return this.delegate.getNextValue();
+ }
+
+ public Object getPreviousValue() {
+ return this.delegate.getPreviousValue();
+ }
+
+ /**
+ * Extend to start listening to the underlying value if necessary.
+ */
+ @Override
+ public void addChangeListener(ChangeListener listener) {
+ if (this.listenerList.getListenerCount(ChangeListener.class) == 0) {
+ this.delegate.addChangeListener(this.delegateListener);
+ this.engageValueHolder();
+ }
+ super.addChangeListener(listener);
+ }
+
+ /**
+ * Extend to stop listening to the underlying value if appropriate.
+ */
+ @Override
+ public void removeChangeListener(ChangeListener listener) {
+ super.removeChangeListener(listener);
+ if (this.listenerList.getListenerCount(ChangeListener.class) == 0) {
+ this.disengageValueHolder();
+ this.delegate.removeChangeListener(this.delegateListener);
+ }
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * A third party has modified the underlying value.
+ * Synchronize the delegate model accordingly.
+ */
+ protected void valueChanged(PropertyChangeEvent e) {
+ this.synchronizeDelegate(e.newValue());
+ }
+
+ /**
+ * Set the delegate's value if it has changed.
+ */
+ protected void synchronizeDelegate(Object value) {
+ // check to see whether the delegate has already been synchronized
+ // (via #setValue())
+ if ( ! this.delegate.getValue().equals(value)) {
+ this.delegate.setValue(value);
+ }
+ }
+
+ protected void engageValueHolder() {
+ this.valueHolder.addPropertyChangeListener(ValueModel.VALUE, this.valueListener);
+ this.synchronizeDelegate(this.valueHolder.getValue());
+ }
+
+ protected void disengageValueHolder() {
+ this.valueHolder.removePropertyChangeListener(ValueModel.VALUE, this.valueListener);
+ }
+
+
+ // ********** standard methods **********
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.valueHolder);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/TableModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/TableModelAdapter.java
new file mode 100644
index 0000000..0bb0929
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/TableModelAdapter.java
@@ -0,0 +1,402 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+
+import org.eclipse.jpt.utility.internal.model.event.ListChangeEvent;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.ListChangeListener;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.CollectionListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This TableModel can be used to keep a TableModelListener (e.g. a JTable)
+ * in synch with a ListValueModel that holds a collection of model objects,
+ * each of which corresponds to a row in the table.
+ * Typically, each column of the table will be bound to a different aspect
+ * of the contained model objects.
+ *
+ * For example, a MWTable has an attribute 'databaseFields' that holds
+ * a collection of MWDatabaseFields that would correspond to the rows of
+ * a JTable; and each MWDatabaseField has a number
+ * of attributes (e.g. name, type, size) that can be bound to the columns of
+ * a row in the JTable. As these database fields are added, removed, and
+ * changed, this model will keep the listeners aware of the changes.
+ *
+ * An instance of this TableModel must be supplied with a
+ * list holder (e.g. the 'databaseFields'), which is a value
+ * model on the bound collection This is required - the
+ * collection itself can be null, but the list value model that
+ * holds it is required. Typically this list will be sorted (@see
+ * SortedListValueModelAdapter).
+ *
+ * This TableModel must also be supplied with a ColumnAdapter that
+ * will be used to configure the headers, renderers, editors, and contents
+ * of the various columns.
+ *
+ * Design decision:
+ * Cell listener options (from low space/high time to high space/low time):
+ * - 1 cell listener listening to every cell (this is the current implementation)
+ * - 1 cell listener per row
+ * - 1 cell listener per cell
+ */
+public class TableModelAdapter extends AbstractTableModel {
+
+ /**
+ * a list of user objects that are converted to
+ * rows via the column adapter
+ */
+ private ListValueModel listHolder;
+ private ListChangeListener listChangeListener;
+
+ /**
+ * each row is an array of cell models
+ */
+ private ArrayList<PropertyValueModel[]> rows; // declare as ArrayList so we can use #ensureCapacity(int)
+
+ /**
+ * client-supplied adapter that provides with the various column
+ * settings and converts the objects in the LVM
+ * into an array of cell models
+ */
+ private ColumnAdapter columnAdapter;
+
+ /**
+ * the single listener that listens to every cell's model
+ */
+ private PropertyChangeListener cellListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * internal constructor
+ */
+ private TableModelAdapter() {
+ super();
+ this.initialize();
+ }
+
+ /**
+ * Construct a table model adapter for the specified objects
+ * and adapter.
+ */
+ public TableModelAdapter(ListValueModel listHolder, ColumnAdapter columnAdapter) {
+ this();
+ if (listHolder == null) {
+ throw new NullPointerException();
+ }
+ this.listHolder = listHolder;
+ this.columnAdapter = columnAdapter;
+ }
+
+ /**
+ * Construct a table model adapter for the specified objects
+ * and adapter.
+ */
+ public TableModelAdapter(CollectionValueModel collectionHolder, ColumnAdapter columnAdapter) {
+ this(new CollectionListValueModelAdapter(collectionHolder), columnAdapter);
+ }
+
+
+ // ********** initialization **********
+
+ private void initialize() {
+ this.listChangeListener = this.buildListChangeListener();
+ this.rows = new ArrayList<PropertyValueModel[]>();
+ this.cellListener = this.buildCellListener();
+ }
+
+ private ListChangeListener buildListChangeListener() {
+ return new ListChangeListener() {
+ public void itemsAdded(ListChangeEvent e) {
+ TableModelAdapter.this.addRows(e.index(), e.itemsSize(), e.items());
+ }
+ public void itemsRemoved(ListChangeEvent e) {
+ TableModelAdapter.this.removeRows(e.index(), e.itemsSize());
+ }
+ public void itemsReplaced(ListChangeEvent e) {
+ TableModelAdapter.this.replaceRows(e.index(), e.items());
+ }
+ public void itemsMoved(ListChangeEvent e) {
+ TableModelAdapter.this.moveRows(e.targetIndex(), e.sourceIndex(), e.moveLength());
+ }
+ public void listCleared(ListChangeEvent e) {
+ TableModelAdapter.this.clearTable();
+ }
+ public void listChanged(ListChangeEvent e) {
+ TableModelAdapter.this.rebuildTable();
+ }
+ @Override
+ public String toString() {
+ return "list listener";
+ }
+ };
+ }
+
+ private PropertyChangeListener buildCellListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent evt) {
+ TableModelAdapter.this.cellChanged((PropertyValueModel) evt.getSource());
+ }
+ @Override
+ public String toString() {
+ return "cell listener";
+ }
+ };
+ }
+
+
+ // ********** TableModel implementation **********
+
+ public int getColumnCount() {
+ return this.columnAdapter.getColumnCount();
+ }
+
+ public int getRowCount() {
+ return this.rows.size();
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return this.columnAdapter.getColumnName(column);
+ }
+
+ @Override
+ public Class getColumnClass(int columnIndex) {
+ return this.columnAdapter.getColumnClass(columnIndex);
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return this.columnAdapter.isColumnEditable(columnIndex);
+ }
+
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ PropertyValueModel[] row = this.rows.get(rowIndex);
+ return row[columnIndex].getValue();
+ }
+
+ @Override
+ public void setValueAt(Object value, int rowIndex, int columnIndex) {
+ PropertyValueModel[] row = this.rows.get(rowIndex);
+ row[columnIndex].setValue(value);
+ }
+
+ /**
+ * Extend to start listening to the underlying model if necessary.
+ */
+ @Override
+ public void addTableModelListener(TableModelListener l) {
+ if (this.hasNoTableModelListeners()) {
+ this.engageModel();
+ }
+ super.addTableModelListener(l);
+ }
+
+ /**
+ * Extend to stop listening to the underlying model if necessary.
+ */
+ @Override
+ public void removeTableModelListener(TableModelListener l) {
+ super.removeTableModelListener(l);
+ if (this.hasNoTableModelListeners()) {
+ this.disengageModel();
+ }
+ }
+
+
+ // ********** public API **********
+
+ /**
+ * Return the underlying list model.
+ */
+ public ListValueModel getModel() {
+ return this.listHolder;
+ }
+
+ /**
+ * Set the underlying list model.
+ */
+ public void setModel(ListValueModel listHolder) {
+ if (listHolder == null) {
+ throw new NullPointerException();
+ }
+ boolean hasListeners = this.hasTableModelListeners();
+ if (hasListeners) {
+ this.disengageModel();
+ }
+ this.listHolder = listHolder;
+ if (hasListeners) {
+ this.engageModel();
+ this.fireTableDataChanged();
+ }
+ }
+
+ /**
+ * Set the underlying collection model.
+ */
+ public void setModel(CollectionValueModel collectionHolder) {
+ this.setModel(new CollectionListValueModelAdapter(collectionHolder));
+ }
+
+
+ // ********** queries **********
+
+ /**
+ * Return whether this model has no listeners.
+ */
+ protected boolean hasNoTableModelListeners() {
+ return this.listenerList.getListenerCount(TableModelListener.class) == 0;
+ }
+
+ /**
+ * Return whether this model has any listeners.
+ */
+ protected boolean hasTableModelListeners() {
+ return ! this.hasNoTableModelListeners();
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Start listening to the list of objects and the various aspects
+ * of the objects that make up the rows.
+ */
+ private void engageModel() {
+ this.listHolder.addListChangeListener(ValueModel.VALUE, this.listChangeListener);
+ this.engageAllCells();
+ }
+
+ /**
+ * Convert the objects into rows and listen to the cells.
+ */
+ private void engageAllCells() {
+ this.rows.ensureCapacity(this.listHolder.size());
+ for (Iterator stream = (Iterator) this.listHolder.getValue(); stream.hasNext(); ) {
+ PropertyValueModel[] row = this.columnAdapter.cellModels(stream.next());
+ this.engageRow(row);
+ this.rows.add(row);
+ }
+ }
+
+ /**
+ * Listen to the cells in the specified row.
+ */
+ private void engageRow(PropertyValueModel[] row) {
+ for (int i = row.length; i-- > 0; ) {
+ row[i].addPropertyChangeListener(ValueModel.VALUE, this.cellListener);
+ }
+ }
+
+ /**
+ * Stop listening.
+ */
+ private void disengageModel() {
+ this.disengageAllCells();
+ this.listHolder.removeListChangeListener(ValueModel.VALUE, this.listChangeListener);
+ }
+
+ private void disengageAllCells() {
+ for (PropertyValueModel[] row : this.rows) {
+ this.disengageRow(row);
+ }
+ this.rows.clear();
+ }
+
+ private void disengageRow(PropertyValueModel[] row) {
+ for (int i = row.length; i-- > 0; ) {
+ row[i].removePropertyChangeListener(ValueModel.VALUE, this.cellListener);
+ }
+ }
+
+ /**
+ * brute-force search for the cell(s) that changed...
+ */
+ void cellChanged(PropertyValueModel cellHolder) {
+ for (int i = this.rows.size(); i-- > 0; ) {
+ PropertyValueModel[] row = this.rows.get(i);
+ for (int j = row.length; j-- > 0; ) {
+ if (row[j] == cellHolder) {
+ this.fireTableCellUpdated(i, j);
+ }
+ }
+ }
+ }
+
+ /**
+ * convert the items to rows
+ */
+ void addRows(int index, int size, Iterator items) {
+ List<PropertyValueModel[]> newRows = new ArrayList<PropertyValueModel[]>(size);
+ while (items.hasNext()) {
+ PropertyValueModel[] row = this.columnAdapter.cellModels(items.next());
+ this.engageRow(row);
+ newRows.add(row);
+ }
+ this.rows.addAll(index, newRows);
+ this.fireTableRowsInserted(index, index + size - 1);
+ }
+
+ void removeRows(int index, int size) {
+ for (int i = 0; i < size; i++) {
+ this.disengageRow(this.rows.remove(index));
+ }
+ this.fireTableRowsDeleted(index, index + size - 1);
+ }
+
+ void replaceRows(int index, Iterator items) {
+ int i = index;
+ while (items.hasNext()) {
+ PropertyValueModel[] row = this.rows.get(i);
+ this.disengageRow(row);
+ row = this.columnAdapter.cellModels(items.next());
+ this.engageRow(row);
+ this.rows.set(i, row);
+ i++;
+ }
+ this.fireTableRowsUpdated(index, i - 1);
+ }
+
+ void moveRows(int targetIndex, int sourceIndex, int length) {
+ ArrayList<PropertyValueModel[]> temp = new ArrayList<PropertyValueModel[]>(length);
+ for (int i = 0; i < length; i++) {
+ temp.add(this.rows.remove(sourceIndex));
+ }
+ this.rows.addAll(targetIndex, temp);
+
+ int start = Math.min(targetIndex, sourceIndex);
+ int end = Math.max(targetIndex, sourceIndex) + length - 1;
+ this.fireTableRowsUpdated(start, end);
+ }
+
+ void clearTable() {
+ this.disengageAllCells();
+ this.fireTableDataChanged();
+ }
+
+ void rebuildTable() {
+ this.disengageAllCells();
+ this.engageAllCells();
+ this.fireTableDataChanged();
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ToggleButtonModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ToggleButtonModelAdapter.java
new file mode 100644
index 0000000..01ad9d5
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/ToggleButtonModelAdapter.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ItemListener;
+
+import javax.swing.JToggleButton.ToggleButtonModel;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.ButtonModel can be used to keep a listener
+ * (e.g. a JCheckBox or a JRadioButton) in synch with a PropertyValueModel
+ * on a boolean.
+ */
+public class ToggleButtonModelAdapter extends ToggleButtonModel {
+
+ /**
+ * The default setting for the toggle button; for when the underlying model is null.
+ * The default [default value] is false (i.e. the toggle button is unchecked/empty).
+ */
+ protected boolean defaultValue;
+
+ /** A value model on the underlying model boolean. */
+ protected PropertyValueModel booleanHolder;
+
+ /**
+ * A listener that allows us to synchronize with
+ * changes made to the underlying model boolean.
+ */
+ protected PropertyChangeListener booleanChangeListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Default constructor - initialize stuff.
+ */
+ private ToggleButtonModelAdapter() {
+ super();
+ this.initialize();
+ }
+
+ /**
+ * Constructor - the boolean holder is required.
+ */
+ public ToggleButtonModelAdapter(PropertyValueModel booleanHolder, boolean defaultValue) {
+ this();
+ if (booleanHolder == null) {
+ throw new NullPointerException();
+ }
+ this.booleanHolder = booleanHolder;
+ // postpone listening to the underlying model
+ // until we have listeners ourselves...
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * Constructor - the boolean holder is required.
+ * The default value will be false.
+ */
+ public ToggleButtonModelAdapter(PropertyValueModel booleanHolder) {
+ this(booleanHolder, false);
+ }
+
+
+ // ********** initialization **********
+
+ protected void initialize() {
+ this.booleanChangeListener = this.buildBooleanChangeListener();
+ }
+
+ protected PropertyChangeListener buildBooleanChangeListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ ToggleButtonModelAdapter.this.booleanChanged(e);
+ }
+ @Override
+ public String toString() {
+ return "boolean listener";
+ }
+ };
+ }
+
+
+ // ********** ButtonModel implementation **********
+
+ /**
+ * Extend to update the underlying model if necessary.
+ */
+ @Override
+ public void setSelected(boolean b) {
+ if (this.isSelected() != b) { // stop the recursion!
+ super.setSelected(b);//put the super call first, otherwise the following gets called twice
+ this.booleanHolder.setValue(Boolean.valueOf(b));
+ }
+ }
+
+ /**
+ * Extend to start listening to the underlying model if necessary.
+ */
+ @Override
+ public void addActionListener(ActionListener l) {
+ if (this.hasNoListeners()) {
+ this.engageModel();
+ }
+ super.addActionListener(l);
+ }
+
+ /**
+ * Extend to stop listening to the underlying model if appropriate.
+ */
+ @Override
+ public void removeActionListener(ActionListener l) {
+ super.removeActionListener(l);
+ if (this.hasNoListeners()) {
+ this.disengageModel();
+ }
+ }
+
+ /**
+ * Extend to start listening to the underlying model if necessary.
+ */
+ @Override
+ public void addItemListener(ItemListener l) {
+ if (this.hasNoListeners()) {
+ this.engageModel();
+ }
+ super.addItemListener(l);
+ }
+
+ /**
+ * Extend to stop listening to the underlying model if appropriate.
+ */
+ @Override
+ public void removeItemListener(ItemListener l) {
+ super.removeItemListener(l);
+ if (this.hasNoListeners()) {
+ this.disengageModel();
+ }
+ }
+
+ /**
+ * Extend to start listening to the underlying model if necessary.
+ */
+ @Override
+ public void addChangeListener(ChangeListener l) {
+ if (this.hasNoListeners()) {
+ this.engageModel();
+ }
+ super.addChangeListener(l);
+ }
+
+ /**
+ * Extend to stop listening to the underlying model if appropriate.
+ */
+ @Override
+ public void removeChangeListener(ChangeListener l) {
+ super.removeChangeListener(l);
+ if (this.hasNoListeners()) {
+ this.disengageModel();
+ }
+ }
+
+
+ // ********** queries **********
+
+ /**
+ * Return whether we have no listeners at all.
+ */
+ protected boolean hasNoListeners() {
+ return this.listenerList.getListenerCount() == 0;
+ }
+
+ protected boolean getDefaultValue() {
+ return this.defaultValue;
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Synchronize with the specified value.
+ * If it is null, use the default value (which is typically false).
+ */
+ protected void setSelected(Boolean value) {
+ if (value == null) {
+ this.setSelected(this.getDefaultValue());
+ } else {
+ this.setSelected(value.booleanValue());
+ }
+ }
+
+ /**
+ * The underlying model has changed - synchronize accordingly.
+ */
+ protected void booleanChanged(PropertyChangeEvent e) {
+ this.setSelected((Boolean) e.newValue());
+ }
+
+ protected void engageModel() {
+ this.booleanHolder.addPropertyChangeListener(ValueModel.VALUE, this.booleanChangeListener);
+ this.setSelected((Boolean) this.booleanHolder.getValue());
+ }
+
+ protected void disengageModel() {
+ this.booleanHolder.removePropertyChangeListener(ValueModel.VALUE, this.booleanChangeListener);
+ }
+
+
+ // ********** standard methods **********
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.booleanHolder);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/TreeModelAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/TreeModelAdapter.java
new file mode 100644
index 0000000..4d88b74
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/model/value/swing/TreeModelAdapter.java
@@ -0,0 +1,700 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.model.value.swing;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.TreePath;
+
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.model.event.ListChangeEvent;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.event.StateChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.ListChangeListener;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.listener.StateChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ReadOnlyPropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.TreeNodeValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+
+/**
+ * This javax.swing.tree.TreeModel can be used to keep a TreeModelListener
+ * (e.g. a JTree) in synch with a tree of TreeNodeValueModel objects. Unlike
+ * javax.swing.tree.DefaultTreeModel, you do not add and remove nodes with
+ * methods implemented here. You can add and remove nodes by adding and
+ * removing them directly to/from the nodes (or, more typically, the domain
+ * objects the nodes are wrapping and listening to).
+ *
+ * Due to limitations in JTree, the root of the tree can never be null,
+ * which, typically, should not be a problem. (If you want to display an empty
+ * tree you can set the JTree's treeModel to null.)
+ */
+public class TreeModelAdapter extends AbstractTreeModel {
+
+ /**
+ * A value model on the underlying tree's root node and its
+ * corresponding listener. This allows clients to swap out
+ * the entire tree. Due to limitations in JTree, the root should
+ * never be set to null while we have listeners.
+ */
+ private PropertyValueModel rootHolder;
+ private PropertyChangeListener rootListener;
+
+ /**
+ * A listener that notifies us when a node's internal
+ * "state" changes (as opposed to the node's value or list of
+ * children), allowing us to forward notification to our listeners.
+ */
+ private StateChangeListener nodeStateListener;
+
+ /**
+ * A listener that notifies us when a node's "value"
+ * changes (as opposed to the node's state or list of
+ * children), allowing us to forward notification to our listeners.
+ * Typically, this will only happen with nodes that hold
+ * primitive data.
+ */
+ private PropertyChangeListener nodeValueListener;
+
+ /**
+ * A listener that notifies us when an underlying node's
+ * "list" of children changes, allowing us to keep our
+ * internal tree in synch with the underlying tree model.
+ */
+ private ListChangeListener childrenListener;
+
+ /* these attributes make up our internal tree */
+ /**
+ * The root cannot be null while we have listeners, which is
+ * most of the time. The root is cached so we can disengage
+ * from it when it has been swapped out.
+ */
+ private TreeNodeValueModel root;
+
+ /**
+ * Map the nodes to their lists of children.
+ * We cache these so we can swap out the entire list of children
+ * when we receive a #listChanged() event (which does not include
+ * the items that were affected).
+ * @see EventChangePolicy#rebuildChildren()
+ */
+ IdentityHashMap<TreeNodeValueModel, List<TreeNodeValueModel>> childrenLists;
+
+ /**
+ * Map the children models to their parents.
+ * We cache these so we can figure out the "real" source of the
+ * list change events (the parent).
+ * @see EventChangePolicy#parent()
+ */
+ IdentityHashMap<ListValueModel, TreeNodeValueModel> parents;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a tree model for the specified root.
+ */
+ public TreeModelAdapter(PropertyValueModel rootHolder) {
+ super();
+ if (rootHolder == null) {
+ throw new NullPointerException();
+ }
+ this.rootHolder = rootHolder;
+ }
+
+ /**
+ * Construct a tree model for the specified root.
+ */
+ public TreeModelAdapter(TreeNodeValueModel root) {
+ this(new ReadOnlyPropertyValueModel(root));
+ }
+
+
+ // ********** initialization **********
+
+ @Override
+ protected void initialize() {
+ super.initialize();
+ this.rootListener = this.buildRootListener();
+ this.nodeStateListener = this.buildNodeStateListener();
+ this.nodeValueListener = this.buildNodeValueListener();
+ this.childrenListener = this.buildChildrenListener();
+ this.childrenLists = new IdentityHashMap<TreeNodeValueModel, List<TreeNodeValueModel>>();
+ this.parents = new IdentityHashMap<ListValueModel, TreeNodeValueModel>();
+ }
+
+ private PropertyChangeListener buildRootListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ TreeModelAdapter.this.rootChanged();
+ }
+ @Override
+ public String toString() {
+ return "root listener";
+ }
+ };
+ }
+
+ private PropertyChangeListener buildNodeValueListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ TreeModelAdapter.this.nodeChanged((TreeNodeValueModel) e.getSource());
+ }
+ @Override
+ public String toString() {
+ return "node value listener";
+ }
+ };
+ }
+
+ private StateChangeListener buildNodeStateListener() {
+ return new StateChangeListener() {
+ public void stateChanged(StateChangeEvent e) {
+ TreeModelAdapter.this.nodeChanged((TreeNodeValueModel) e.getSource());
+ }
+ @Override
+ public String toString() {
+ return "node state listener";
+ }
+ };
+ }
+
+ private ListChangeListener buildChildrenListener() {
+ return new ListChangeListener() {
+ public void itemsAdded(ListChangeEvent e) {
+ new EventChangePolicy(e).addChildren();
+ }
+ public void itemsRemoved(ListChangeEvent e) {
+ new EventChangePolicy(e).removeChildren();
+ }
+ public void itemsReplaced(ListChangeEvent e) {
+ new EventChangePolicy(e).replaceChildren();
+ }
+ public void itemsMoved(ListChangeEvent e) {
+ new EventChangePolicy(e).moveChildren();
+ }
+ public void listCleared(ListChangeEvent e) {
+ new EventChangePolicy(e).clearChildren();
+ }
+ public void listChanged(ListChangeEvent e) {
+ new EventChangePolicy(e).rebuildChildren();
+ }
+ @Override
+ public String toString() {
+ return "children listener";
+ }
+ };
+ }
+
+
+ // ********** TreeModel implementation **********
+
+ public Object getRoot() {
+ return this.root;
+ }
+
+ public Object getChild(Object parent, int index) {
+ return ((TreeNodeValueModel) parent).getChild(index);
+ }
+
+ public int getChildCount(Object parent) {
+ return ((TreeNodeValueModel) parent).childrenSize();
+ }
+
+ public boolean isLeaf(Object node) {
+ return ((TreeNodeValueModel) node).isLeaf();
+ }
+
+ public void valueForPathChanged(TreePath path, Object newValue) {
+ ((TreeNodeValueModel) path.getLastPathComponent()).setValue(newValue);
+ }
+
+ public int getIndexOfChild(Object parent, Object child) {
+ return ((TreeNodeValueModel) parent).indexOfChild((TreeNodeValueModel) child);
+ }
+
+ /**
+ * Extend to start listening to the underlying model if necessary.
+ */
+ @Override
+ public void addTreeModelListener(TreeModelListener l) {
+ if (this.hasNoTreeModelListeners()) {
+ this.engageModel();
+ }
+ super.addTreeModelListener(l);
+ }
+
+ /**
+ * Extend to stop listening to the underlying model if appropriate.
+ */
+ @Override
+ public void removeTreeModelListener(TreeModelListener l) {
+ super.removeTreeModelListener(l);
+ if (this.hasNoTreeModelListeners()) {
+ this.disengageModel();
+ }
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * Listen to the root and all the other nodes
+ * in the underlying tree model.
+ */
+ private void engageModel() {
+ this.rootHolder.addPropertyChangeListener(ValueModel.VALUE, this.rootListener);
+ this.root = (TreeNodeValueModel) this.rootHolder.getValue();
+ if (this.root == null) {
+ throw new NullPointerException(); // the root cannot be null while we have listeners
+ }
+ this.engageNode(this.root);
+ this.addRoot();
+ }
+
+ /**
+ * Add the root and all of the nodes to the underlying tree.
+ */
+ private void addRoot() {
+ this.addNode(0, this.root);
+ }
+
+ /**
+ * Stop listening to the root and all the other
+ * nodes in the underlying tree model.
+ */
+ private void disengageModel() {
+ this.removeRoot();
+ this.disengageNode(this.root);
+ this.root = null;
+ this.rootHolder.removePropertyChangeListener(ValueModel.VALUE, this.rootListener);
+ }
+
+ /**
+ * Remove the root and all of the nodes from the underlying tree.
+ */
+ private void removeRoot() {
+ this.removeNode(0, this.root);
+ }
+
+ /**
+ * The root has been swapped.
+ * This method is a bit gnarly because the API for notifying listeners
+ * that the root has changed is a bit inconsistent with that used for
+ * non-root nodes.
+ */
+ void rootChanged() {
+ TreeNodeValueModel newRoot = (TreeNodeValueModel) this.rootHolder.getValue();
+ if (newRoot == null) {
+ throw new NullPointerException(); // the root cannot be null while we have listeners
+ }
+ // remove all the current root's children from the tree
+ // and remove the it from the internal tree
+ this.removeRoot();
+
+ // save the old root and swap in the new root
+ TreeNodeValueModel oldRoot = this.root;
+ this.root = newRoot;
+
+ // we must be listening to both the old and new roots when we fire the event
+ // because their values can be affected by whether they have listeners
+ this.engageNode(this.root);
+ this.fireTreeRootReplaced(this.root);
+ // now we can stop listening to the old root
+ this.disengageNode(oldRoot);
+
+ // add the new root to the internal tree and
+ // add all its children to the tree also
+ this.addRoot();
+ }
+
+ /**
+ * Either the "value" or the "state" of the specified node has changed,
+ * forward notification to our listeners.
+ */
+ void nodeChanged(TreeNodeValueModel node) {
+ TreeNodeValueModel parent = node.getParent();
+ if (parent == null) {
+ this.fireTreeRootChanged(node);
+ } else {
+ this.fireTreeNodeChanged(parent.path(), parent.indexOfChild(node), node);
+ }
+ }
+
+ /**
+ * Listen to the nodes, notify our listeners that the nodes were added,
+ * and then add the nodes to our internal tree.
+ * We must listen to the nodes before notifying anybody, because
+ * adding a listener can change the value of a node.
+ */
+ void addChildren(Object[] path, int[] childIndices, Object[] children) {
+ int len = childIndices.length;
+ for (int i = 0; i < len; i++) {
+ this.engageNode((TreeNodeValueModel) children[i]);
+ }
+ this.fireTreeNodesInserted(path, childIndices, children);
+ for (int i = 0; i < len; i++) {
+ this.addNode(childIndices[i], (TreeNodeValueModel) children[i]);
+ }
+ }
+
+ /**
+ * Listen to the node and its children model.
+ */
+ private void engageNode(TreeNodeValueModel node) {
+ node.addStateChangeListener(this.nodeStateListener);
+ node.addPropertyChangeListener(ValueModel.VALUE, this.nodeValueListener);
+ node.getChildrenModel().addListChangeListener(ValueModel.VALUE, this.childrenListener);
+ }
+
+ /**
+ * Add the node to our internal tree;
+ * then recurse down through the node's children,
+ * adding them to the internal tree also.
+ */
+ private void addNode(int index, TreeNodeValueModel node) {
+ this.addNodeToInternalTree(node.getParent(), index, node, node.getChildrenModel());
+ new NodeChangePolicy(node).addChildren();
+ }
+
+ /**
+ * Add the specified node to our internal tree.
+ */
+ private void addNodeToInternalTree(TreeNodeValueModel parent, int index, TreeNodeValueModel node, ListValueModel childrenModel) {
+ List<TreeNodeValueModel> siblings = this.childrenLists.get(parent);
+ if (siblings == null) {
+ siblings = new ArrayList<TreeNodeValueModel>();
+ this.childrenLists.put(parent, siblings);
+ }
+ siblings.add(index, node);
+
+ this.parents.put(childrenModel, node);
+ }
+
+ /**
+ * Remove nodes from our internal tree, notify our listeners that the
+ * nodes were removed, then stop listening to the nodes.
+ * We must listen to the nodes until after notifying anybody, because
+ * removing a listener can change the value of a node.
+ */
+ void removeChildren(Object[] path, int[] childIndices, Object[] children) {
+ int len = childIndices.length;
+ for (int i = 0; i < len; i++) {
+ // the indices slide down a notch each time we remove a child
+ this.removeNode(childIndices[i] - i, (TreeNodeValueModel) children[i]);
+ }
+ this.fireTreeNodesRemoved(path, childIndices, children);
+ for (int i = 0; i < len; i++) {
+ this.disengageNode((TreeNodeValueModel) children[i]);
+ }
+ }
+
+ /**
+ * First, recurse down through the node's children,
+ * removing them from our internal tree;
+ * then remove the node itself from our internal tree.
+ */
+ private void removeNode(int index, TreeNodeValueModel node) {
+ new NodeChangePolicy(node).removeChildren();
+ this.removeNodeFromInternalTree(node.getParent(), index, node, node.getChildrenModel());
+ }
+
+ /**
+ * Remove the specified node from our internal tree.
+ */
+ private void removeNodeFromInternalTree(TreeNodeValueModel parent, int index, TreeNodeValueModel node, ListValueModel childrenModel) {
+ this.parents.remove(childrenModel);
+
+ List<TreeNodeValueModel> siblings = this.childrenLists.get(parent);
+ siblings.remove(index);
+ if (siblings.isEmpty()) {
+ this.childrenLists.remove(parent);
+ }
+ }
+
+ /**
+ * Stop listening to the node and its children model.
+ */
+ private void disengageNode(TreeNodeValueModel node) {
+ node.getChildrenModel().removeListChangeListener(ValueModel.VALUE, this.childrenListener);
+ node.removePropertyChangeListener(ValueModel.VALUE, this.nodeValueListener);
+ node.removeStateChangeListener(this.nodeStateListener);
+ }
+
+ void moveChildren(TreeNodeValueModel parent, int targetIndex, int sourceIndex, int length) {
+ List<TreeNodeValueModel> childrenList = this.childrenLists.get(parent);
+ ArrayList<TreeNodeValueModel> temp = new ArrayList<TreeNodeValueModel>(length);
+ for (int i = 0; i < length; i++) {
+ temp.add(childrenList.remove(sourceIndex));
+ }
+ childrenList.addAll(targetIndex, temp);
+
+ this.fireTreeStructureChanged(parent.path());
+ }
+
+
+ // ********** standard methods **********
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.root);
+ }
+
+
+ // ********** inner classes **********
+
+ /**
+ * Coalesce some of the common change policy behavior.
+ */
+ private abstract class ChangePolicy {
+
+ ChangePolicy() {
+ super();
+ }
+
+ /**
+ * Add the current set of children.
+ */
+ void addChildren() {
+ TreeModelAdapter.this.addChildren(this.parent().path(), this.childIndices(), this.childArray());
+ }
+
+ /**
+ * Remove the current set of children.
+ */
+ void removeChildren() {
+ TreeModelAdapter.this.removeChildren(this.parent().path(), this.childIndices(), this.childArray());
+ }
+
+ /**
+ * Return an array of the indices of the current set of children,
+ * which should be contiguous.
+ */
+ int[] childIndices() {
+ return this.buildIndices(this.childrenStartIndex(), this.childrenSize());
+ }
+
+ /**
+ * Return an array of the current set of children.
+ */
+ Object[] childArray() {
+ return this.buildArray(this.children(), this.childrenSize());
+ }
+
+ /**
+ * Build an array to hold the elements in the specified iterator.
+ * If they are different sizes, something is screwed up...
+ */
+ Object[] buildArray(Iterator<?> stream, int size) {
+ Object[] array = new Object[size];
+ for (int i = 0; stream.hasNext(); i++) {
+ array[i] = stream.next();
+ }
+ return array;
+ }
+
+ /**
+ * Return a set of indices, starting at zero and
+ * continuing for the specified size.
+ */
+ int[] buildIndices(int size) {
+ return buildIndices(0, size);
+ }
+
+ /**
+ * Return a set of indices, starting at the specified index and
+ * continuing for the specified size.
+ */
+ int[] buildIndices(int start, int size) {
+ int[] indices = new int[size];
+ int index = start;
+ for (int i = 0; i < size; i++) {
+ indices[i] = index++;
+ }
+ return indices;
+ }
+
+ /**
+ * Return the parent of the current set of children.
+ */
+ abstract TreeNodeValueModel parent();
+
+ /**
+ * Return the starting index for the current set of children.
+ */
+ abstract int childrenStartIndex();
+
+ /**
+ * Return the size of the current set of children.
+ */
+ abstract int childrenSize();
+
+ /**
+ * Return an interator on the current set of children.
+ */
+ abstract Iterator children();
+
+ }
+
+
+ /**
+ * Wraps a ListChangeEvent for adding, removing, replacing,
+ * and changing children.
+ */
+ private class EventChangePolicy extends ChangePolicy {
+ private ListChangeEvent event;
+
+ EventChangePolicy(ListChangeEvent event) {
+ this.event = event;
+ }
+
+ /**
+ * Map the ListChangeEvent's source to the corresponding parent.
+ */
+ @Override
+ TreeNodeValueModel parent() {
+ return TreeModelAdapter.this.parents.get(this.event.getSource());
+ }
+
+ /**
+ * The ListChangeEvent's item index is the children start index.
+ */
+ @Override
+ int childrenStartIndex() {
+ return this.event.index();
+ }
+
+ /**
+ * The ListChangeEvent's size is the children size.
+ */
+ @Override
+ int childrenSize() {
+ return this.event.itemsSize();
+ }
+
+ /**
+ * The ListChangeEvent's items are the children.
+ */
+ @Override
+ Iterator children() {
+ return this.event.items();
+ }
+
+ /**
+ * Remove the old nodes and add the new ones.
+ */
+ void replaceChildren() {
+ Object[] parentPath = this.parent().path();
+ int[] childIndices = this.childIndices();
+ TreeModelAdapter.this.removeChildren(parentPath, childIndices, this.replacedChildren());
+ TreeModelAdapter.this.addChildren(parentPath, childIndices, this.childArray());
+ }
+
+ /**
+ * Remove the old nodes and add the new ones.
+ */
+ void moveChildren() {
+ TreeModelAdapter.this.moveChildren(this.parent(), this.event.targetIndex(), this.event.sourceIndex(), this.event.moveLength());
+ }
+
+ /**
+ * Clear all the nodes.
+ */
+ void clearChildren() {
+ TreeNodeValueModel parent = this.parent();
+ Object[] parentPath = parent.path();
+ List<TreeNodeValueModel> childrenList = TreeModelAdapter.this.childrenLists.get(parent);
+ int[] childIndices = this.buildIndices(childrenList.size());
+ Object[] childArray = this.buildArray(childrenList.iterator(), childrenList.size());
+ TreeModelAdapter.this.removeChildren(parentPath, childIndices, childArray);
+ }
+
+ /**
+ * Remove all the old nodes and add all the new nodes.
+ */
+ void rebuildChildren() {
+ TreeNodeValueModel parent = this.parent();
+ Object[] parentPath = parent.path();
+ List<TreeNodeValueModel> childrenList = TreeModelAdapter.this.childrenLists.get(parent);
+ int[] childIndices = this.buildIndices(childrenList.size());
+ Object[] childArray = this.buildArray(childrenList.iterator(), childrenList.size());
+ TreeModelAdapter.this.removeChildren(parentPath, childIndices, childArray);
+
+ childIndices = this.buildIndices(parent.getChildrenModel().size());
+ childArray = this.buildArray((Iterator) parent.getChildrenModel().getValue(), parent.childrenSize());
+ TreeModelAdapter.this.addChildren(parentPath, childIndices, childArray);
+ }
+
+ /**
+ * The ListChangeEvent's replaced items are the replaced children.
+ */
+ Object[] replacedChildren() {
+ return this.buildArray(this.event.replacedItems(), this.event.itemsSize());
+ }
+
+ }
+
+
+ /**
+ * Wraps a TreeNodeValueModel for adding and removing its children.
+ */
+ private class NodeChangePolicy extends ChangePolicy {
+ private TreeNodeValueModel node;
+
+ NodeChangePolicy(TreeNodeValueModel node) {
+ this.node = node;
+ }
+
+ /**
+ * The node itself is the parent.
+ */
+ @Override
+ TreeNodeValueModel parent() {
+ return this.node;
+ }
+
+ /**
+ * Since we will always be dealing with all of the node's
+ * children, the children start index is always zero.
+ */
+ @Override
+ int childrenStartIndex() {
+ return 0;
+ }
+
+ /**
+ * Since we will always be dealing with all of the node's
+ * children, the children size is always equal to the size
+ * of the children model.
+ */
+ @Override
+ int childrenSize() {
+ return this.node.getChildrenModel().size();
+ }
+
+ /**
+ * Since we will always be dealing with all of the node's
+ * children, the children are all the objects held by
+ * the children model.
+ */
+ @Override
+ Iterator children() {
+ return (Iterator) this.node.getChildrenModel().getValue();
+ }
+
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/CachingComboBoxModel.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/CachingComboBoxModel.java
new file mode 100644
index 0000000..8ebe261
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/CachingComboBoxModel.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import javax.swing.ComboBoxModel;
+
+/**
+ * This interface allows a client to better control the performance of
+ * a combo box model by allowing the client to specify when it is
+ * acceptable for the model to "cache" and "uncache" its list of elements.
+ * The model may ignore these hints if appropriate.
+ */
+public interface CachingComboBoxModel extends ComboBoxModel {
+
+ /**
+ * Cache the comboBoxModel List. If you call this, you
+ * must make sure to call uncacheList() as well. Otherwise
+ * stale data will be in the ComboBox until cacheList() is
+ * called again or uncacheList() is called.
+ */
+ void cacheList();
+
+ /**
+ * Clear the cached list. Next time the list is needed it will
+ * be built when it is not cached.
+ */
+ void uncacheList();
+
+ /**
+ * Check to see if the list is already cached. This can be used for
+ * MouseEvents, since they are not terribly predictable.
+ */
+ boolean isCached();
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/CheckBoxTableCellRenderer.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/CheckBoxTableCellRenderer.java
new file mode 100644
index 0000000..ae63d33
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/CheckBoxTableCellRenderer.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JCheckBox;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+
+import org.eclipse.jpt.utility.internal.swing.TableCellEditorAdapter.ImmediateEditListener;
+
+/**
+ * Make the cell look like a check box.
+ */
+public class CheckBoxTableCellRenderer implements TableCellEditorAdapter.Renderer {
+
+ /** the component used to paint the cell */
+ private JCheckBox checkBox;
+
+ /** the listener to be notified on an immediate edit */
+ protected TableCellEditorAdapter.ImmediateEditListener immediateEditListener;
+
+ /** "normal" border - assume the default table "focus" border is 1 pixel thick */
+ private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
+
+
+ // ********** constructors/initialization **********
+
+ /**
+ * Construct a cell renderer with no label or icon.
+ */
+ public CheckBoxTableCellRenderer() {
+ super();
+ this.initialize();
+ }
+
+ /**
+ * Construct a cell renderer with the specified text and icon,
+ * either of which may be null.
+ */
+ public CheckBoxTableCellRenderer(String text, Icon icon) {
+ this();
+ this.setText(text);
+ this.setIcon(icon);
+ }
+
+ /**
+ * Construct a cell renderer with the specified text.
+ */
+ public CheckBoxTableCellRenderer(String text) {
+ this(text, null);
+ }
+
+ /**
+ * Construct a cell renderer with the specified icon.
+ */
+ public CheckBoxTableCellRenderer(Icon icon) {
+ this(null, icon);
+ }
+
+ protected void initialize() {
+ this.checkBox = this.buildCheckBox();
+ // by default, check boxes do not paint their borders
+ this.checkBox.setBorderPainted(true);
+ // this setting is recommended for check boxes inside of trees and tables
+ this.checkBox.setBorderPaintedFlat(true);
+ }
+
+ protected JCheckBox buildCheckBox() {
+ JCheckBox cb = new JCheckBox();
+ cb.addActionListener(this.buildActionListener());
+ return cb;
+ }
+
+ private ActionListener buildActionListener() {
+ return new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (CheckBoxTableCellRenderer.this.immediateEditListener != null) {
+ CheckBoxTableCellRenderer.this.immediateEditListener.immediateEdit();
+ }
+ }
+ };
+ }
+
+
+ // ********** TableCellRenderer implementation **********
+
+ /**
+ * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ this.checkBox.setHorizontalAlignment(SwingConstants.CENTER);
+ this.checkBox.setComponentOrientation(table.getComponentOrientation());
+ this.checkBox.setFont(table.getFont());
+ this.checkBox.setEnabled(table.isEnabled());
+
+ this.checkBox.setForeground(this.foregroundColor(table, value, selected, hasFocus, row, column));
+ this.checkBox.setBackground(this.backgroundColor(table, value, selected, hasFocus, row, column));
+ // once the colors are set, calculate opaque setting
+ this.checkBox.setOpaque(this.cellIsOpaqueIn(table, value, selected, hasFocus, row, column));
+ this.checkBox.setBorder(this.border(table, value, selected, hasFocus, row, column));
+
+ this.setValue(value);
+ return this.checkBox;
+ }
+
+ /**
+ * Return the cell's foreground color.
+ */
+ protected Color foregroundColor(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ if (selected) {
+ if (hasFocus && table.isCellEditable(row, column)) {
+ return UIManager.getColor("Table.focusCellForeground");
+ }
+ return table.getSelectionForeground();
+ }
+ return table.getForeground();
+ }
+
+ /**
+ * Return the cell's background color.
+ */
+ protected Color backgroundColor(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ if (selected) {
+ if (hasFocus && table.isCellEditable(row, column)) {
+ return UIManager.getColor("Table.focusCellBackground");
+ }
+ return table.getSelectionBackground();
+ }
+ return table.getBackground();
+ }
+
+ /**
+ * Return the cell's border.
+ */
+ protected Border border(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ return hasFocus ? UIManager.getBorder("Table.focusCellHighlightBorder") : NO_FOCUS_BORDER;
+ }
+
+ /**
+ * Return whether the cell should be opaque in the table.
+ * If the cell's background is the same as the table's background
+ * and table is opaque, we don't need to paint the background -
+ * the table will do it.
+ */
+ protected boolean cellIsOpaqueIn(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ Color cellBackground = this.checkBox.getBackground();
+ Color tableBackground = table.getBackground();
+ return ! (table.isOpaque() && cellBackground.equals(tableBackground));
+ }
+
+ /**
+ * Set the check box's value.
+ */
+ protected void setValue(Object value) {
+ // CR#3999318 - This null check needs to be removed once JDK bug is fixed
+ if (value == null) {
+ value = Boolean.FALSE;
+ }
+ this.checkBox.setSelected(((Boolean) value).booleanValue());
+ }
+
+
+ // ********** TableCellEditorAdapter.Renderer implementation **********
+
+ /**
+ * @see TableCellEditorAdapter
+ */
+ public Object getValue() {
+ return Boolean.valueOf(this.checkBox.isSelected());
+ }
+
+ /**
+ * @see TableCellEditorAdapter
+ */
+ public void setImmediateEditListener(ImmediateEditListener listener) {
+ this.immediateEditListener = listener;
+ }
+
+ // ********** public API **********
+
+ /**
+ * Set the check box's text; which by default is blank.
+ */
+ public void setText(String text) {
+ this.checkBox.setText(text);
+ }
+
+ /**
+ * Set the check box's icon; which by default is not present.
+ */
+ public void setIcon(Icon icon) {
+ this.checkBox.setIcon(icon);
+ }
+
+ /**
+ * Return the renderer's preferred height. This allows you
+ * to set the table's row height to something the check box
+ * will look good in....
+ */
+ public int getPreferredHeight() {
+ // add in space for the border top and bottom
+ return (int) this.checkBox.getPreferredSize().getHeight() + 2;
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/ComboBoxTableCellRenderer.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/ComboBoxTableCellRenderer.java
new file mode 100644
index 0000000..942e0b2
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/ComboBoxTableCellRenderer.java
@@ -0,0 +1,339 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.ComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JTable;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+
+/**
+ * Make the cell look like a combo-box.
+ */
+public class ComboBoxTableCellRenderer implements TableCellEditorAdapter.Renderer {
+
+ /* caching the combo box because we are caching the comboBoxModel.
+ * Everytime we rebuilt the comboBox we would set the model on it and not
+ * remove the model from the old combo box. This meant that new listeners
+ * kept being added to the comboBoxModel for every comboBox build.
+ * Not sure if there is a way to clear out the old combo box, or why
+ * we were buildig a new combo box every time so I went with caching it.
+ */
+ private JComboBox comboBox;
+
+ /** the items used to populate the combo box */
+ private CachingComboBoxModel model;
+ private ListCellRenderer renderer;
+ Object value;
+ private static int height = -1;
+ boolean fakeFocusFlag;
+
+ /** the listener to be notified on an immediate edit */
+ protected TableCellEditorAdapter.ImmediateEditListener immediateEditListener;
+
+ /** hold the original colors of the combo-box */
+ private static Color defaultForeground;
+ private static Color defaultBackground;
+
+ /** "normal" border - assume the default table "focus" border is 1 pixel thick */
+ private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
+
+
+ // ********** constructors/initialization **********
+
+ /**
+ * Default constructor.
+ */
+ private ComboBoxTableCellRenderer() {
+ super();
+ initialize();
+ }
+
+ /**
+ * Construct a cell renderer that uses the specified combo-box model.
+ */
+ public ComboBoxTableCellRenderer(ComboBoxModel model) {
+ this(new NonCachingComboBoxModel(model));
+ }
+
+ /**
+ * Construct a cell renderer that uses the specified caching combo-box model.
+ */
+ public ComboBoxTableCellRenderer(CachingComboBoxModel model) {
+ this();
+ this.model = model;
+ }
+
+ /**
+ * Construct a cell renderer that uses the specified
+ * combo-box model and renderer.
+ */
+ public ComboBoxTableCellRenderer(ComboBoxModel model, ListCellRenderer renderer) {
+ this(new NonCachingComboBoxModel(model), renderer);
+ }
+
+ /**
+ * Construct a cell renderer that uses the specified
+ * caching combo-box model and renderer.
+ */
+ public ComboBoxTableCellRenderer(CachingComboBoxModel model, ListCellRenderer renderer) {
+ this(model);
+ this.renderer = renderer;
+ }
+
+ protected void initialize() {
+ // save the original colors of the combo-box, so we
+ // can use them to paint non-selected cells
+ if (height == -1) {
+ JComboBox cb = new JComboBox();
+ cb.addItem("m");
+
+ // add in space for the border top and bottom
+ height = cb.getPreferredSize().height + 2;
+
+ defaultForeground = cb.getForeground();
+ defaultBackground = cb.getBackground();
+ }
+ }
+
+ static JLabel prototypeLabel = new JLabel("Prototype", new EmptyIcon(16), SwingConstants.LEADING);
+
+ protected JComboBox buildComboBox() {
+
+ final JComboBox result = new JComboBox() {
+ private boolean fakeFocus;
+ @Override
+ public boolean hasFocus() {
+ return fakeFocus || super.hasFocus();
+ }
+ @Override
+ public void paint(Graphics g) {
+ fakeFocus = ComboBoxTableCellRenderer.this.fakeFocusFlag;
+ super.paint(g);
+ fakeFocus = false;
+ }
+ //wrap the renderer to deal with the prototypeDisplayValue
+ @Override
+ public void setRenderer(final ListCellRenderer aRenderer) {
+ super.setRenderer(new ListCellRenderer(){
+ public Component getListCellRendererComponent(JList list, Object v, int index, boolean isSelected, boolean cellHasFocus) {
+ if (v == prototypeLabel) {
+ return prototypeLabel;
+ }
+ return aRenderer.getListCellRendererComponent(list, v, index, isSelected, cellHasFocus);
+ }
+ });
+ }
+ @Override
+ public int getSelectedIndex() {
+ boolean listNotCached = !listIsCached();
+ if (listNotCached) {
+ cacheList();
+ }
+
+ int index = super.getSelectedIndex();
+
+ if (listNotCached) {
+ uncacheList();
+ }
+ return index;
+ }
+
+ };
+ // stole this code from javax.swing.DefaultCellEditor
+ result.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
+ result.addActionListener(this.buildActionListener());
+ result.addPopupMenuListener(this.buildPopupMenuListener());
+
+ //These are used to workaround problems with Swing trying to
+ //determine the size of a comboBox with a large model
+ result.setPrototypeDisplayValue(prototypeLabel);
+ getListBox(result).setPrototypeCellValue(prototypeLabel);
+
+ return result;
+ }
+
+
+ private JList getListBox(JComboBox result) {
+ return (JList) ClassTools.getFieldValue(result.getUI(), "listBox");
+ }
+
+
+ private ActionListener buildActionListener() {
+ return new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JComboBox cb = (JComboBox) e.getSource();
+ Object selectedItem = cb.getSelectedItem();
+
+ // Only update the selected item and invoke immediateEdit() if the
+ // selected item actually changed, during the initialization of the
+ // editing, the model changes and causes this method to be invoked,
+ // it causes CR#3963675 to occur because immediateEdit() stop the
+ // editing, which is done at the wrong time
+ if (ComboBoxTableCellRenderer.this.value != selectedItem) {
+ ComboBoxTableCellRenderer.this.value = cb.getSelectedItem();
+ ComboBoxTableCellRenderer.this.immediateEdit();
+ }
+ }
+ };
+ }
+
+ void immediateEdit() {
+ if (this.immediateEditListener != null) {
+ this.immediateEditListener.immediateEdit();
+ }
+ }
+
+ private PopupMenuListener buildPopupMenuListener() {
+ return new PopupMenuListener() {
+
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+ if (listIsCached()) {
+ uncacheList();
+ }
+ cacheList();
+ }
+
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+ if (listIsCached()) {
+ uncacheList();
+ }
+
+ }
+
+ public void popupMenuCanceled(PopupMenuEvent e) {
+ if (listIsCached()) {
+ uncacheList();
+ }
+ }
+ };
+ }
+
+
+ void cacheList() {
+ this.model.cacheList();
+ }
+
+ void uncacheList() {
+ this.model.uncacheList();
+ }
+
+ boolean listIsCached() {
+ return this.model.isCached();
+ }
+ // ********** TableCellRenderer implementation **********
+
+ /**
+ * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ public Component getTableCellRendererComponent(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
+ this.fakeFocusFlag = selected || hasFocus;
+ if (this.comboBox == null) {
+ this.comboBox = this.buildComboBox();
+
+ this.comboBox.setComponentOrientation(table.getComponentOrientation());
+ this.comboBox.setModel(this.model);
+ if (this.renderer != null) {
+ this.comboBox.setRenderer(this.renderer);
+ }
+ this.comboBox.setFont(table.getFont());
+ this.comboBox.setEnabled(table.isEnabled());
+ this.comboBox.setBorder(this.border(table, val, selected, hasFocus, row, column));
+ }
+
+ // We need to go through the model since JComboBox might prevent us from
+ // selecting the value. This can happen when the value is not contained
+ // in the model, see CR#3950044 for an example
+ this.model.setSelectedItem(val);
+
+ return this.comboBox;
+ }
+
+ /**
+ * Return the cell's foreground color.
+ */
+ protected Color foregroundColor(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
+ if (selected) {
+ if (hasFocus && table.isCellEditable(row, column)) {
+ return defaultForeground;
+ }
+ return table.getSelectionForeground();
+ }
+ return defaultForeground;
+ }
+
+ /**
+ * Return the cell's background color.
+ */
+ protected Color backgroundColor(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
+ if (selected) {
+ if (hasFocus && table.isCellEditable(row, column)) {
+ return defaultBackground;
+ }
+ return table.getSelectionBackground();
+ }
+ return defaultBackground;
+ }
+
+ /**
+ * Return the cell's border.
+ */
+ protected Border border(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
+ return hasFocus ?
+ UIManager.getBorder("Table.focusCellHighlightBorder")
+ :
+ NO_FOCUS_BORDER;
+ }
+
+
+ // ********** TableCellEditorAdapter.Renderer implementation **********
+
+ /**
+ * @see TableCellEditorAdapter#getValue()
+ */
+ public Object getValue() {
+ return this.value;
+ }
+
+ /**
+ * @see TableCellEditorAdapter#setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener)
+ */
+ public void setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener listener) {
+ this.immediateEditListener = listener;
+ }
+
+
+ // ********** public API **********
+
+ /**
+ * Return the renderer's preferred height. This allows you
+ * to set the row height to something the combo-box will look good in....
+ */
+ public int getPreferredHeight() {
+ return height;
+ }
+
+}
\ No newline at end of file
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Displayable.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/Displayable.java
similarity index 98%
rename from jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Displayable.java
rename to jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/Displayable.java
index fbb1775..905fb6a 100644
--- a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Displayable.java
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/Displayable.java
@@ -7,7 +7,7 @@
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
-package org.eclipse.jpt.utility.internal;
+package org.eclipse.jpt.utility.internal.swing;
import java.text.Collator;
import java.util.Comparator;
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/EmptyIcon.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/EmptyIcon.java
new file mode 100644
index 0000000..802251f
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/EmptyIcon.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.Component;
+import java.awt.Graphics;
+
+import javax.swing.Icon;
+
+/**
+ * Implement the Icon interface with an icon that has a size but
+ * does not paint anything on the graphics context.
+ */
+public class EmptyIcon
+ implements Icon
+{
+ private final int width;
+ private final int height;
+
+ public static final EmptyIcon NULL_INSTANCE = new EmptyIcon(0);
+
+
+ public EmptyIcon(int width, int height) {
+ super();
+ this.width = width;
+ this.height = height;
+ }
+
+ public EmptyIcon(int size) {
+ this(size, size);
+ }
+
+
+ // ********** Icon implementation **********
+
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ // don't paint anything for an empty icon
+ }
+
+ public int getIconWidth() {
+ return this.width;
+ }
+
+ public int getIconHeight() {
+ return this.height;
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/FilteringListBrowser.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/FilteringListBrowser.java
new file mode 100644
index 0000000..7031545
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/FilteringListBrowser.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JOptionPane;
+import javax.swing.ListModel;
+
+/**
+ * This implementation of LongListComponent.Browser uses a
+ * JOptionPane to prompt the user for the selection. The JOPtionPane
+ * is passed a FilteringListPanel to assist the user in making
+ * a selection.
+ */
+public class FilteringListBrowser
+ implements ListChooser.ListBrowser
+{
+ private FilteringListPanel panel;
+
+ /**
+ * Default constructor.
+ */
+ public FilteringListBrowser() {
+ super();
+ this.panel = this.buildPanel();
+ }
+
+ protected FilteringListPanel buildPanel() {
+ return new LocalFilteringListPanel();
+ }
+
+ /**
+ * Prompt the user using a JOptionPane with a filtering
+ * list panel.
+ */
+ public void browse(ListChooser chooser) {
+ this.initializeCellRenderer(chooser);
+
+ int option =
+ JOptionPane.showOptionDialog(
+ chooser,
+ this.message(chooser),
+ this.title(chooser),
+ this.optionType(chooser),
+ this.messageType(chooser),
+ this.icon(chooser),
+ this.selectionValues(chooser),
+ this.initialSelectionValue(chooser)
+ );
+
+ if (option == JOptionPane.OK_OPTION) {
+ chooser.getModel().setSelectedItem(this.panel.getSelection());
+ }
+
+ // clear the text field so the list box is re-filtered
+ this.panel.getTextField().setText("");
+ }
+
+ protected void initializeCellRenderer(JComboBox comboBox) {
+ // default behavior should be to use the cell renderer from the combobox.
+ this.panel.getListBox().setCellRenderer(comboBox.getRenderer());
+ }
+
+ /**
+ * the message can be anything - here we build a component
+ */
+ protected Object message(JComboBox comboBox) {
+ this.panel.setCompleteList(this.convertToArray(comboBox.getModel()));
+ this.panel.setSelection(comboBox.getModel().getSelectedItem());
+ return this.panel;
+ }
+
+ protected String title(JComboBox comboBox) {
+ return null;
+ }
+
+ protected int optionType(JComboBox comboBox) {
+ return JOptionPane.OK_CANCEL_OPTION;
+ }
+
+ protected int messageType(JComboBox comboBox) {
+ return JOptionPane.QUESTION_MESSAGE;
+ }
+
+ protected Icon icon(JComboBox comboBox) {
+ return null;
+ }
+
+ protected Object[] selectionValues(JComboBox comboBox) {
+ return null;
+ }
+
+ protected Object initialSelectionValue(JComboBox comboBox) {
+ return null;
+ }
+
+ /**
+ * Convert the list of objects in the specified list model
+ * into an array.
+ */
+ protected Object[] convertToArray(ListModel model) {
+ int size = model.getSize();
+ Object[] result = new Object[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = model.getElementAt(i);
+ }
+ return result;
+ }
+
+
+ // ********** custom panel **********
+
+ protected class LocalFilteringListPanel extends FilteringListPanel {
+
+ protected LocalFilteringListPanel() {
+ super(new Object[0], null);
+ }
+
+ /**
+ * Disable the performance tweak because JOptionPane
+ * will try open wide enough to disable the horizontal scroll bar;
+ * and it looks a bit clumsy.
+ * @see oracle.toplink.workbench.uitools.FilteringListPanel#prototypeCellValue()
+ */
+ protected String prototypeCellValue() {
+ return null;
+ }
+
+ }
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/FilteringListPanel.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/FilteringListPanel.java
new file mode 100644
index 0000000..bb36dfb
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/FilteringListPanel.java
@@ -0,0 +1,444 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+
+import javax.swing.AbstractListModel;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.border.Border;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import org.eclipse.jpt.utility.internal.SimpleStringMatcher;
+import org.eclipse.jpt.utility.internal.StringConverter;
+import org.eclipse.jpt.utility.internal.StringMatcher;
+
+/**
+ * This panel presents an entry field and a list box of choices that
+ * allows the user to filter the entries in the list box by entering
+ * a pattern in the entry field.
+ *
+ * By default, two wildcards are allowed in the pattern:
+ * '*' will match any set of zero or more characters
+ * '?' will match any single character
+ *
+ * The panel consists of 4 components that can be customized:
+ * - 1 text field
+ * - 1 list box
+ * - 2 labels, one for each of the above
+ *
+ * Other aspects of the panel's behavior can be changed:
+ * - the string converter determines how the objects in the
+ * list are converted to strings and compared to the pattern
+ * entered in the text field; by default the converter simply
+ * uses the result of the object's #toString() method
+ * (if you replace the string converter, you will probably
+ * want to replace the list box's cell renderer also)
+ * - the string matcher can also be changed if you would
+ * like different pattern matching behavior than that
+ * described above
+ * - you can specify the maximum size of the list - this may
+ * force the user to enter a pattern restrictive enough
+ * to result in a list smaller than the maximum size; the
+ * default is -1, which disables the restriction
+ *
+ * This panel is not a typical panel, in the sense that it does not share
+ * its model with clients via value models. Instead, this panel's model
+ * is set and queried directly because it is designed to be used in a
+ * dialog that directs the user's behavior (as opposed to a "normal"
+ * window).
+ */
+public class FilteringListPanel extends JPanel {
+
+ /**
+ * The complete list of available choices
+ * (as opposed to the partial list held by the list box).
+ */
+ private Object[] completeList;
+
+ /**
+ * An adapter used to convert the objects in the list
+ * to strings so they can be run through the matcher
+ * and displayed in the text field.
+ */
+ StringConverter stringConverter;
+
+ /** The text field. */
+ private JTextField textField;
+ private JLabel textFieldLabel;
+ private DocumentListener textFieldListener;
+
+ /** The list box. */
+ private JList listBox;
+ private JLabel listBoxLabel;
+
+ /** The maximum number of entries displayed in the list box. */
+ private int maxListSize;
+
+ /**
+ * The matcher used to filter the list against
+ * the pattern entered in the text field. By default,
+ * this allows the two wildcard characters described in
+ * the class comment.
+ */
+ private StringMatcher stringMatcher;
+
+ /**
+ * Performance tweak: We use this buffer instead of
+ * a temporary variable during filtering so we don't have
+ * to keep re-allocating it.
+ */
+ private Object[] buffer;
+
+ private static final Border TEXT_FIELD_LABEL_BORDER = BorderFactory.createEmptyBorder(0, 0, 5, 0);
+ private static final Border LIST_BOX_LABEL_BORDER = BorderFactory.createEmptyBorder(5, 0, 5, 0);
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a FilteringListPanel with the specified list of choices
+ * and initial selection. Use the default string converter to convert the
+ * choices and selection to strings (which simply calls #toString() on
+ * the objects).
+ */
+ public FilteringListPanel(Object[] completeList, Object initialSelection) {
+ this(completeList, initialSelection, StringConverter.Default.instance());
+ }
+
+ /**
+ * Construct a FilteringListPanel with the specified list of choices
+ * and initial selection. Use the specified string converter to convert the
+ * choices and selection to strings.
+ */
+ public FilteringListPanel(Object[] completeList, Object initialSelection, StringConverter stringConverter) {
+ super(new BorderLayout());
+ this.completeList = completeList;
+ this.stringConverter = stringConverter;
+ this.initialize(initialSelection);
+ }
+
+
+ // ********** initialization **********
+
+ private void initialize(Object initialSelection) {
+ this.maxListSize = this.defaultMaxListSize();
+ this.buffer = new Object[this.max()];
+
+ this.textFieldListener = this.buildTextFieldListener();
+
+ this.stringMatcher = this.buildStringMatcher();
+
+ this.initializeLayout(initialSelection);
+ }
+
+ /**
+ * Return the current max number of entries allowed in the list box.
+ */
+ private int max() {
+ if (this.maxListSize == -1) {
+ return this.completeList.length;
+ }
+ return Math.min(this.maxListSize, this.completeList.length);
+ }
+
+ /**
+ * Build a listener that will listen to changes in the text field
+ * and filter the list appropriately.
+ */
+ private DocumentListener buildTextFieldListener() {
+ return new DocumentListener() {
+ public void insertUpdate(DocumentEvent e) {
+ FilteringListPanel.this.filterList();
+ }
+ public void changedUpdate(DocumentEvent e) {
+ FilteringListPanel.this.filterList();
+ }
+ public void removeUpdate(DocumentEvent e) {
+ FilteringListPanel.this.filterList();
+ }
+ @Override
+ public String toString() {
+ return "text field listener";
+ }
+ };
+ }
+
+ private int defaultMaxListSize() {
+ return -1;
+ }
+
+ private StringMatcher buildStringMatcher() {
+ return new SimpleStringMatcher();
+ }
+
+ private void initializeLayout(Object initialSelection) {
+ // text field
+ JPanel textFieldPanel = new JPanel(new BorderLayout());
+ this.textFieldLabel = new JLabel();
+ this.textFieldLabel.setBorder(TEXT_FIELD_LABEL_BORDER);
+ textFieldPanel.add(this.textFieldLabel, BorderLayout.NORTH);
+
+ this.textField = new JTextField();
+ this.textField.getDocument().addDocumentListener(this.textFieldListener);
+ this.textFieldLabel.setLabelFor(this.textField);
+ textFieldPanel.add(this.textField, BorderLayout.CENTER);
+
+ this.add(textFieldPanel, BorderLayout.NORTH);
+
+ // list box
+ JPanel listBoxPanel = new JPanel(new BorderLayout());
+ this.listBoxLabel = new JLabel();
+ this.listBoxLabel.setBorder(LIST_BOX_LABEL_BORDER);
+ listBoxPanel.add(this.listBoxLabel, BorderLayout.NORTH);
+
+ this.listBox = new JList();
+ this.listBox.setDoubleBuffered(true);
+ this.listBox.setModel(this.buildPartialArrayListModel(this.completeList, this.max()));
+ this.listBox.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ // performance tweak(?)
+ this.listBox.setPrototypeCellValue(this.prototypeCellValue());
+ this.listBox.setPrototypeCellValue(null);
+ this.listBox.setCellRenderer(this.buildDefaultCellRenderer());
+ this.listBoxLabel.setLabelFor(this.listBox);
+ // bug 2777802 - scroll bars shouldn't be on the tab sequence
+ JScrollPane listBoxScrollPane = new JScrollPane(this.listBox);
+ listBoxScrollPane.getHorizontalScrollBar().setFocusable(false);
+ listBoxScrollPane.getVerticalScrollBar().setFocusable(false);
+ listBoxPanel.add(listBoxScrollPane, BorderLayout.CENTER);
+
+ // initialize the widgets
+ this.listBox.setSelectedValue(initialSelection, true);
+ this.textField.select(0, this.textField.getText().length());
+
+ this.add(listBoxPanel, BorderLayout.CENTER);
+ }
+
+
+ // ********** public API **********
+
+ public Object getSelection() {
+ return this.listBox.getSelectedValue();
+ }
+
+ public void setSelection(Object selection) {
+ this.listBox.setSelectedValue(selection, true);
+ }
+
+ public Object[] getCompleteList() {
+ return this.completeList;
+ }
+
+ /**
+ * rebuild the filtering buffer and re-apply the filter
+ * to the new list
+ */
+ public void setCompleteList(Object[] completeList) {
+ this.completeList = completeList;
+ if (this.buffer.length < this.max()) {
+ // the buffer will never shrink - might want to re-consider... -bjv
+ this.buffer = new Object[this.max()];
+ }
+ this.filterList();
+ }
+
+ public int getMaxListSize() {
+ return this.maxListSize;
+ }
+
+ public void setMaxListSize(int maxListSize) {
+ this.maxListSize = maxListSize;
+ if (this.buffer.length < this.max()) {
+ // the buffer will never shrink - might want to re-consider... -bjv
+ this.buffer = new Object[this.max()];
+ }
+ this.filterList();
+ }
+
+ public StringConverter getStringConverter() {
+ return this.stringConverter;
+ }
+
+ /**
+ * apply the new filter to the list
+ */
+ public void setStringConverter(StringConverter stringConverter) {
+ this.stringConverter = stringConverter;
+ this.filterList();
+ }
+
+ /**
+ * allow client code to access the text field
+ * (so we can set the focus)
+ */
+ public JTextField getTextField() {
+ return this.textField;
+ }
+
+ /**
+ * allow client code to access the text field label
+ */
+ public JLabel getTextFieldLabel() {
+ return this.textFieldLabel;
+ }
+
+ /**
+ * convenience method
+ */
+ public void setTextFieldLabelText(String text) {
+ this.textFieldLabel.setText(text);
+ }
+
+ /**
+ * allow client code to access the list box
+ * (so we can add mouse listeners for double-clicking)
+ */
+ public JList getListBox() {
+ return this.listBox;
+ }
+
+ /**
+ * convenience method
+ */
+ public void setListBoxCellRenderer(ListCellRenderer renderer) {
+ this.listBox.setCellRenderer(renderer);
+ }
+
+ /**
+ * allow client code to access the list box label
+ */
+ public JLabel getListBoxLabel() {
+ return this.listBoxLabel;
+ }
+
+ /**
+ * convenience method
+ */
+ public void setListBoxLabelText(String text) {
+ this.listBoxLabel.setText(text);
+ }
+
+ /**
+ * convenience method
+ */
+ public void setComponentsFont(Font font) {
+ this.textFieldLabel.setFont(font);
+ this.textField.setFont(font);
+ this.listBoxLabel.setFont(font);
+ this.listBox.setFont(font);
+ }
+
+ public StringMatcher getStringMatcher() {
+ return this.stringMatcher;
+ }
+
+ /**
+ * re-apply the filter to the list
+ */
+ public void setStringMatcher(StringMatcher stringMatcher) {
+ this.stringMatcher = stringMatcher;
+ this.filterList();
+ }
+
+
+ // ********** internal methods **********
+
+ /**
+ * Allow subclasses to disable performance tweak
+ * by returning null here.
+ */
+ protected String prototypeCellValue() {
+ return "==========> A_STRING_THAT_IS_DEFINITELY_LONGER_THAN_EVERY_STRING_IN_THE_LIST <==========";
+ }
+
+ /**
+ * By default, use the string converter to build the text
+ * used by the list box's cell renderer.
+ */
+ protected ListCellRenderer buildDefaultCellRenderer() {
+ return new SimpleListCellRenderer() {
+ @Override
+ protected String buildText(Object value) {
+ return FilteringListPanel.this.stringConverter.convertToString(value);
+ }
+ };
+ }
+
+ /**
+ * Something has changed that requires us to filter the list.
+ *
+ * This method is synchronized because a fast typist can
+ * generate events quicker than we can filter the list. (? -bjv)
+ */
+ synchronized void filterList() {
+ // temporarily stop listening to the list box selection, since we will
+ // be changing the selection during the filtering and don't want
+ // that to affect the text field
+ this.filterList(this.textField.getText());
+ }
+
+ /**
+ * Filter the contents of the list box to match the
+ * specified pattern.
+ */
+ private void filterList(String pattern) {
+ if (pattern.length() == 0) {
+ this.listBox.setModel(this.buildPartialArrayListModel(this.completeList, this.max()));
+ } else {
+ this.stringMatcher.setPatternString(pattern);
+ int j = 0;
+ int len = this.completeList.length;
+ int max = this.max();
+ for (int i = 0; i < len; i++) {
+ if (this.stringMatcher.matches(this.stringConverter.convertToString(this.completeList[i]))) {
+ this.buffer[j++] = this.completeList[i];
+ }
+ if (j == max) {
+ break;
+ }
+ }
+ this.listBox.setModel(this.buildPartialArrayListModel(this.buffer, j));
+ }
+
+ // after filtering the list, determine the appropriate selection
+ if (this.listBox.getModel().getSize() == 0) {
+ this.listBox.getSelectionModel().clearSelection();
+ } else {
+ this.listBox.getSelectionModel().setAnchorSelectionIndex(0);
+ this.listBox.getSelectionModel().setLeadSelectionIndex(0);
+ this.listBox.ensureIndexIsVisible(0);
+ }
+ }
+
+ /**
+ * Build a list model that wraps only a portion of the specified array.
+ * The model will include the array entries from 0 to (size - 1).
+ */
+ private ListModel buildPartialArrayListModel(final Object[] array, final int size) {
+ return new AbstractListModel() {
+ public int getSize() {
+ return size;
+ }
+ public Object getElementAt(int index) {
+ return array[index];
+ }
+ };
+ }
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/ListChooser.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/ListChooser.java
new file mode 100644
index 0000000..52f8c8f
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/ListChooser.java
@@ -0,0 +1,427 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.AWTEvent;
+import java.awt.AWTException;
+import java.awt.Component;
+import java.awt.EventQueue;
+import java.awt.Point;
+import java.awt.Robot;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+
+import javax.swing.ComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingConstants;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.plaf.basic.BasicComboBoxUI;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+
+/**
+ * This component provides a way to handle selecting an item from a
+ * list that may grow too large to be handled conveniently by a combo-box.
+ * If the list's size is less than the designated "long" list size,
+ * the choice list will be displayed in a normal combo-box popup;
+ * otherwise, a dialog will be used to prompt the user to choose a selection.
+ *
+ * To change the browse mechanism, subclasses may
+ * - override the method #buildBrowser()
+ * - override the method #browse(), in which case the method
+ * #buildBrowser() may be ignored.
+ */
+public class ListChooser
+ extends JComboBox
+{
+
+ /** the size of a "long" list - anything smaller is a "short" list */
+ int longListSize = DEFAULT_LONG_LIST_SIZE;
+
+ /** the default size of a "long" list, which is 20 (to match JOptionPane's behavior) */
+ public static final int DEFAULT_LONG_LIST_SIZE = 20;
+
+ /** property change associated with long list size */
+ public static final String LONG_LIST_SIZE_PROPERTY = "longListSize";
+
+ static JLabel prototypeLabel = new JLabel("Prototype", new EmptyIcon(17), SwingConstants.LEADING);
+
+ /**
+ * whether the chooser is choosable. if a chooser is not choosable,
+ * it only serves as a display widget. a user may not change its
+ * selected value.
+ */
+ boolean choosable = true;
+
+ /** property change associated with choosable */
+ public static final String CHOOSABLE_PROPERTY = "choosable";
+
+ /** the browser used to make a selection from the long list - typically via a dialog */
+ private ListBrowser browser;
+
+ private NodeSelector nodeSelector;
+
+ /** INTERNAL - The popup is being shown. Used to prevent infinite loop. */
+ boolean popupAlreadyInProgress;
+
+
+ // **************** Constructors ******************************************
+
+ /**
+ * Construct a list chooser for the specified model.
+ */
+ public ListChooser(ComboBoxModel model) {
+ this(model, new NodeSelector.DefaultNodeSelector());
+ }
+
+ public ListChooser(CachingComboBoxModel model) {
+ this(model, new NodeSelector.DefaultNodeSelector());
+ }
+
+ public ListChooser(ComboBoxModel model, NodeSelector nodeSelector) {
+ this(new NonCachingComboBoxModel(model), nodeSelector);
+ }
+
+ public ListChooser(CachingComboBoxModel model, NodeSelector nodeSelector) {
+ super(model);
+ this.initialize();
+ this.nodeSelector = nodeSelector;
+ }
+ // **************** Initialization ****************************************
+
+ protected void initialize() {
+ this.addPopupMenuListener(this.buildPopupMenuListener());
+ this.setRenderer(new DefaultListCellRenderer());
+ this.addKeyListener(buildF3KeyListener());
+
+ //These are used to workaround problems with Swing trying to
+ //determine the size of a comboBox with a large model
+ setPrototypeDisplayValue(prototypeLabel);
+ getListBox().setPrototypeCellValue(prototypeLabel);
+ }
+
+
+ private JList getListBox() {
+ return (JList) ClassTools.getFieldValue(this.ui, "listBox");
+ }
+
+ /**
+ * When the popup is about to be shown, the event is consumed, and
+ * PopupHandler determines whether to reshow the popup or to show
+ * the long list browser.
+ */
+ private PopupMenuListener buildPopupMenuListener() {
+ return new PopupMenuListener() {
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+ ListChooser.this.aboutToShowPopup();
+ }
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+ // do nothing
+ }
+ public void popupMenuCanceled(PopupMenuEvent e) {
+ // do nothing
+ }
+ @Override
+ public String toString() {
+ return "pop-up menu listener";
+ }
+ };
+ }
+
+ /**
+ * If this code is being reached due to the PopupHandler already being in progress,
+ * then do nothing. Otherwise, set the flag to true and launch the PopupHandler.
+ */
+ void aboutToShowPopup() {
+ if (this.popupAlreadyInProgress) {
+ return;
+ }
+
+ this.popupAlreadyInProgress = true;
+ EventQueue.invokeLater(new PopupHandler());
+ }
+
+
+ private KeyListener buildF3KeyListener() {
+ return new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_F3) {
+ goToSelectedItem();
+ }
+ }
+ @Override
+ public String toString() {
+ return "F3 key listener";
+ }
+ };
+ }
+
+ public void goToSelectedItem() {
+ if (getSelectedItem() != null) {
+ ListChooser.this.nodeSelector.selectNodeFor(getSelectedItem());
+ }
+ }
+
+ // **************** Browsing **********************************************
+
+ /**
+ * Lazily initialize because subclasses may have further initialization to do
+ * before browser can be built.
+ */
+ protected void browse() {
+ if (this.browser == null) {
+ this.browser = this.buildBrowser();
+ }
+
+ this.browser.browse(this);
+ }
+
+ /**
+ * Return the "browser" used to make a selection from the long list,
+ * typically via a dialog.
+ */
+ protected ListChooser.ListBrowser buildBrowser() {
+ return new SimpleListBrowser();
+ }
+
+
+ // **************** Choosable functionality *******************************
+
+ /** override behavior - consume selection if chooser is not choosable */
+ @Override
+ public void setSelectedIndex(int anIndex) {
+ if (this.choosable) {
+ super.setSelectedIndex(anIndex);
+ }
+ }
+
+ private void updateArrowButton() {
+ try {
+ BasicComboBoxUI comboBoxUi = (BasicComboBoxUI) ListChooser.this.getUI();
+ JButton arrowButton = (JButton) ClassTools.getFieldValue(comboBoxUi, "arrowButton");
+ arrowButton.setEnabled(this.isEnabled() && this.choosable);
+ }
+ catch (Exception e) {
+ // this is a huge hack to try and make the combo box look right,
+ // so if it doesn't work, just swallow the exception
+ }
+ }
+
+
+ // **************** List Caching *******************************
+
+ void cacheList() {
+ ((CachingComboBoxModel) getModel()).cacheList();
+ }
+
+ void uncacheList() {
+ ((CachingComboBoxModel) getModel()).uncacheList();
+ }
+
+ boolean listIsCached() {
+ return ((CachingComboBoxModel) getModel()).isCached();
+ }
+
+ // **************** Public ************************************************
+
+ public int getLongListSize() {
+ return this.longListSize;
+ }
+
+ public void setLongListSize(int newLongListSize) {
+ int oldLongListSize = this.longListSize;
+ this.longListSize = newLongListSize;
+ this.firePropertyChange(LONG_LIST_SIZE_PROPERTY, oldLongListSize, newLongListSize);
+ }
+
+ public boolean isChoosable() {
+ return this.choosable;
+ }
+
+ public void setChoosable(boolean newValue) {
+ boolean oldValue = this.choosable;
+ this.choosable = newValue;
+ this.firePropertyChange(CHOOSABLE_PROPERTY, oldValue, newValue);
+ this.updateArrowButton();
+ }
+
+ // **************** Handle selecting null as a value **********************
+
+ private boolean selectedIndexIsNoneSelectedItem(int index) {
+ return index == -1 &&
+ getModel().getSize() > 0 &&
+ getModel().getElementAt(0) == null;
+ }
+
+ @Override
+ public int getSelectedIndex() {
+ boolean listNotCached = !listIsCached();
+ if (listNotCached) {
+ cacheList();
+ }
+
+ int index = super.getSelectedIndex();
+
+ // Use index 0 to show the <none selected> item since the actual value is
+ // null and JComboBox does not handle null values
+ if (selectedIndexIsNoneSelectedItem(index)) {
+ index = 0;
+ }
+
+ if (listNotCached) {
+ uncacheList();
+ }
+ return index;
+ }
+
+ //wrap the renderer to deal with the prototypeDisplayValue
+ @Override
+ public void setRenderer(final ListCellRenderer aRenderer) {
+ super.setRenderer(new ListCellRenderer(){
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ if (value == prototypeLabel) {
+ return prototypeLabel;
+ }
+ return aRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ }
+ });
+ }
+
+
+ // **************** Member classes ****************************************
+
+ /**
+ * Define the API required by this ListChooser when it must
+ * prompt the user to select an item from the "long" list.
+ */
+ public interface ListBrowser
+ {
+ /**
+ * Prompt the user to make a selection from the specified
+ * combo-box's model.
+ */
+ void browse(ListChooser parentChooser);
+ }
+
+
+ /**
+ * Runnable class that consumes popup window and determines whether
+ * to reshow popup or to launch browser, based on the size of the list.
+ */
+ private class PopupHandler
+ implements Runnable
+ {
+ /** The mouse event */
+ private MouseEvent lastMouseEvent;
+
+ /** The component from which the last mouse event was thrown */
+ private JComponent eventComponent;
+
+ /** The location of the component at the time the last mouse event was thrown */
+ private Point componentLocation;
+
+ /** The location of the mouse at the time the last mouse event was thrown */
+ private Point mouseLocation;
+
+
+ PopupHandler() {
+ this.initialize();
+ }
+
+ private void initialize() {
+ AWTEvent event = EventQueue.getCurrentEvent();
+
+ if (event instanceof MouseEvent) {
+ this.lastMouseEvent = (MouseEvent) event;
+ this.eventComponent = (JComponent) this.lastMouseEvent.getSource();
+ this.componentLocation = this.eventComponent.getLocationOnScreen();
+ this.mouseLocation = this.lastMouseEvent.getPoint();
+ }
+ else {
+ this.eventComponent = null;
+ this.componentLocation = null;
+ this.mouseLocation = null;
+ }
+ }
+
+ public void run() {
+ ListChooser.this.hidePopup();
+
+ cacheList();
+ if (ListChooser.this.choosable == true) {
+ // If the combo box model is of sufficient length, the browser will be shown.
+ // Asking the combo box model for its size should be enough to ensure that
+ // its size is recalculated.
+ if (ListChooser.this.getModel().getSize() > ListChooser.this.longListSize) {
+ this.checkComboBoxButton();
+ ListChooser.this.browse();
+ }
+ else {
+ ListChooser.this.showPopup();
+ this.checkMousePosition();
+ }
+ }
+ if (listIsCached()) {
+ uncacheList();
+ }
+
+ ListChooser.this.popupAlreadyInProgress = false;
+ }
+
+ /** If this is not done, the button never becomes un-pressed */
+ private void checkComboBoxButton() {
+ try {
+ BasicComboBoxUI comboBoxUi = (BasicComboBoxUI) ListChooser.this.getUI();
+ JButton arrowButton = (JButton) ClassTools.getFieldValue(comboBoxUi, "arrowButton");
+ arrowButton.getModel().setPressed(false);
+ }
+ catch (Exception e) {
+ // this is a huge hack to try and make the combo box look right,
+ // so if it doesn't work, just swallow the exception
+ }
+ }
+
+ /**
+ * Moves the mouse back to its original position before any jiggery pokery that we've done.
+ */
+ private void checkMousePosition() {
+ if (this.eventComponent == null) {
+ return;
+ }
+
+ final Point newComponentLocation = this.eventComponent.getLocationOnScreen();
+ boolean componentMoved =
+ newComponentLocation.x - this.componentLocation.x != 0
+ || newComponentLocation.y - this.componentLocation.y != 0;
+
+ if (componentMoved) {
+ try {
+ new Robot().mouseMove(
+ newComponentLocation.x + this.mouseLocation.x,
+ newComponentLocation.y + this.mouseLocation.y
+ );
+ }
+ catch (AWTException ex) {
+ // move failed - do nothing
+ }
+ }
+ }
+ }
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/NodeSelector.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/NodeSelector.java
new file mode 100644
index 0000000..f8b4d14
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/NodeSelector.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+/**
+ * This will be called when the user presses F3 or chooses
+ * 'Go To' in the context menu
+ */
+public interface NodeSelector
+{
+ /**
+ * Select the appropriate Node in the tree or the editor panel.
+ */
+ void selectNodeFor(Object item);
+
+ /**
+ * This NodeSelector will do nothing when selectNodeFor(Object) is called
+ */
+ class DefaultNodeSelector implements NodeSelector {
+
+ public void selectNodeFor(Object item) {
+ //default is to do nothing
+ }
+ }
+}
\ No newline at end of file
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/NonCachingComboBoxModel.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/NonCachingComboBoxModel.java
new file mode 100644
index 0000000..ee72264
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/NonCachingComboBoxModel.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import javax.swing.ComboBoxModel;
+import javax.swing.event.ListDataListener;
+
+/**
+ * This implementation of the CachingComboBoxModel interface can be used
+ * whenever there is no need for caching (i.e. the contents of the selection
+ * list can be generated with little latency). All the normal ComboBoxModel
+ * behavior is delegated to a client-supplied ComboBoxModel.
+ */
+public class NonCachingComboBoxModel implements CachingComboBoxModel {
+ private ComboBoxModel wrappedComboBoxModel;
+
+ public NonCachingComboBoxModel(ComboBoxModel wrappedComboBoxModel) {
+ this.wrappedComboBoxModel = wrappedComboBoxModel;
+ }
+
+
+ // ********** CachingComboBoxModel implementation **********
+
+ public void cacheList() {
+ //do nothing
+ }
+
+ public void uncacheList() {
+ //do nothing
+ }
+
+ public boolean isCached() {
+ return false;
+ }
+
+
+ // ********** ComboBoxModel implementation **********
+
+ public void setSelectedItem(Object anItem) {
+ this.wrappedComboBoxModel.setSelectedItem(anItem);
+ }
+
+ public Object getSelectedItem() {
+ return this.wrappedComboBoxModel.getSelectedItem();
+ }
+
+
+ // ********** ListModel implementation **********
+
+ public int getSize() {
+ return this.wrappedComboBoxModel.getSize();
+ }
+
+ public Object getElementAt(int index) {
+ return this.wrappedComboBoxModel.getElementAt(index);
+ }
+
+ public void addListDataListener(ListDataListener l) {
+ this.wrappedComboBoxModel.addListDataListener(l);
+ }
+
+ public void removeListDataListener(ListDataListener l) {
+ this.wrappedComboBoxModel.removeListDataListener(l);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleDisplayable.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleDisplayable.java
new file mode 100644
index 0000000..d46d8e1
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleDisplayable.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import javax.swing.Icon;
+
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+
+/**
+ * This implementation of Displayable converts any Object
+ * to a Displayable. Subclass it to override #displayString() and
+ * #icon() if necessary. Change notification will be fired if the
+ * object is changed.
+ *
+ * This can be used for Strings - the display string
+ * will simply be the String itself.
+ */
+public class SimpleDisplayable
+ extends AbstractModel
+ implements Displayable
+{
+ /** The object to be converted to a Displayable. */
+ protected Object object;
+
+
+ /**
+ * Construct a displayable for the specified object.
+ */
+ public SimpleDisplayable(Object object) {
+ super();
+ this.object = object;
+ }
+
+ public SimpleDisplayable(boolean b) {
+ this(Boolean.valueOf(b));
+ }
+
+ public SimpleDisplayable(char c) {
+ this(new Character(c));
+ }
+
+ public SimpleDisplayable(byte b) {
+ this(new Byte(b));
+ }
+
+ public SimpleDisplayable(short s) {
+ this(new Short(s));
+ }
+
+ public SimpleDisplayable(int i) {
+ this(new Integer(i));
+ }
+
+ public SimpleDisplayable(long l) {
+ this(new Long(l));
+ }
+
+ public SimpleDisplayable(float f) {
+ this(new Float(f));
+ }
+
+ public SimpleDisplayable(double d) {
+ this(new Double(d));
+ }
+
+
+ // ********** Displayable implementation **********
+
+ public String displayString() {
+ return this.object.toString();
+ }
+
+ public Icon icon() {
+ return null;
+ }
+
+
+ // ********** Comparable implementation **********
+
+ public int compareTo(Displayable o) {
+ return DEFAULT_COMPARATOR.compare(this, o);
+ }
+
+
+ // ********** accessors **********
+
+ public Object getObject() {
+ return this.object;
+ }
+
+ public void setObject(Object object) {
+ String oldDisplayString = this.displayString();
+ Icon oldIcon = this.icon();
+ this.object = object;
+ this.firePropertyChanged(DISPLAY_STRING_PROPERTY, oldDisplayString, this.displayString());
+ this.firePropertyChanged(ICON_PROPERTY, oldIcon, this.icon());
+ }
+
+ public boolean getBoolean() {
+ return ((Boolean) this.object).booleanValue();
+ }
+
+ public void setBoolean(boolean b) {
+ this.setObject(Boolean.valueOf(b));
+ }
+
+ public char getChar() {
+ return ((Character) this.object).charValue();
+ }
+
+ public void setChar(char c) {
+ this.setObject(new Character(c));
+ }
+
+ public byte getByte() {
+ return ((Byte) this.object).byteValue();
+ }
+
+ public void setByte(byte b) {
+ this.setObject(new Byte(b));
+ }
+
+ public short getShort() {
+ return ((Short) this.object).shortValue();
+ }
+
+ public void setShort(short s) {
+ this.setObject(new Short(s));
+ }
+
+ public int getInt() {
+ return ((Integer) this.object).intValue();
+ }
+
+ public void setInt(int i) {
+ this.setObject(new Integer(i));
+ }
+
+ public long getLong() {
+ return ((Long) this.object).longValue();
+ }
+
+ public void setLong(long l) {
+ this.setObject(new Long(l));
+ }
+
+ public float getFloat() {
+ return ((Float) this.object).floatValue();
+ }
+
+ public void setFloat(float f) {
+ this.setObject(new Float(f));
+ }
+
+ public double getDouble() {
+ return ((Double) this.object).doubleValue();
+ }
+
+ public void setDouble(double d) {
+ this.setObject(new Double(d));
+ }
+
+
+ // ********** override methods **********
+
+ @Override
+ public void toString(StringBuilder sb) {
+ sb.append(this.object);
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleListBrowser.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleListBrowser.java
new file mode 100644
index 0000000..7215c6a
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleListBrowser.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JOptionPane;
+import javax.swing.ListModel;
+
+/**
+ * This implementation of ListChooser.Browser uses a
+ * JOptionPane to prompt the user for the selection. Subclasses
+ * can change the dialog's title, message, and/or icon.
+ */
+public class SimpleListBrowser
+ implements ListChooser.ListBrowser
+{
+ /** Default constructor */
+ protected SimpleListBrowser() {
+ super();
+ }
+
+ /**
+ * Prompt the user using a JOptionPane.
+ */
+ public void browse(ListChooser chooser) {
+ Object selection =
+ JOptionPane.showInputDialog(
+ chooser,
+ this.message(chooser),
+ this.title(chooser),
+ this.messageType(chooser),
+ this.icon(chooser),
+ this.selectionValues(chooser),
+ this.initialSelectionValue(chooser)
+ );
+
+ if (selection != null) {
+ chooser.getModel().setSelectedItem(selection);
+ }
+ }
+
+ protected Object message(JComboBox comboBox) {
+ return null;
+ }
+
+ protected String title(JComboBox comboBox) {
+ return null;
+ }
+
+ protected int messageType(JComboBox comboBox) {
+ return JOptionPane.QUESTION_MESSAGE;
+ }
+
+ protected Icon icon(JComboBox comboBox) {
+ return null;
+ }
+
+ protected Object[] selectionValues(JComboBox comboBox) {
+ return this.convertToArray(comboBox.getModel());
+ }
+
+ protected Object initialSelectionValue(JComboBox comboBox) {
+ return comboBox.getModel().getSelectedItem();
+ }
+
+ /**
+ * Convert the list of objects in the specified list model
+ * into an array.
+ */
+ protected Object[] convertToArray(ListModel model) {
+ int size = model.getSize();
+ Object[] result = new Object[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = model.getElementAt(i);
+ }
+ return result;
+ }
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleListCellRenderer.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleListCellRenderer.java
new file mode 100644
index 0000000..facf5d3
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SimpleListCellRenderer.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.Component;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.JList;
+
+/**
+ * This renderer should behave the same as the DefaultListCellRenderer;
+ * but it slightly refactors the calculation of the icon and text of the list
+ * cell so that subclasses can easily override the methods that build
+ * the icon and text.
+ *
+ * In most cases, you need only override:
+ * #buildIcon(Object value)
+ * #buildText(Object value)
+ */
+public class SimpleListCellRenderer
+ extends DefaultListCellRenderer
+{
+
+ /**
+ * Construct a simple renderer.
+ */
+ public SimpleListCellRenderer() {
+ super();
+ }
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ // substitute null for the cell value so nothing is drawn initially...
+ super.getListCellRendererComponent(list, null, index, isSelected, cellHasFocus);
+ this.setOpaque(true);
+
+ // ...then set the icon and text manually
+ this.setIcon(this.buildIcon(list, value, index, isSelected, cellHasFocus));
+ this.setText(this.buildText(list, value, index, isSelected, cellHasFocus));
+
+ this.setToolTipText(this.buildToolTipText(list, value, index, isSelected, cellHasFocus));
+
+ // the context will be initialized only if a reader is running
+ if (this.accessibleContext != null) {
+ this.accessibleContext.setAccessibleName(this.buildAccessibleName(list, value, index, isSelected, cellHasFocus));
+ }
+
+ return this;
+ }
+
+ /**
+ * Return the icon representation of the specified cell
+ * value and other settings. (Even more settings are
+ * accessible via inherited getters: hasFocus, isEnabled, etc.)
+ */
+ protected Icon buildIcon(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ return this.buildIcon(value);
+ }
+
+ /**
+ * Return the icon representation of the specified cell
+ * value. The default is to display no icon at all unless the
+ * value itself is an icon.
+ */
+ protected Icon buildIcon(Object value) {
+ // replicate the default behavior
+ return (value instanceof Icon) ? (Icon) value : null;
+ }
+
+ /**
+ * Return the textual representation of the specified cell
+ * value and other settings. (Even more settings are
+ * accessible via inherited getters: hasFocus, isEnabled, etc.)
+ */
+ protected String buildText(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ return this.buildText(value);
+ }
+
+ /**
+ * Return the textual representation of the specified cell
+ * value. The default is to display the object's default string
+ * representation (as returned by #toString()); unless the
+ * value itself is an icon, in which case no text is displayed.
+ */
+ protected String buildText(Object value) {
+ return (value instanceof Icon) ? "" : ((value == null) ? "" : value.toString());
+ }
+
+ /**
+ * Return the text displayed when the cursor lingers over the specified cell.
+ * (Even more settings are accessible via inherited getters: hasFocus, isEnabled, etc.)
+ */
+ protected String buildToolTipText(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ return this.buildToolTipText(value);
+ }
+
+ /**
+ * Return the text displayed when the cursor lingers over the specified cell.
+ */
+ protected String buildToolTipText(Object value) {
+ return null;
+ }
+
+ /**
+ * Return the accessible name to be given to the component used to render
+ * the given value and other settings. (Even more settings are accessible via
+ * inherited getters: hasFocus, isEnabled, etc.)
+ */
+ protected String buildAccessibleName(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ return this.buildAccessibleName(value);
+ }
+
+ /**
+ * Return the accessible name to be given to the component used to render
+ * the given value.
+ */
+ protected String buildAccessibleName(Object value) {
+ return null;
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SpinnerTableCellRenderer.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SpinnerTableCellRenderer.java
new file mode 100644
index 0000000..777d512
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/SpinnerTableCellRenderer.java
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.SpinnerModel;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * Make the cell look like a spinner.
+ */
+public class SpinnerTableCellRenderer implements TableCellEditorAdapter.Renderer {
+
+ /** the component used to paint the cell */
+ protected JSpinner spinner;
+
+ /** the listener to be notified on an immediate edit */
+ protected TableCellEditorAdapter.ImmediateEditListener immediateEditListener;
+
+
+ // ********** constructors/initialization **********
+
+ /**
+ * Construct a cell renderer that uses the default
+ * spinner model, which is a "number" model.
+ */
+ public SpinnerTableCellRenderer() {
+ super();
+ this.initialize();
+ }
+
+ /**
+ * Construct a cell renderer that uses the specified
+ * spinner model, which will determine how the values are displayed.
+ */
+ public SpinnerTableCellRenderer(SpinnerModel model) {
+ this();
+ this.setModel(model);
+ }
+
+ protected void initialize() {
+ this.spinner = this.buildSpinner();
+ }
+
+ protected JSpinner buildSpinner() {
+ JSpinner s = new JSpinner();
+ s.addChangeListener(this.buildChangeListener());
+ return s;
+ }
+
+ private ChangeListener buildChangeListener() {
+ return new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ if (SpinnerTableCellRenderer.this.immediateEditListener != null) {
+ SpinnerTableCellRenderer.this.immediateEditListener.immediateEdit();
+ }
+ }
+ };
+ }
+
+
+ // ********** TableCellRenderer implementation **********
+
+ /**
+ * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ this.spinner.setComponentOrientation(table.getComponentOrientation());
+ this.spinner.setFont(table.getFont());
+ this.spinner.setEnabled(table.isEnabled());
+
+ JComponent editor = this.editor();
+ editor.setForeground(this.foregroundColor(table, value, selected, hasFocus, row, column));
+ editor.setBackground(this.backgroundColor(table, value, selected, hasFocus, row, column));
+ this.spinner.setBorder(this.border(table, value, selected, hasFocus, row, column));
+
+ this.setValue(value);
+ return this.spinner;
+ }
+
+ /**
+ * Return the cell's foreground color.
+ */
+ protected Color foregroundColor(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ if (selected) {
+ if (hasFocus && table.isCellEditable(row, column)) {
+ return UIManager.getColor("Table.focusCellForeground");
+ }
+ return table.getSelectionForeground();
+ }
+ return table.getForeground();
+ }
+
+ /**
+ * Return the cell's background color.
+ */
+ protected Color backgroundColor(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ if (selected) {
+ if (hasFocus && table.isCellEditable(row, column)) {
+ return UIManager.getColor("Table.focusCellBackground");
+ }
+ return table.getSelectionBackground();
+ }
+ return table.getBackground();
+ }
+
+ /**
+ * Return the cell's border.
+ */
+ protected Border border(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
+ if (hasFocus) {
+ return UIManager.getBorder("Table.focusCellHighlightBorder");
+ }
+ if (selected) {
+ return BorderFactory.createLineBorder(table.getSelectionBackground(), 1);
+ }
+ return BorderFactory.createLineBorder(table.getBackground(), 1);
+ }
+
+ /**
+ * Return the editor component whose colors should be set
+ * by the renderer.
+ */
+ protected JComponent editor() {
+ JComponent editor = this.spinner.getEditor();
+ if (editor instanceof JSpinner.DefaultEditor) {
+ // typically, the editor will be the default or one of its subclasses...
+ editor = ((JSpinner.DefaultEditor) editor).getTextField();
+ }
+ return editor;
+ }
+
+ /**
+ * Set the spinner's value
+ */
+ protected void setValue(Object value) {
+ // CR#3999318 - This null check needs to be removed once JDK bug is fixed
+ if (value == null) {
+ value = new Integer(0);
+ }
+ this.spinner.setValue(value);
+ }
+
+
+ // ********** TableCellEditorAdapter.Renderer implementation **********
+
+ /**
+ * @see TableCellEditorAdapter#getValue()
+ */
+ public Object getValue() {
+ return this.spinner.getValue();
+ }
+
+ /**
+ * @see TableCellEditorAdapter#setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener listener)
+ */
+ public void setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener listener) {
+ this.immediateEditListener = listener;
+ }
+
+
+ // ********** public API **********
+
+ /**
+ * Set the spinner's model.
+ */
+ public void setModel(SpinnerModel model) {
+ this.spinner.setModel(model);
+ }
+
+ /**
+ * Return the renderer's preferred height. This allows you
+ * to set the row height to something the spinner will look good in....
+ */
+ public int getPreferredHeight() {
+ // add in space for the border top and bottom
+ return (int) this.spinner.getPreferredSize().getHeight() + 2;
+ }
+
+}
diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/TableCellEditorAdapter.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/TableCellEditorAdapter.java
new file mode 100644
index 0000000..3368f72
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/swing/TableCellEditorAdapter.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.internal.swing;
+
+import java.awt.Component;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JTable;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableCellRenderer;
+
+/**
+ * A table cell editor that wraps a table cell renderer.
+ */
+public class TableCellEditorAdapter extends AbstractCellEditor implements TableCellEditor {
+
+ /** delegate to a renderer */
+ private Renderer renderer;
+
+
+ // ********** constructors/initialization **********
+
+ private TableCellEditorAdapter() {
+ super();
+ }
+
+ /**
+ * Construct a cell editor that behaves like the specified renderer.
+ */
+ public TableCellEditorAdapter(Renderer renderer) {
+ this();
+ this.initialize(renderer);
+ }
+
+ protected void initialize(Renderer r) {
+ this.renderer = r;
+ r.setImmediateEditListener(this.buildImmediateEditListener());
+ }
+
+ private ImmediateEditListener buildImmediateEditListener() {
+ return new ImmediateEditListener() {
+ public void immediateEdit() {
+ TableCellEditorAdapter.this.stopCellEditing();
+ }
+ };
+ }
+
+
+ // ********** CellEditor implementation **********
+
+ /**
+ * @see javax.swing.CellEditor#getCellEditorValue()
+ */
+ public Object getCellEditorValue() {
+ return this.renderer.getValue();
+ }
+
+
+ // ********** TableCellEditor implementation **********
+
+ /**
+ * @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable, java.lang.Object, boolean, int, int)
+ */
+ public Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int row, int column) {
+ return this.renderer.getTableCellRendererComponent(table, value, selected, true, row, column);
+ }
+
+
+
+ // ********** Member classes **********************************************
+
+ /**
+ * This interface defines the methods that must be implemented by a renderer
+ * that can be wrapped by a TableCellEditorAdapter.
+ */
+ public interface Renderer extends TableCellRenderer {
+
+ /**
+ * Return the current value of the renderer.
+ */
+ Object getValue();
+
+ /**
+ * Set the immediate edit listener
+ */
+ void setImmediateEditListener(ImmediateEditListener listener);
+ }
+
+
+ public interface ImmediateEditListener {
+
+ /**
+ * Called when the renderer does an "immediate edit"
+ */
+ void immediateEdit();
+ }
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemCollectionListValueModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemCollectionListValueModelAdapterTests.java
index 1886a8c..aabb467 100644
--- a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemCollectionListValueModelAdapterTests.java
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemCollectionListValueModelAdapterTests.java
@@ -16,7 +16,6 @@
import javax.swing.Icon;
import org.eclipse.jpt.utility.internal.Bag;
-import org.eclipse.jpt.utility.internal.Displayable;
import org.eclipse.jpt.utility.internal.HashBag;
import org.eclipse.jpt.utility.internal.model.AbstractModel;
import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
@@ -26,6 +25,7 @@
import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel;
import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.swing.Displayable;
import org.eclipse.jpt.utility.tests.internal.TestTools;
import junit.framework.TestCase;
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemListListValueModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemListListValueModelAdapterTests.java
index 6aa0a15..01889c3 100644
--- a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemListListValueModelAdapterTests.java
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemListListValueModelAdapterTests.java
@@ -16,7 +16,6 @@
import javax.swing.Icon;
import org.eclipse.jpt.utility.internal.Bag;
-import org.eclipse.jpt.utility.internal.Displayable;
import org.eclipse.jpt.utility.internal.HashBag;
import org.eclipse.jpt.utility.internal.model.AbstractModel;
import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
@@ -26,6 +25,7 @@
import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel;
import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.swing.Displayable;
import org.eclipse.jpt.utility.tests.internal.TestTools;
import junit.framework.TestCase;
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemPropertyListValueModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemPropertyListValueModelAdapterTests.java
index e7429dc..98372de 100644
--- a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemPropertyListValueModelAdapterTests.java
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/ItemPropertyListValueModelAdapterTests.java
@@ -20,7 +20,6 @@
import javax.swing.Icon;
import org.eclipse.jpt.utility.internal.Bag;
-import org.eclipse.jpt.utility.internal.Displayable;
import org.eclipse.jpt.utility.internal.HashBag;
import org.eclipse.jpt.utility.internal.model.AbstractModel;
import org.eclipse.jpt.utility.internal.model.value.ItemPropertyListValueModelAdapter;
@@ -29,6 +28,7 @@
import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel;
import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.swing.Displayable;
import org.eclipse.jpt.utility.tests.internal.TestTools;
import junit.framework.TestCase;
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/JptUtilityModelValueTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/JptUtilityModelValueTests.java
index 0aa607d..eae9934 100644
--- a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/JptUtilityModelValueTests.java
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/JptUtilityModelValueTests.java
@@ -10,7 +10,7 @@
package org.eclipse.jpt.utility.tests.internal.model.value;
import org.eclipse.jpt.utility.tests.internal.model.value.prefs.JptUtilityModelValuePrefsTests;
-//import org.eclipse.jpt.utility.tests.internal.model.value.swing.JptUtilityModelValueSwingTests;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.JptUtilityModelValueSwingTests;
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -21,7 +21,7 @@
TestSuite suite = new TestSuite(JptUtilityModelValueTests.class.getPackage().getName());
suite.addTest(JptUtilityModelValuePrefsTests.suite());
-// suite.addTest(JptUtilityModelValueSwingTests.suite());
+ suite.addTest(JptUtilityModelValueSwingTests.suite());
suite.addTestSuite(BufferedPropertyValueModelTests.class);
suite.addTestSuite(CollectionAspectAdapterTests.class);
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/CheckBoxModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/CheckBoxModelAdapterTests.java
new file mode 100644
index 0000000..78fe762
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/CheckBoxModelAdapterTests.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.ButtonModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.CheckBoxModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class CheckBoxModelAdapterTests extends TestCase {
+ private PropertyValueModel booleanHolder;
+ private ButtonModel buttonModelAdapter;
+ boolean eventFired;
+
+ public CheckBoxModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.booleanHolder = new SimplePropertyValueModel(Boolean.TRUE);
+ this.buttonModelAdapter = new CheckBoxModelAdapter(this.booleanHolder);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testSetSelected() throws Exception {
+ this.eventFired = false;
+ this.buttonModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ CheckBoxModelAdapterTests.this.eventFired = true;
+ }
+ });
+ this.buttonModelAdapter.setSelected(false);
+ assertTrue(this.eventFired);
+ assertEquals(Boolean.FALSE, this.booleanHolder.getValue());
+ }
+
+ public void testSetValue() throws Exception {
+ this.eventFired = false;
+ this.buttonModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ CheckBoxModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertTrue(this.buttonModelAdapter.isSelected());
+ this.booleanHolder.setValue(Boolean.FALSE);
+ assertTrue(this.eventFired);
+ assertFalse(this.buttonModelAdapter.isSelected());
+ }
+
+ public void testDefaultValue() throws Exception {
+ this.eventFired = false;
+ this.buttonModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ CheckBoxModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertTrue(this.buttonModelAdapter.isSelected());
+ this.booleanHolder.setValue(null);
+ assertTrue(this.eventFired);
+ assertFalse(this.buttonModelAdapter.isSelected());
+
+ this.eventFired = false;
+ this.booleanHolder.setValue(Boolean.FALSE);
+ assertFalse(this.eventFired);
+ assertFalse(this.buttonModelAdapter.isSelected());
+ }
+
+ public void testHasListeners() throws Exception {
+ SimplePropertyValueModel localBooleanHolder = (SimplePropertyValueModel) this.booleanHolder;
+ assertFalse(localBooleanHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.buttonModelAdapter);
+
+ ChangeListener listener = new TestChangeListener();
+ this.buttonModelAdapter.addChangeListener(listener);
+ assertTrue(localBooleanHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(this.buttonModelAdapter);
+
+ this.buttonModelAdapter.removeChangeListener(listener);
+ assertFalse(localBooleanHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.buttonModelAdapter);
+ }
+
+ private void verifyHasNoListeners(Object model) throws Exception {
+ EventListenerList listenerList = (EventListenerList) ClassTools.getFieldValue(model, "listenerList");
+ assertEquals(0, listenerList.getListenerList().length);
+ }
+
+ private void verifyHasListeners(Object model) throws Exception {
+ EventListenerList listenerList = (EventListenerList) ClassTools.getFieldValue(model, "listenerList");
+ assertFalse(listenerList.getListenerList().length == 0);
+ }
+
+
+ // ********** member class **********
+ private class TestChangeListener implements ChangeListener {
+ TestChangeListener() {
+ super();
+ }
+ public void stateChanged(ChangeEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/CheckBoxModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/CheckBoxModelAdapterUITest.java
new file mode 100644
index 0000000..2ffe323
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/CheckBoxModelAdapterUITest.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ButtonModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.CheckBoxModelAdapter;
+
+
+/**
+ * Play around with a set of check boxes.
+ */
+public class CheckBoxModelAdapterUITest {
+
+ private TestModel testModel;
+ private PropertyValueModel testModelHolder;
+ private PropertyValueModel flag1Holder;
+ private PropertyValueModel flag2Holder;
+ private PropertyValueModel notFlag2Holder;
+ private ButtonModel flag1ButtonModel;
+ private ButtonModel flag2ButtonModel;
+ private ButtonModel notFlag2ButtonModel;
+
+ public static void main(String[] args) throws Exception {
+ new CheckBoxModelAdapterUITest().exec(args);
+ }
+
+ private CheckBoxModelAdapterUITest() {
+ super();
+ }
+
+ private void exec(String[] args) throws Exception {
+ this.testModel = new TestModel(true, true);
+ this.testModelHolder = new SimplePropertyValueModel(this.testModel);
+ this.flag1Holder = this.buildFlag1Holder(this.testModelHolder);
+ this.flag1ButtonModel = this.buildCheckBoxModelAdapter(this.flag1Holder);
+ this.flag2Holder = this.buildFlag2Holder(this.testModelHolder);
+ this.flag2ButtonModel = this.buildCheckBoxModelAdapter(this.flag2Holder);
+ this.notFlag2Holder = this.buildNotFlag2Holder(this.testModelHolder);
+ this.notFlag2ButtonModel = this.buildCheckBoxModelAdapter(this.notFlag2Holder);
+ this.openWindow();
+ }
+
+ private PropertyValueModel buildFlag1Holder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.FLAG1_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return Boolean.valueOf(((TestModel) this.subject).isFlag1());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setFlag1(((Boolean) value).booleanValue());
+ }
+ };
+ }
+
+ private PropertyValueModel buildFlag2Holder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.FLAG2_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return Boolean.valueOf(((TestModel) this.subject).isFlag2());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setFlag2(((Boolean) value).booleanValue());
+ }
+ };
+ }
+
+ private PropertyValueModel buildNotFlag2Holder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.NOT_FLAG2_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return Boolean.valueOf(((TestModel) this.subject).isNotFlag2());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setNotFlag2(((Boolean) value).booleanValue());
+ }
+ };
+ }
+
+ private ButtonModel buildCheckBoxModelAdapter(PropertyValueModel booleanHolder) {
+ return new CheckBoxModelAdapter(booleanHolder);
+ }
+
+ private void openWindow() {
+ JFrame window = new JFrame(this.getClass().getName());
+ window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ window.addWindowListener(this.buildWindowListener());
+ window.getContentPane().add(this.buildMainPanel(), "Center");
+ window.setSize(400, 100);
+ window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildCheckBoxPanel(), BorderLayout.NORTH);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ private Component buildCheckBoxPanel() {
+ JPanel taskListPanel = new JPanel(new GridLayout(1, 0));
+ taskListPanel.add(this.buildFlag1CheckBox());
+ taskListPanel.add(this.buildFlag2CheckBox());
+ taskListPanel.add(this.buildNotFlag2CheckBox());
+ taskListPanel.add(this.buildUnattachedCheckBox());
+ return taskListPanel;
+ }
+
+ private JCheckBox buildFlag1CheckBox() {
+ JCheckBox checkBox = new JCheckBox();
+ checkBox.setText("flag 1");
+ checkBox.setModel(this.flag1ButtonModel);
+ return checkBox;
+ }
+
+ private JCheckBox buildFlag2CheckBox() {
+ JCheckBox checkBox = new JCheckBox();
+ checkBox.setText("flag 2");
+ checkBox.setModel(this.flag2ButtonModel);
+ return checkBox;
+ }
+
+ private JCheckBox buildNotFlag2CheckBox() {
+ JCheckBox checkBox = new JCheckBox();
+ checkBox.setText("not flag 2");
+ checkBox.setModel(this.notFlag2ButtonModel);
+ return checkBox;
+ }
+
+ private JCheckBox buildUnattachedCheckBox() {
+ JCheckBox checkBox = new JCheckBox("unattached");
+ checkBox.getModel().addItemListener(this.buildUnattachedItemListener());
+ return checkBox;
+ }
+
+ private ItemListener buildUnattachedItemListener() {
+ return new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ System.out.println("unattached state changed: " + e);
+ }
+ };
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new GridLayout(1, 0));
+ controlPanel.add(this.buildFlipFlag1Button());
+ controlPanel.add(this.buildClearModelButton());
+ controlPanel.add(this.buildRestoreModelButton());
+ controlPanel.add(this.buildPrintModelButton());
+ return controlPanel;
+ }
+
+ private JButton buildFlipFlag1Button() {
+ return new JButton(this.buildFlipFlag1Action());
+ }
+
+ private Action buildFlipFlag1Action() {
+ Action action = new AbstractAction("flip flag 1") {
+ public void actionPerformed(ActionEvent event) {
+ CheckBoxModelAdapterUITest.this.flipFlag1();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void flipFlag1() {
+ this.testModel.setFlag1( ! this.testModel.isFlag1());
+ }
+
+ private JButton buildClearModelButton() {
+ return new JButton(this.buildClearModelAction());
+ }
+
+ private Action buildClearModelAction() {
+ Action action = new AbstractAction("clear model") {
+ public void actionPerformed(ActionEvent event) {
+ CheckBoxModelAdapterUITest.this.clearModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void clearModel() {
+ this.testModelHolder.setValue(null);
+ }
+
+ private JButton buildRestoreModelButton() {
+ return new JButton(this.buildRestoreModelAction());
+ }
+
+ private Action buildRestoreModelAction() {
+ Action action = new AbstractAction("restore model") {
+ public void actionPerformed(ActionEvent event) {
+ CheckBoxModelAdapterUITest.this.restoreModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void restoreModel() {
+ this.testModelHolder.setValue(this.testModel);
+ }
+
+ private JButton buildPrintModelButton() {
+ return new JButton(this.buildPrintModelAction());
+ }
+
+ private Action buildPrintModelAction() {
+ Action action = new AbstractAction("print model") {
+ public void actionPerformed(ActionEvent event) {
+ CheckBoxModelAdapterUITest.this.printModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void printModel() {
+ System.out.println("flag 1: " + this.testModel.isFlag1());
+ System.out.println("flag 2: " + this.testModel.isFlag2());
+ System.out.println("not flag 2: " + this.testModel.isNotFlag2());
+ System.out.println("***");
+ }
+
+
+ private class TestModel extends AbstractModel {
+ private boolean flag1;
+ public static final String FLAG1_PROPERTY = "flag1";
+ private boolean flag2;
+ public static final String FLAG2_PROPERTY = "flag2";
+ private boolean notFlag2;
+ public static final String NOT_FLAG2_PROPERTY = "notFlag2";
+
+ public TestModel(boolean flag1, boolean flag2) {
+ this.flag1 = flag1;
+ this.flag2 = flag2;
+ this.notFlag2 = ! flag2;
+ }
+ public boolean isFlag1() {
+ return this.flag1;
+ }
+ public void setFlag1(boolean flag1) {
+ boolean old = this.flag1;
+ this.flag1 = flag1;
+ this.firePropertyChanged(FLAG1_PROPERTY, old, flag1);
+ }
+ public boolean isFlag2() {
+ return this.flag2;
+ }
+ public void setFlag2(boolean flag2) {
+ boolean old = this.flag2;
+ this.flag2 = flag2;
+ this.firePropertyChanged(FLAG2_PROPERTY, old, flag2);
+
+ old = this.notFlag2;
+ this.notFlag2 = ! flag2;
+ this.firePropertyChanged(NOT_FLAG2_PROPERTY, old, this.notFlag2);
+ }
+ public boolean isNotFlag2() {
+ return this.notFlag2;
+ }
+ public void setNotFlag2(boolean notFlag2) {
+ this.setFlag2( ! notFlag2);
+ }
+ @Override
+ public String toString() {
+ return "TestModel(" + this.isFlag1() + " - " + this.isFlag2() + ")";
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterTests.java
new file mode 100644
index 0000000..d12a5cb
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterTests.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.swing.ComboBoxModel;
+import javax.swing.ListModel;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.ComboBoxModelAdapter;
+import org.eclipse.jpt.utility.internal.swing.Displayable;
+import org.eclipse.jpt.utility.internal.swing.SimpleDisplayable;
+import org.eclipse.jpt.utility.tests.internal.model.value.SynchronizedList;
+
+import junit.framework.TestCase;
+
+public class ComboBoxModelAdapterTests extends TestCase {
+
+ public ComboBoxModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // nothing yet...
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // nothing yet...
+ super.tearDown();
+ }
+
+ public void testHasListeners() throws Exception {
+ SimpleListValueModel listHolder = this.buildListHolder();
+ assertFalse(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+ SimplePropertyValueModel selectionHolder = new SimplePropertyValueModel(((ListIterator) listHolder.getValue()).next());
+ assertFalse(selectionHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+
+ ComboBoxModel comboBoxModel = new ComboBoxModelAdapter(listHolder, selectionHolder);
+ assertFalse(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+ assertFalse(selectionHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(comboBoxModel);
+
+ SynchronizedList synchList = new SynchronizedList(comboBoxModel);
+ PropertyChangeListener selectionListener = this.buildSelectionListener();
+ selectionHolder.addPropertyChangeListener(ValueModel.VALUE, selectionListener);
+ assertTrue(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+ assertTrue(selectionHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(comboBoxModel);
+
+ comboBoxModel.removeListDataListener(synchList);
+ selectionHolder.removePropertyChangeListener(ValueModel.VALUE, selectionListener);
+ assertFalse(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+ assertFalse(selectionHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(comboBoxModel);
+ }
+
+ private PropertyChangeListener buildSelectionListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent evt) {
+ // do nothing...
+ }
+ };
+ }
+
+ private void verifyHasNoListeners(ListModel listModel) throws Exception {
+ boolean hasNoListeners = ((Boolean) ClassTools.executeMethod(listModel, "hasNoListDataListeners")).booleanValue();
+ assertTrue(hasNoListeners);
+ }
+
+ private void verifyHasListeners(ListModel listModel) throws Exception {
+ boolean hasListeners = ((Boolean) ClassTools.executeMethod(listModel, "hasListDataListeners")).booleanValue();
+ assertTrue(hasListeners);
+ }
+
+ private SimpleListValueModel buildListHolder() {
+ return new SimpleListValueModel(this.buildList());
+ }
+
+ private List<Displayable> buildList() {
+ List<Displayable> list = new ArrayList<Displayable>();
+ this.populateCollection(list);
+ return list;
+ }
+
+ private void populateCollection(Collection<Displayable> c) {
+ c.add(new SimpleDisplayable("foo"));
+ c.add(new SimpleDisplayable("bar"));
+ c.add(new SimpleDisplayable("baz"));
+ c.add(new SimpleDisplayable("joo"));
+ c.add(new SimpleDisplayable("jar"));
+ c.add(new SimpleDisplayable("jaz"));
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterUITest.java
new file mode 100644
index 0000000..8dec2ae
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterUITest.java
@@ -0,0 +1,389 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.UIManager;
+import javax.swing.WindowConstants;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.CollectionTools;
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.ComboBoxModelAdapter;
+import org.eclipse.jpt.utility.internal.swing.FilteringListBrowser;
+import org.eclipse.jpt.utility.internal.swing.ListChooser;
+import org.eclipse.jpt.utility.internal.swing.SimpleListCellRenderer;
+
+
+/**
+ * Play around with a set of combo-boxes.
+ *
+ * DefaultLongListBrowserDialogUITest subclasses this class; so be
+ * careful when making changes.
+ */
+public class ComboBoxModelAdapterUITest {
+
+ protected JFrame window;
+ private TestModel testModel;
+ private PropertyValueModel testModelHolder;
+ private PropertyValueModel colorHolder;
+ private ListValueModel colorListHolder;
+ protected ComboBoxModel colorComboBoxModel;
+ private int nextColorNumber = 0;
+
+ public static void main(String[] args) throws Exception {
+ new ComboBoxModelAdapterUITest().exec(args);
+ }
+
+ protected ComboBoxModelAdapterUITest() {
+ super();
+ }
+
+ protected void exec(String[] args) throws Exception {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+// UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); // Metal LAF
+// UIManager.setLookAndFeel(com.sun.java.swing.plaf.windows.WindowsLookAndFeel.class.getName());
+// UIManager.setLookAndFeel(com.sun.java.swing.plaf.motif.MotifLookAndFeel.class.getName());
+// UIManager.setLookAndFeel(oracle.bali.ewt.olaf.OracleLookAndFeel.class.getName());
+ this.testModel = this.buildTestModel();
+ this.testModelHolder = new SimplePropertyValueModel(this.testModel);
+ this.colorHolder = this.buildColorHolder(this.testModelHolder);
+ this.colorListHolder = this.buildColorListHolder();
+ this.colorComboBoxModel = this.buildComboBoxModelAdapter(this.colorListHolder, this.colorHolder);
+ this.openWindow();
+ }
+
+ private PropertyValueModel buildColorHolder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.COLOR_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((TestModel) this.subject).getColor();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setColor((String) value);
+ }
+ };
+ }
+
+ protected TestModel buildTestModel() {
+ return new TestModel();
+ }
+
+ protected ListValueModel buildColorListHolder() {
+ return new SimpleListValueModel(TestModel.validColors());
+// return new AbstractReadOnlyListValueModel() {
+// public Object getValue() {
+// return new ArrayListIterator(TestModel.VALID_COLORS);
+// }
+// public int size() {
+// return TestModel.VALID_COLORS.length;
+// }
+// };
+ }
+
+ private ComboBoxModel buildComboBoxModelAdapter(ListValueModel listHolder, PropertyValueModel selectionHolder) {
+ return new ComboBoxModelAdapter(listHolder, selectionHolder);
+ }
+
+ private void openWindow() {
+ this.window = new JFrame(ClassTools.shortNameFor(this.getClass()));
+ this.window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ this.window.addWindowListener(this.buildWindowListener());
+ this.window.getContentPane().add(this.buildMainPanel(), "Center");
+ this.window.setLocation(300, 300);
+ this.window.setSize(400, 150);
+ this.window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildComboBoxPanel(), BorderLayout.NORTH);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ protected JPanel buildComboBoxPanel() {
+ JPanel panel = new JPanel(new GridLayout(1, 0));
+ panel.add(this.buildComboBox());
+ panel.add(this.buildComboBox());
+ panel.add(this.buildListChooser1());
+ panel.add(this.buildListChooser2());
+ return panel;
+ }
+
+ private JComboBox buildComboBox() {
+ JComboBox comboBox = new JComboBox(this.colorComboBoxModel);
+ comboBox.setRenderer(this.buildComboBoxRenderer());
+ return comboBox;
+ }
+
+ protected ListCellRenderer buildComboBoxRenderer() {
+ return new SimpleListCellRenderer() {
+ @Override
+ protected String buildText(Object value) {
+ return super.buildText(value);
+ }
+ };
+ }
+
+ private ListChooser buildListChooser1() {
+ return new LocalListChooser1(this.colorComboBoxModel);
+ }
+
+ private ListChooser buildListChooser2() {
+ return new LocalListChooser2(this.colorComboBoxModel);
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new GridLayout(2, 0));
+ controlPanel.add(this.buildResetColorButton());
+ controlPanel.add(this.buildClearModelButton());
+ controlPanel.add(this.buildRestoreModelButton());
+ controlPanel.add(this.buildPrintModelButton());
+ controlPanel.add(this.buildAddTenButton());
+ controlPanel.add(this.buildRemoveTenButton());
+ return controlPanel;
+ }
+
+ // ********** reset color button **********
+ private JButton buildResetColorButton() {
+ return new JButton(this.buildResetColorAction());
+ }
+
+ private Action buildResetColorAction() {
+ Action action = new AbstractAction("reset color") {
+ public void actionPerformed(ActionEvent event) {
+ ComboBoxModelAdapterUITest.this.resetColor();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void resetColor() {
+ this.testModel.setColor(TestModel.DEFAULT_COLOR);
+ }
+
+ // ********** clear model button **********
+ private JButton buildClearModelButton() {
+ return new JButton(this.buildClearModelAction());
+ }
+
+ private Action buildClearModelAction() {
+ Action action = new AbstractAction("clear model") {
+ public void actionPerformed(ActionEvent event) {
+ ComboBoxModelAdapterUITest.this.clearModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void clearModel() {
+ this.testModelHolder.setValue(null);
+ }
+
+ // ********** restore model button **********
+ private JButton buildRestoreModelButton() {
+ return new JButton(this.buildRestoreModelAction());
+ }
+
+ private Action buildRestoreModelAction() {
+ Action action = new AbstractAction("restore model") {
+ public void actionPerformed(ActionEvent event) {
+ ComboBoxModelAdapterUITest.this.restoreModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void restoreModel() {
+ this.testModelHolder.setValue(this.testModel);
+ }
+
+ // ********** print model button **********
+ private JButton buildPrintModelButton() {
+ return new JButton(this.buildPrintModelAction());
+ }
+
+ private Action buildPrintModelAction() {
+ Action action = new AbstractAction("print model") {
+ public void actionPerformed(ActionEvent event) {
+ ComboBoxModelAdapterUITest.this.printModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void printModel() {
+ System.out.println(this.testModel);
+ }
+
+ // ********** add 20 button **********
+ private JButton buildAddTenButton() {
+ return new JButton(this.buildAddTenAction());
+ }
+
+ private Action buildAddTenAction() {
+ Action action = new AbstractAction("add 20") {
+ public void actionPerformed(ActionEvent event) {
+ ComboBoxModelAdapterUITest.this.addTen();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void addTen() {
+ for (int i = this.nextColorNumber; i < this.nextColorNumber + 20; i++) {
+ this.colorListHolder.addItem(this.colorListHolder.size(), "color" + i);
+ }
+ this.nextColorNumber += 20;
+ }
+
+ // ********** remove 20 button **********
+ private JButton buildRemoveTenButton() {
+ return new JButton(this.buildRemoveTenAction());
+ }
+
+ private Action buildRemoveTenAction() {
+ Action action = new AbstractAction("remove 20") {
+ public void actionPerformed(ActionEvent event) {
+ ComboBoxModelAdapterUITest.this.removeTen();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void removeTen() {
+ for (int i = 0; i < 20; i++) {
+ if (this.colorListHolder.size() > 0) {
+ this.colorListHolder.removeItem(this.colorListHolder.size() - 1);
+ }
+ }
+ }
+
+
+ protected static class TestModel extends AbstractModel {
+ private String color;
+ public static final String COLOR_PROPERTY = "color";
+ public static final String RED = "red";
+ public static final String ORANGE = "orange";
+ public static final String YELLOW = "yellow";
+ public static final String GREEN = "green";
+ public static final String BLUE = "blue";
+ public static final String INDIGO = "indigo";
+ public static final String VIOLET = "violet";
+ public static final String DEFAULT_COLOR = RED;
+ public static List<String> validColors;
+ public static final String[] DEFAULT_VALID_COLORS = {
+ RED,
+ ORANGE,
+ YELLOW,
+ GREEN,
+ BLUE,
+ INDIGO,
+ VIOLET
+ };
+
+ public static List<String> validColors() {
+ if (validColors == null) {
+ validColors = buildDefaultValidColors();
+ }
+ return validColors;
+ }
+ public static List<String> buildDefaultValidColors() {
+ List<String> result = new ArrayList<String>();
+ CollectionTools.addAll(result, DEFAULT_VALID_COLORS);
+ return result;
+ }
+
+ public TestModel() {
+ this(DEFAULT_COLOR);
+ }
+ public TestModel(String color) {
+ this.color = color;
+ }
+ public String getColor() {
+ return this.color;
+ }
+ public void setColor(String color) {
+ this.checkColor(color);
+ Object old = this.color;
+ this.color = color;
+ this.firePropertyChanged(COLOR_PROPERTY, old, color);
+ }
+ public void checkColor(String c) {
+ if ( ! validColors().contains(c)) {
+ throw new IllegalArgumentException(c);
+ }
+ }
+ @Override
+ public String toString() {
+ return "TestModel(" + this.color + ")";
+ }
+ }
+
+
+ private class LocalListChooser1 extends ListChooser {
+ public LocalListChooser1(ComboBoxModel model) {
+ super(model);
+ }
+ }
+
+
+ private class LocalListChooser2 extends ListChooser {
+ public LocalListChooser2(ComboBoxModel model) {
+ super(model);
+ }
+ @Override
+ protected ListBrowser buildBrowser() {
+ return new FilteringListBrowser();
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterUITest2.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterUITest2.java
new file mode 100644
index 0000000..db86b84
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ComboBoxModelAdapterUITest2.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.ListCellRenderer;
+
+import org.eclipse.jpt.utility.internal.model.value.ExtendedListValueModelWrapper;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.swing.SimpleListCellRenderer;
+
+/**
+ *
+ */
+public class ComboBoxModelAdapterUITest2 extends ComboBoxModelAdapterUITest {
+
+ public static void main(String[] args) throws Exception {
+ new ComboBoxModelAdapterUITest2().exec(args);
+ }
+
+ public ComboBoxModelAdapterUITest2() {
+ super();
+ }
+
+ /**
+ * add a null to the front of the list
+ */
+ @Override
+ protected ListValueModel buildColorListHolder() {
+ // the default is to prepend the wrapped list with a null item
+ return new ExtendedListValueModelWrapper(super.buildColorListHolder());
+ }
+
+ /**
+ * use a different model that allows the color to be set to null
+ */
+ @Override
+ protected TestModel buildTestModel() {
+ return new TestModel2();
+ }
+
+ /**
+ * convert null to some text
+ */
+ @Override
+ protected ListCellRenderer buildComboBoxRenderer() {
+ return new SimpleListCellRenderer() {
+ @Override
+ protected String buildText(Object value) {
+ return (value == null) ? "<none selected>" : super.buildText(value);
+ }
+ };
+ }
+
+
+ protected static class TestModel2 extends TestModel {
+ /**
+ * null is OK here
+ */
+ @Override
+ public void checkColor(String color) {
+ if (color == null) {
+ return;
+ }
+ super.checkColor(color);
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DateSpinnerModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DateSpinnerModelAdapterTests.java
new file mode 100644
index 0000000..bdad5b7
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DateSpinnerModelAdapterTests.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.util.Date;
+
+import javax.swing.SpinnerModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.DateSpinnerModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class DateSpinnerModelAdapterTests extends TestCase {
+ private PropertyValueModel valueHolder;
+ private SpinnerModel spinnerModelAdapter;
+ boolean eventFired;
+
+ public DateSpinnerModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.valueHolder = new SimplePropertyValueModel(new Date());
+ this.spinnerModelAdapter = new DateSpinnerModelAdapter(this.valueHolder);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testSetValueSpinnerModel() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ DateSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ Date newDate = new Date();
+ newDate.setTime(777777);
+ this.spinnerModelAdapter.setValue(newDate);
+ assertTrue(this.eventFired);
+ assertEquals(777777, ((Date) this.valueHolder.getValue()).getTime());
+ }
+
+ public void testSetValueValueHolder() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ DateSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ Date newDate = new Date();
+ newDate.setTime(777777);
+ this.valueHolder.setValue(newDate);
+ assertTrue(this.eventFired);
+ assertEquals(777777, ((Date) this.spinnerModelAdapter.getValue()).getTime());
+ }
+
+ public void testDefaultValue() throws Exception {
+ Date newDate = new Date();
+ newDate.setTime(777777);
+ this.valueHolder.setValue(newDate);
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ DateSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(777777, ((Date) this.spinnerModelAdapter.getValue()).getTime());
+ this.valueHolder.setValue(null);
+ assertTrue(this.eventFired);
+ assertFalse(((Date) this.spinnerModelAdapter.getValue()).getTime() == 777777);
+ }
+
+ public void testHasListeners() throws Exception {
+ SimplePropertyValueModel localValueHolder = (SimplePropertyValueModel) this.valueHolder;
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+
+ ChangeListener listener = new TestChangeListener();
+ this.spinnerModelAdapter.addChangeListener(listener);
+ assertTrue(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(this.spinnerModelAdapter);
+
+ this.spinnerModelAdapter.removeChangeListener(listener);
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+ }
+
+ private void verifyHasNoListeners(SpinnerModel adapter) throws Exception {
+ assertEquals(0, ((DateSpinnerModelAdapter) adapter).getChangeListeners().length);
+ }
+
+ private void verifyHasListeners(Object adapter) throws Exception {
+ assertFalse(((DateSpinnerModelAdapter) adapter).getChangeListeners().length == 0);
+ }
+
+ public void testNullInitialValue() {
+ Date today = new Date();
+ this.valueHolder = new SimplePropertyValueModel();
+ this.spinnerModelAdapter = new DateSpinnerModelAdapter(this.valueHolder, today);
+
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ DateSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(today, this.spinnerModelAdapter.getValue());
+
+ Date newDate = new Date();
+ newDate.setTime(777777);
+ this.valueHolder.setValue(newDate);
+
+ assertTrue(this.eventFired);
+ assertEquals(777777, ((Date) this.spinnerModelAdapter.getValue()).getTime());
+ }
+
+
+ // ********** inner class **********
+ private class TestChangeListener implements ChangeListener {
+ TestChangeListener() {
+ super();
+ }
+ public void stateChanged(ChangeEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DocumentAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DocumentAdapterTests.java
new file mode 100644
index 0000000..da8d87a
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DocumentAdapterTests.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.DocumentEvent.EventType;
+import javax.swing.text.Document;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.DocumentAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class DocumentAdapterTests extends TestCase {
+ private PropertyValueModel stringHolder;
+ Document documentAdapter;
+ boolean eventFired;
+
+ public DocumentAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.stringHolder = new SimplePropertyValueModel("0123456789");
+ this.documentAdapter = new DocumentAdapter(this.stringHolder);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testRemove() throws Exception {
+ this.eventFired = false;
+ this.documentAdapter.addDocumentListener(new TestDocumentListener() {
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ DocumentAdapterTests.this.eventFired = true;
+ assertEquals(EventType.REMOVE, e.getType());
+ assertEquals(DocumentAdapterTests.this.documentAdapter, e.getDocument());
+ // this will be the removal of "23456"
+ assertEquals(2, e.getOffset());
+ assertEquals(5, e.getLength());
+ }
+ });
+ this.documentAdapter.remove(2, 5);
+ assertTrue(this.eventFired);
+ assertEquals("01789", this.stringHolder.getValue());
+ }
+
+ public void testInsert() throws Exception {
+ this.eventFired = false;
+ this.documentAdapter.addDocumentListener(new TestDocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ DocumentAdapterTests.this.eventFired = true;
+ assertEquals(EventType.INSERT, e.getType());
+ assertEquals(DocumentAdapterTests.this.documentAdapter, e.getDocument());
+ // this will be the insert of "xxxxxx"
+ assertEquals(2, e.getOffset());
+ assertEquals(5, e.getLength());
+ }
+ });
+ this.documentAdapter.insertString(2, "xxxxx", null);
+ assertTrue(this.eventFired);
+ assertEquals("01xxxxx23456789", this.stringHolder.getValue());
+ }
+
+ public void testSetValue() throws Exception {
+ this.eventFired = false;
+ this.documentAdapter.addDocumentListener(new TestDocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ DocumentAdapterTests.this.eventFired = true;
+ assertEquals(EventType.INSERT, e.getType());
+ assertEquals(DocumentAdapterTests.this.documentAdapter, e.getDocument());
+ // this will be the insert of "foo"
+ assertEquals(0, e.getOffset());
+ assertEquals(3, e.getLength());
+ }
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ assertEquals(EventType.REMOVE, e.getType());
+ assertEquals(DocumentAdapterTests.this.documentAdapter, e.getDocument());
+ // this will be the removal of "0123456789"
+ assertEquals(0, e.getOffset());
+ assertEquals(10, e.getLength());
+ }
+ });
+ assertEquals("0123456789", this.documentAdapter.getText(0, this.documentAdapter.getLength()));
+ this.stringHolder.setValue("foo");
+ assertTrue(this.eventFired);
+ assertEquals("foo", this.documentAdapter.getText(0, this.documentAdapter.getLength()));
+ }
+
+ public void testHasListeners() throws Exception {
+ SimplePropertyValueModel localStringHolder = (SimplePropertyValueModel) this.stringHolder;
+ assertFalse(localStringHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.documentAdapter);
+
+ DocumentListener listener = new TestDocumentListener();
+ this.documentAdapter.addDocumentListener(listener);
+ assertTrue(localStringHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(this.documentAdapter);
+
+ this.documentAdapter.removeDocumentListener(listener);
+ assertFalse(localStringHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.documentAdapter);
+ }
+
+ private void verifyHasNoListeners(Object document) throws Exception {
+ Object delegate = ClassTools.getFieldValue(document, "delegate");
+ Object[] listeners = (Object[]) ClassTools.executeMethod(delegate, "getDocumentListeners");
+ assertEquals(0, listeners.length);
+ }
+
+ private void verifyHasListeners(Object document) throws Exception {
+ Object delegate = ClassTools.getFieldValue(document, "delegate");
+ Object[] listeners = (Object[]) ClassTools.executeMethod(delegate, "getDocumentListeners");
+ assertFalse(listeners.length == 0);
+ }
+
+
+private class TestDocumentListener implements DocumentListener {
+ TestDocumentListener() {
+ super();
+ }
+ public void changedUpdate(DocumentEvent e) {
+ fail("unexpected event");
+ }
+ public void insertUpdate(DocumentEvent e) {
+ fail("unexpected event");
+ }
+ public void removeUpdate(DocumentEvent e) {
+ fail("unexpected event");
+ }
+}
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DocumentAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DocumentAdapterUITest.java
new file mode 100644
index 0000000..d6343bd
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/DocumentAdapterUITest.java
@@ -0,0 +1,257 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.WindowConstants;
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.DocumentAdapter;
+
+/**
+ * Play around with a set of entry fields.
+ */
+public class DocumentAdapterUITest {
+
+ private TestModel testModel;
+ private static final String DEFAULT_NAME = "Scooby Doo";
+ private PropertyValueModel testModelHolder;
+ private PropertyValueModel nameHolder;
+ private Document nameDocument;
+ private Document upperCaseNameDocument;
+
+ public static void main(String[] args) throws Exception {
+ new DocumentAdapterUITest().exec(args);
+ }
+
+ private DocumentAdapterUITest() {
+ super();
+ }
+
+ private void exec(String[] args) throws Exception {
+ this.testModel = new TestModel(DEFAULT_NAME);
+ this.testModelHolder = new SimplePropertyValueModel(this.testModel);
+ this.nameHolder = this.buildNameHolder(this.testModelHolder);
+ this.nameDocument = this.buildNameDocument(this.nameHolder);
+ this.upperCaseNameDocument = this.buildUpperCaseNameDocument(this.nameHolder);
+ this.openWindow();
+ }
+
+ private PropertyValueModel buildNameHolder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.NAME_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((TestModel) this.subject).getName();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setName((String) value);
+ }
+ };
+ }
+
+ private Document buildNameDocument(PropertyValueModel stringHolder) {
+ return new DocumentAdapter(stringHolder);
+ }
+
+ private Document buildUpperCaseNameDocument(PropertyValueModel stringHolder) {
+ return new DocumentAdapter(stringHolder, this.buildUpperCaseNameDocumentDelegate());
+ }
+
+ private AbstractDocument buildUpperCaseNameDocumentDelegate() {
+ return new PlainDocument() {
+ @Override
+ public void insertString(int offset, String string, AttributeSet a) throws BadLocationException {
+ if (string == null) {
+ return;
+ }
+ char[] upper = string.toCharArray();
+ for (int i = 0; i < upper.length; i++) {
+ upper[i] = Character.toUpperCase(upper[i]);
+ }
+ super.insertString(offset, new String(upper), a);
+ }
+ };
+ }
+
+ private void openWindow() {
+ JFrame window = new JFrame(this.getClass().getName());
+ window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ window.addWindowListener(this.buildWindowListener());
+ window.getContentPane().add(this.buildMainPanel(), "Center");
+ window.setSize(400, 100);
+ window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildTextFieldPanel(), BorderLayout.NORTH);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ private Component buildTextFieldPanel() {
+ JPanel taskListPanel = new JPanel(new GridLayout(1, 0));
+ taskListPanel.add(this.buildNameTextField());
+ taskListPanel.add(this.buildReadOnlyNameTextField());
+ taskListPanel.add(this.buildUpperCaseNameTextField());
+ return taskListPanel;
+ }
+
+ private JTextField buildNameTextField() {
+ return new JTextField(this.nameDocument, null, 0);
+ }
+
+ private JTextField buildReadOnlyNameTextField() {
+ JTextField nameTextField = this.buildNameTextField();
+ nameTextField.setEditable(false);
+ return nameTextField;
+ }
+
+ private JTextField buildUpperCaseNameTextField() {
+ return new JTextField(this.upperCaseNameDocument, null, 0);
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new GridLayout(1, 0));
+ controlPanel.add(this.buildResetNameButton());
+ controlPanel.add(this.buildClearModelButton());
+ controlPanel.add(this.buildRestoreModelButton());
+ controlPanel.add(this.buildPrintModelButton());
+ return controlPanel;
+ }
+
+ private JButton buildResetNameButton() {
+ return new JButton(this.buildResetNameAction());
+ }
+
+ private Action buildResetNameAction() {
+ Action action = new AbstractAction("reset name") {
+ public void actionPerformed(ActionEvent event) {
+ DocumentAdapterUITest.this.resetName();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void resetName() {
+ this.testModel.setName(DEFAULT_NAME);
+ }
+
+ private JButton buildClearModelButton() {
+ return new JButton(this.buildClearModelAction());
+ }
+
+ private Action buildClearModelAction() {
+ Action action = new AbstractAction("clear model") {
+ public void actionPerformed(ActionEvent event) {
+ DocumentAdapterUITest.this.clearModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void clearModel() {
+ this.testModelHolder.setValue(null);
+ }
+
+ private JButton buildRestoreModelButton() {
+ return new JButton(this.buildRestoreModelAction());
+ }
+
+ private Action buildRestoreModelAction() {
+ Action action = new AbstractAction("restore model") {
+ public void actionPerformed(ActionEvent event) {
+ DocumentAdapterUITest.this.restoreModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void restoreModel() {
+ this.testModelHolder.setValue(this.testModel);
+ }
+
+ private JButton buildPrintModelButton() {
+ return new JButton(this.buildPrintModelAction());
+ }
+
+ private Action buildPrintModelAction() {
+ Action action = new AbstractAction("print model") {
+ public void actionPerformed(ActionEvent event) {
+ DocumentAdapterUITest.this.printModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void printModel() {
+ System.out.println("name: " + this.testModel.getName());
+ }
+
+
+ private class TestModel extends AbstractModel {
+ private String name;
+ public static final String NAME_PROPERTY = "name";
+
+ public TestModel(String name) {
+ this.name = name;
+ }
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ Object old = this.name;
+ this.name = name;
+ this.firePropertyChanged(NAME_PROPERTY, old, name);
+ }
+ @Override
+ public String toString() {
+ return "TestModel(" + this.getName() + ")";
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/JptUtilityModelValueSwingTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/JptUtilityModelValueSwingTests.java
new file mode 100644
index 0000000..b531bd3
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/JptUtilityModelValueSwingTests.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class JptUtilityModelValueSwingTests {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite(JptUtilityModelValueSwingTests.class.getPackage().getName());
+
+ suite.addTestSuite(CheckBoxModelAdapterTests.class);
+ suite.addTestSuite(ComboBoxModelAdapterTests.class);
+ suite.addTestSuite(DateSpinnerModelAdapterTests.class);
+ suite.addTestSuite(DocumentAdapterTests.class);
+ suite.addTestSuite(ListModelAdapterTests.class);
+ suite.addTestSuite(ListSpinnerModelAdapterTests.class);
+ suite.addTestSuite(NumberSpinnerModelAdapterTests.class);
+ suite.addTestSuite(ObjectListSelectionModelTests.class);
+ suite.addTestSuite(PrimitiveListTreeModelTests.class);
+ suite.addTestSuite(RadioButtonModelAdapterTests.class);
+ suite.addTestSuite(SpinnerModelAdapterTests.class);
+ suite.addTestSuite(TableModelAdapterTests.class);
+ suite.addTestSuite(TreeModelAdapterTests.class);
+
+ return suite;
+ }
+
+ private JptUtilityModelValueSwingTests() {
+ super();
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListModelAdapterTests.java
new file mode 100644
index 0000000..a66c76c
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListModelAdapterTests.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.swing.ListModel;
+
+import org.eclipse.jpt.utility.internal.Bag;
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.HashBag;
+import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimpleCollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.ListModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.model.value.SynchronizedList;
+
+import junit.framework.TestCase;
+
+public class ListModelAdapterTests extends TestCase {
+
+ public ListModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // nothing yet...
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // nothing yet...
+ super.tearDown();
+ }
+
+ public void testCollectionSynchronization() {
+ CollectionValueModel collectionHolder = this.buildCollectionHolder();
+ ListModel listModel = new ListModelAdapter(collectionHolder);
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ assertEquals(6, synchList.size());
+ this.compare(listModel, synchList);
+
+ collectionHolder.addItem("tom");
+ collectionHolder.addItem("dick");
+ collectionHolder.addItem("harry");
+ collectionHolder.addItem(null);
+ assertEquals(10, synchList.size());
+ this.compare(listModel, synchList);
+
+ collectionHolder.removeItem("foo");
+ collectionHolder.removeItem("jar");
+ collectionHolder.removeItem("harry");
+ collectionHolder.removeItem(null);
+ assertEquals(6, synchList.size());
+ this.compare(listModel, synchList);
+ }
+
+ public void testListSynchronization() {
+ ListValueModel listHolder = this.buildListHolder();
+ ListModel listModel = new ListModelAdapter(listHolder);
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ assertEquals(6, synchList.size());
+ this.compare(listModel, synchList);
+
+ listHolder.addItem(6, "tom");
+ listHolder.addItem(7, "dick");
+ listHolder.addItem(8, "harry");
+ listHolder.addItem(9, null);
+ assertEquals(10, synchList.size());
+ this.compare(listModel, synchList);
+
+ listHolder.removeItem(9);
+ listHolder.removeItem(8);
+ listHolder.removeItem(4);
+ listHolder.removeItem(0);
+ assertEquals(6, synchList.size());
+ this.compare(listModel, synchList);
+ }
+
+ public void testSetModel() {
+ SimpleListValueModel listHolder1 = this.buildListHolder();
+ ListModelAdapter listModel = new ListModelAdapter(listHolder1);
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ assertTrue(listHolder1.hasAnyListChangeListeners(ValueModel.VALUE));
+ assertEquals(6, synchList.size());
+ this.compare(listModel, synchList);
+
+ SimpleListValueModel listHolder2 = this.buildListHolder2();
+ listModel.setModel(listHolder2);
+ assertEquals(3, synchList.size());
+ this.compare(listModel, synchList);
+ assertTrue(listHolder1.hasNoListChangeListeners(ValueModel.VALUE));
+ assertTrue(listHolder2.hasAnyListChangeListeners(ValueModel.VALUE));
+
+ listModel.setModel(new SimpleListValueModel());
+ assertEquals(0, synchList.size());
+ this.compare(listModel, synchList);
+ assertTrue(listHolder1.hasNoListChangeListeners(ValueModel.VALUE));
+ assertTrue(listHolder2.hasNoListChangeListeners(ValueModel.VALUE));
+ }
+
+ private void compare(ListModel listModel, List list) {
+ assertEquals(listModel.getSize(), list.size());
+ for (int i = 0; i < listModel.getSize(); i++) {
+ assertEquals(listModel.getElementAt(i), list.get(i));
+ }
+ }
+
+ public void testCollectionSort() {
+ this.verifyCollectionSort(null);
+ }
+
+ public void testListSort() {
+ this.verifyListSort(null);
+ }
+
+ public void testCustomCollectionSort() {
+ this.verifyCollectionSort(this.buildCustomComparator());
+ }
+
+ public void testCustomListSort() {
+ this.verifyListSort(this.buildCustomComparator());
+ }
+
+ private Comparator buildCustomComparator() {
+ // sort with reverse order
+ return new Comparator() {
+ public int compare(Object o1, Object o2) {
+ return ((Comparable) o2).compareTo(o1);
+ }
+ };
+ }
+
+ private void verifyCollectionSort(Comparator comparator) {
+ CollectionValueModel collectionHolder = this.buildCollectionHolder();
+ ListModel listModel = new ListModelAdapter(new SortedListValueModelAdapter(collectionHolder, comparator));
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ assertEquals(6, synchList.size());
+ this.compareSort(listModel, synchList, comparator);
+
+ collectionHolder.addItem("tom");
+ collectionHolder.addItem("dick");
+ collectionHolder.addItem("harry");
+ assertEquals(9, synchList.size());
+ this.compareSort(listModel, synchList, comparator);
+
+ collectionHolder.removeItem("foo");
+ collectionHolder.removeItem("jar");
+ collectionHolder.removeItem("harry");
+ assertEquals(6, synchList.size());
+ this.compareSort(listModel, synchList, comparator);
+ }
+
+ private void verifyListSort(Comparator comparator) {
+ ListValueModel listHolder = this.buildListHolder();
+ ListModel listModel = new ListModelAdapter(new SortedListValueModelAdapter(listHolder, comparator));
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ assertEquals(6, synchList.size());
+ this.compareSort(listModel, synchList, comparator);
+
+ listHolder.addItem(0, "tom");
+ listHolder.addItem(0, "dick");
+ listHolder.addItem(0, "harry");
+ assertEquals(9, synchList.size());
+ this.compareSort(listModel, synchList, comparator);
+
+ listHolder.removeItem(8);
+ listHolder.removeItem(4);
+ listHolder.removeItem(0);
+ listHolder.removeItem(5);
+ assertEquals(5, synchList.size());
+ this.compareSort(listModel, synchList, comparator);
+ }
+
+ private void compareSort(ListModel listModel, List list, Comparator comparator) {
+ SortedSet ss = new TreeSet(comparator);
+ for (int i = 0; i < listModel.getSize(); i++) {
+ ss.add(listModel.getElementAt(i));
+ }
+ assertEquals(ss.size(), list.size());
+ for (Iterator stream1 = ss.iterator(), stream2 = list.iterator(); stream1.hasNext(); ) {
+ assertEquals(stream1.next(), stream2.next());
+ }
+ }
+
+ public void testHasListeners() throws Exception {
+ SimpleListValueModel listHolder = this.buildListHolder();
+ assertFalse(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+
+ ListModel listModel = new ListModelAdapter(listHolder);
+ assertFalse(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(listModel);
+
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ assertTrue(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(listModel);
+
+ listModel.removeListDataListener(synchList);
+ assertFalse(listHolder.hasAnyListChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(listModel);
+ }
+
+ public void testGetSize() throws Exception {
+ SimpleListValueModel listHolder = this.buildListHolder();
+ ListModel listModel = new ListModelAdapter(listHolder);
+ this.verifyHasNoListeners(listModel);
+ assertEquals(6, listModel.getSize());
+
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ this.verifyHasListeners(listModel);
+ assertEquals(6, listModel.getSize());
+
+ listModel.removeListDataListener(synchList);
+ this.verifyHasNoListeners(listModel);
+ assertEquals(6, listModel.getSize());
+ }
+
+ public void testGetElementAt() throws Exception {
+ SimpleListValueModel listHolder = this.buildListHolder();
+ ListModel listModel = new ListModelAdapter(new SortedListValueModelAdapter(listHolder));
+ SynchronizedList synchList = new SynchronizedList(listModel);
+ this.verifyHasListeners(listModel);
+ assertEquals("bar", listModel.getElementAt(0));
+ assertEquals("bar", synchList.get(0));
+ }
+
+ private void verifyHasNoListeners(ListModel listModel) throws Exception {
+ boolean hasNoListeners = ((Boolean) ClassTools.executeMethod(listModel, "hasNoListDataListeners")).booleanValue();
+ assertTrue(hasNoListeners);
+ }
+
+ private void verifyHasListeners(ListModel listModel) throws Exception {
+ boolean hasListeners = ((Boolean) ClassTools.executeMethod(listModel, "hasListDataListeners")).booleanValue();
+ assertTrue(hasListeners);
+ }
+
+ private CollectionValueModel buildCollectionHolder() {
+ return new SimpleCollectionValueModel(this.buildCollection());
+ }
+
+ private Collection<String> buildCollection() {
+ Bag<String> bag = new HashBag<String>();
+ this.populateCollection(bag);
+ return bag;
+ }
+
+ private SimpleListValueModel buildListHolder() {
+ return new SimpleListValueModel(this.buildList());
+ }
+
+ private List<String> buildList() {
+ List<String> list = new ArrayList<String>();
+ this.populateCollection(list);
+ return list;
+ }
+
+ private void populateCollection(Collection<String> c) {
+ c.add("foo");
+ c.add("bar");
+ c.add("baz");
+ c.add("joo");
+ c.add("jar");
+ c.add("jaz");
+ }
+
+ private SimpleListValueModel buildListHolder2() {
+ return new SimpleListValueModel(this.buildList2());
+ }
+
+ private List<String> buildList2() {
+ List<String> list = new ArrayList<String>();
+ this.populateCollection2(list);
+ return list;
+ }
+
+ private void populateCollection2(Collection<String> c) {
+ c.add("tom");
+ c.add("dick");
+ c.add("harry");
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListModelAdapterUITest.java
new file mode 100644
index 0000000..2c45a4f
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListModelAdapterUITest.java
@@ -0,0 +1,363 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListModel;
+import javax.swing.WindowConstants;
+
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.ListAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.ListModelAdapter;
+import org.eclipse.jpt.utility.internal.swing.Displayable;
+
+/**
+ * an example UI for testing various permutations of the ListModelAdapter
+ */
+public class ListModelAdapterUITest {
+
+ private PropertyValueModel taskListHolder;
+ private TextField taskTextField;
+
+ public static void main(String[] args) throws Exception {
+ new ListModelAdapterUITest().exec(args);
+ }
+
+ private ListModelAdapterUITest() {
+ super();
+ }
+
+ private void exec(String[] args) throws Exception {
+ this.taskListHolder = new SimplePropertyValueModel(new TaskList());
+ this.openWindow();
+ }
+
+ private void openWindow() {
+ JFrame window = new JFrame(this.getClass().getName());
+ window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ window.addWindowListener(this.buildWindowListener());
+ window.getContentPane().add(this.buildMainPanel(), "Center");
+ window.setSize(800, 400);
+ window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildTaskListPanel(), BorderLayout.CENTER);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ private Component buildTaskListPanel() {
+ JPanel taskListPanel = new JPanel(new GridLayout(0, 1));
+ taskListPanel.add(this.buildPrimitiveTaskListPanel());
+ taskListPanel.add(this.buildDisplayableTaskListPanel());
+ return taskListPanel;
+ }
+
+ private Component buildPrimitiveTaskListPanel() {
+ JPanel taskListPanel = new JPanel(new GridLayout(1, 0));
+ taskListPanel.add(this.buildUnsortedPrimitiveListPanel());
+ taskListPanel.add(this.buildStandardSortedPrimitiveListPanel());
+ taskListPanel.add(this.buildCustomSortedPrimitiveListPanel());
+ return taskListPanel;
+ }
+
+ private Component buildDisplayableTaskListPanel() {
+ JPanel taskListPanel = new JPanel(new GridLayout(1, 0));
+ taskListPanel.add(this.buildUnsortedDisplayableListPanel());
+ taskListPanel.add(this.buildStandardSortedDisplayableListPanel());
+ taskListPanel.add(this.buildCustomSortedDisplayableListPanel());
+ return taskListPanel;
+ }
+
+ private Component buildUnsortedPrimitiveListPanel() {
+ return this.buildListPanel(" primitive unsorted", this.buildUnsortedPrimitiveListModel());
+ }
+
+ private Component buildStandardSortedPrimitiveListPanel() {
+ return this.buildListPanel(" primitive sorted", this.buildStandardSortedPrimitiveListModel());
+ }
+
+ private Component buildCustomSortedPrimitiveListPanel() {
+ return this.buildListPanel(" primitive reverse sorted", this.buildCustomSortedPrimitiveListModel());
+ }
+
+ private Component buildUnsortedDisplayableListPanel() {
+ return this.buildListPanel(" displayable unsorted", this.buildUnsortedDisplayableListModel());
+ }
+
+ private Component buildStandardSortedDisplayableListPanel() {
+ return this.buildListPanel(" displayable sorted", this.buildStandardSortedDisplayableListModel());
+ }
+
+ private Component buildCustomSortedDisplayableListPanel() {
+ return this.buildListPanel(" displayable reverse sorted", this.buildCustomSortedDisplayableListModel());
+ }
+
+ private ListModel buildUnsortedPrimitiveListModel() {
+ return new ListModelAdapter(this.buildPrimitiveTaskListAdapter());
+ }
+
+ private ListModel buildStandardSortedPrimitiveListModel() {
+ return new ListModelAdapter(new SortedListValueModelAdapter(this.buildPrimitiveTaskListAdapter()));
+ }
+
+ private ListModel buildCustomSortedPrimitiveListModel() {
+ return new ListModelAdapter(new SortedListValueModelAdapter(this.buildPrimitiveTaskListAdapter(), this.buildCustomComparator()));
+ }
+
+ private ListModel buildUnsortedDisplayableListModel() {
+ return new ListModelAdapter(this.buildDisplayableTaskListAdapter());
+ }
+
+ private ListModel buildStandardSortedDisplayableListModel() {
+ return new ListModelAdapter(new SortedListValueModelAdapter(this.buildDisplayableTaskListAdapter()));
+ }
+
+ private ListModel buildCustomSortedDisplayableListModel() {
+ return new ListModelAdapter(new SortedListValueModelAdapter(this.buildDisplayableTaskListAdapter(), this.buildCustomComparator()));
+ }
+
+ private Component buildListPanel(String label, ListModel listModel) {
+ JPanel listPanel = new JPanel(new BorderLayout());
+ JLabel listLabel = new JLabel(label);
+ listPanel.add(listLabel, BorderLayout.NORTH);
+
+ JList listBox = new JList();
+ listBox.setModel(listModel);
+ listBox.setDoubleBuffered(true);
+ listLabel.setLabelFor(listBox);
+ listPanel.add(new JScrollPane(listBox), BorderLayout.CENTER);
+ return listPanel;
+ }
+
+ private Comparator buildCustomComparator() {
+ return new Comparator() {
+ public int compare(Object o1, Object o2) {
+ return ((Comparable) o2).compareTo(o1);
+ }
+ };
+ }
+
+ private ListValueModel buildPrimitiveTaskListAdapter() {
+ return new ListAspectAdapter(TaskList.TASKS_LIST, this.taskList()) {
+ @Override
+ protected ListIterator getValueFromSubject() {
+ return ((TaskList) this.subject).tasks();
+ }
+ };
+ }
+
+ private ListValueModel buildDisplayableTaskListAdapter() {
+ return new ListAspectAdapter(TaskList.TASK_OBJECTS_LIST, this.taskList()) {
+ @Override
+ protected ListIterator getValueFromSubject() {
+ return ((TaskList) this.subject).taskObjects();
+ }
+ };
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new BorderLayout());
+ controlPanel.add(this.buildAddRemoveTaskPanel(), BorderLayout.CENTER);
+ controlPanel.add(this.buildClearButton(), BorderLayout.EAST);
+ return controlPanel;
+ }
+
+ private Component buildAddRemoveTaskPanel() {
+ JPanel addRemoveTaskPanel = new JPanel(new BorderLayout());
+ addRemoveTaskPanel.add(this.buildAddButton(), BorderLayout.WEST);
+ addRemoveTaskPanel.add(this.buildTaskTextField(), BorderLayout.CENTER);
+ addRemoveTaskPanel.add(this.buildRemoveButton(), BorderLayout.EAST);
+ return addRemoveTaskPanel;
+ }
+
+ private String getTask() {
+ return this.taskTextField.getText();
+ }
+
+ private TaskList taskList() {
+ return (TaskList) this.taskListHolder.getValue();
+ }
+
+ void addTask() {
+ String task = this.getTask();
+ if (task.length() != 0) {
+ this.taskList().addTask(task);
+ }
+ }
+
+ void removeTask() {
+ String task = this.getTask();
+ if (task.length() != 0) {
+ this.taskList().removeTask(task);
+ }
+ }
+
+ void clearTasks() {
+ this.taskList().clearTasks();
+ }
+
+ private TextField buildTaskTextField() {
+ this.taskTextField = new TextField();
+ return this.taskTextField;
+ }
+
+ private JButton buildAddButton() {
+ return new JButton(this.buildAddAction());
+ }
+
+ private Action buildAddAction() {
+ Action action = new AbstractAction("add") {
+ public void actionPerformed(ActionEvent event) {
+ ListModelAdapterUITest.this.addTask();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ private JButton buildRemoveButton() {
+ return new JButton(this.buildRemoveAction());
+ }
+
+ private Action buildRemoveAction() {
+ Action action = new AbstractAction("remove") {
+ public void actionPerformed(ActionEvent event) {
+ ListModelAdapterUITest.this.removeTask();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ private JButton buildClearButton() {
+ return new JButton(this.buildClearAction());
+ }
+
+ private Action buildClearAction() {
+ Action action = new AbstractAction("clear") {
+ public void actionPerformed(ActionEvent event) {
+ ListModelAdapterUITest.this.clearTasks();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ private class TaskList extends AbstractModel {
+ private List<String> tasks = new ArrayList<String>();
+ private List<TaskObject> taskObjects = new ArrayList<TaskObject>();
+ public static final String TASKS_LIST = "tasks";
+ public static final String TASK_OBJECTS_LIST = "taskObjects";
+ TaskList() {
+ super();
+ }
+ public ListIterator<String> tasks() {
+ return this.tasks.listIterator();
+ }
+ public ListIterator<TaskObject> taskObjects() {
+ return this.taskObjects.listIterator();
+ }
+ public void addTask(String task) {
+ int index = this.tasks.size();
+ this.tasks.add(index, task);
+ this.fireItemAdded(TASKS_LIST, index, task);
+
+ TaskObject taskObject = new TaskObject(task);
+ this.taskObjects.add(index, taskObject);
+ this.fireItemAdded(TASK_OBJECTS_LIST, index, taskObject);
+ }
+ public void removeTask(String task) {
+ int index = this.tasks.indexOf(task);
+ if (index != -1) {
+ Object removedTask = this.tasks.remove(index);
+ this.fireItemRemoved(TASKS_LIST, index, removedTask);
+ // assume the indexes match...
+ Object removedTaskObject = this.taskObjects.remove(index);
+ this.fireItemRemoved(TASK_OBJECTS_LIST, index, removedTaskObject);
+ }
+ }
+ public void clearTasks() {
+ this.tasks.clear();
+ this.fireListChanged(TASKS_LIST);
+ this.taskObjects.clear();
+ this.fireListChanged(TASK_OBJECTS_LIST);
+ }
+ }
+
+ private class TaskObject extends AbstractModel implements Displayable {
+ private String name;
+ private Date creationTimeStamp;
+ public TaskObject(String name) {
+ this.name = name;
+ this.creationTimeStamp = new Date();
+ }
+ public String displayString() {
+ return this.name + ": " + this.creationTimeStamp.getTime();
+ }
+ public Icon icon() {
+ return null;
+ }
+ public int compareTo(Displayable o) {
+ return DEFAULT_COMPARATOR.compare(this, o);
+ }
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ Object old = this.name;
+ this.name = name;
+ this.firePropertyChanged(DISPLAY_STRING_PROPERTY, old, name);
+ }
+ @Override
+ public String toString() {
+ return "TaskObject(" + this.displayString() + ")";
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListSpinnerModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListSpinnerModelAdapterTests.java
new file mode 100644
index 0000000..9a715e5
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ListSpinnerModelAdapterTests.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.SpinnerModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.ListSpinnerModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class ListSpinnerModelAdapterTests extends TestCase {
+ private PropertyValueModel valueHolder;
+ private SpinnerModel spinnerModelAdapter;
+ boolean eventFired;
+ private static final String[] VALUE_LIST = {"red", "green", "blue"};
+ private static final String DEFAULT_VALUE = VALUE_LIST[0];
+
+ public ListSpinnerModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.valueHolder = new SimplePropertyValueModel(DEFAULT_VALUE);
+ this.spinnerModelAdapter = new ListSpinnerModelAdapter(this.valueHolder, VALUE_LIST);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testSetValueSpinnerModel() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ ListSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(DEFAULT_VALUE, this.valueHolder.getValue());
+ this.spinnerModelAdapter.setValue(VALUE_LIST[2]);
+ assertTrue(this.eventFired);
+ assertEquals(VALUE_LIST[2], this.valueHolder.getValue());
+ }
+
+ public void testSetValueValueHolder() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ ListSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(DEFAULT_VALUE, this.spinnerModelAdapter.getValue());
+ this.valueHolder.setValue(VALUE_LIST[2]);
+ assertTrue(this.eventFired);
+ assertEquals(VALUE_LIST[2], this.spinnerModelAdapter.getValue());
+ }
+
+ public void testDefaultValue() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ ListSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(DEFAULT_VALUE, this.spinnerModelAdapter.getValue());
+
+ this.valueHolder.setValue(VALUE_LIST[2]);
+ assertTrue(this.eventFired);
+ assertEquals(VALUE_LIST[2], this.spinnerModelAdapter.getValue());
+
+ this.eventFired = false;
+ this.valueHolder.setValue(null);
+ assertTrue(this.eventFired);
+ assertEquals(VALUE_LIST[0], this.spinnerModelAdapter.getValue());
+ }
+
+ public void testHasListeners() throws Exception {
+ SimplePropertyValueModel localValueHolder = (SimplePropertyValueModel) this.valueHolder;
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+
+ ChangeListener listener = new TestChangeListener();
+ this.spinnerModelAdapter.addChangeListener(listener);
+ assertTrue(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(this.spinnerModelAdapter);
+
+ this.spinnerModelAdapter.removeChangeListener(listener);
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+ }
+
+ private void verifyHasNoListeners(SpinnerModel adapter) throws Exception {
+ assertEquals(0, ((ListSpinnerModelAdapter) adapter).getChangeListeners().length);
+ }
+
+ private void verifyHasListeners(Object adapter) throws Exception {
+ assertFalse(((ListSpinnerModelAdapter) adapter).getChangeListeners().length == 0);
+ }
+
+
+ private class TestChangeListener implements ChangeListener {
+ TestChangeListener() {
+ super();
+ }
+ public void stateChanged(ChangeEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/NumberSpinnerModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/NumberSpinnerModelAdapterTests.java
new file mode 100644
index 0000000..0077539
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/NumberSpinnerModelAdapterTests.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.SpinnerModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.NumberSpinnerModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class NumberSpinnerModelAdapterTests extends TestCase {
+ private PropertyValueModel valueHolder;
+ private SpinnerModel spinnerModelAdapter;
+ boolean eventFired;
+
+ public NumberSpinnerModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.valueHolder = new SimplePropertyValueModel(new Integer(0));
+ this.spinnerModelAdapter = new NumberSpinnerModelAdapter(this.valueHolder, -33, 33, 1);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testSetValueSpinnerModel() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ NumberSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ this.spinnerModelAdapter.setValue(new Integer(5));
+ assertTrue(this.eventFired);
+ assertEquals(new Integer(5), this.valueHolder.getValue());
+ }
+
+ public void testSetValueValueHolder() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ NumberSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(new Integer(0), this.spinnerModelAdapter.getValue());
+ this.valueHolder.setValue(new Integer(7));
+ assertTrue(this.eventFired);
+ assertEquals(new Integer(7), this.spinnerModelAdapter.getValue());
+ }
+
+ public void testDefaultValue() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ NumberSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(new Integer(0), this.spinnerModelAdapter.getValue());
+ this.valueHolder.setValue(null);
+ assertTrue(this.eventFired);
+ assertEquals(new Integer(-33), this.spinnerModelAdapter.getValue());
+ }
+
+ public void testHasListeners() throws Exception {
+ SimplePropertyValueModel localValueHolder = (SimplePropertyValueModel) this.valueHolder;
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+
+ ChangeListener listener = new TestChangeListener();
+ this.spinnerModelAdapter.addChangeListener(listener);
+ assertTrue(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(this.spinnerModelAdapter);
+
+ this.spinnerModelAdapter.removeChangeListener(listener);
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+ }
+
+ private void verifyHasNoListeners(SpinnerModel adapter) throws Exception {
+ assertEquals(0, ((NumberSpinnerModelAdapter) adapter).getChangeListeners().length);
+ }
+
+ private void verifyHasListeners(Object adapter) throws Exception {
+ assertFalse(((NumberSpinnerModelAdapter) adapter).getChangeListeners().length == 0);
+ }
+
+ public void testNullInitialValue() {
+ this.valueHolder = new SimplePropertyValueModel();
+ this.spinnerModelAdapter = new NumberSpinnerModelAdapter(this.valueHolder, new Integer(-33), new Integer(33), new Integer(1), new Integer(0));
+
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ NumberSpinnerModelAdapterTests.this.eventFired = true;
+ }
+ });
+ assertEquals(new Integer(0), this.spinnerModelAdapter.getValue());
+ this.valueHolder.setValue(new Integer(7));
+ assertTrue(this.eventFired);
+ assertEquals(new Integer(7), this.spinnerModelAdapter.getValue());
+ }
+
+
+ // ********** inner class **********
+ private class TestChangeListener implements ChangeListener {
+ TestChangeListener() {
+ super();
+ }
+ public void stateChanged(ChangeEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ObjectListSelectionModelTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ObjectListSelectionModelTests.java
new file mode 100644
index 0000000..13aea76
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ObjectListSelectionModelTests.java
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.DefaultListModel;
+import javax.swing.ListModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.eclipse.jpt.utility.internal.CollectionTools;
+import org.eclipse.jpt.utility.internal.model.value.swing.ObjectListSelectionModel;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class ObjectListSelectionModelTests extends TestCase {
+ private DefaultListModel listModel;
+ private ObjectListSelectionModel selectionModel;
+
+ public ObjectListSelectionModelTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.listModel = this.buildListModel();
+ this.selectionModel = this.buildSelectionModel(this.listModel);
+ }
+
+ private DefaultListModel buildListModel() {
+ DefaultListModel lm = new DefaultListModel();
+ lm.addElement("foo");
+ lm.addElement("bar");
+ lm.addElement("baz");
+ return lm;
+ }
+
+ private ObjectListSelectionModel buildSelectionModel(ListModel lm) {
+ return new ObjectListSelectionModel(lm);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testListDataListener() {
+ this.selectionModel.addListSelectionListener(this.buildListSelectionListener());
+ this.selectionModel.setSelectionInterval(0, 0);
+ assertEquals("foo", this.selectionModel.getSelectedValue());
+ this.listModel.set(0, "jar");
+ assertEquals("jar", this.selectionModel.getSelectedValue());
+ }
+
+ public void testGetSelectedValue() {
+ this.selectionModel.setSelectionInterval(0, 0);
+ assertEquals("foo", this.selectionModel.getSelectedValue());
+ }
+
+ public void testGetSelectedValues() {
+ this.selectionModel.setSelectionInterval(0, 0);
+ this.selectionModel.addSelectionInterval(2, 2);
+ assertEquals(2, this.selectionModel.getSelectedValues().length);
+ assertTrue(CollectionTools.contains(this.selectionModel.getSelectedValues(), "foo"));
+ assertTrue(CollectionTools.contains(this.selectionModel.getSelectedValues(), "baz"));
+ }
+
+ public void testSetSelectedValue() {
+ this.selectionModel.setSelectedValue("foo");
+ assertEquals(0, this.selectionModel.getMinSelectionIndex());
+ assertEquals(0, this.selectionModel.getMaxSelectionIndex());
+ }
+
+ public void testSetSelectedValues() {
+ this.selectionModel.setSelectedValues(new Object[] {"foo", "baz"});
+ assertEquals(0, this.selectionModel.getMinSelectionIndex());
+ assertEquals(2, this.selectionModel.getMaxSelectionIndex());
+ }
+
+ public void testAddSelectedValue() {
+ this.listModel.addElement("joo");
+ this.listModel.addElement("jar");
+ this.listModel.addElement("jaz");
+ this.selectionModel.setSelectedValue("foo");
+ this.selectionModel.addSelectedValue("jaz");
+ assertEquals(0, this.selectionModel.getMinSelectionIndex());
+ assertEquals(5, this.selectionModel.getMaxSelectionIndex());
+ assertTrue(this.selectionModel.isSelectedIndex(0));
+ assertFalse(this.selectionModel.isSelectedIndex(1));
+ assertFalse(this.selectionModel.isSelectedIndex(2));
+ assertFalse(this.selectionModel.isSelectedIndex(3));
+ assertFalse(this.selectionModel.isSelectedIndex(4));
+ assertTrue(this.selectionModel.isSelectedIndex(5));
+ }
+
+ public void testAddSelectedValues() {
+ this.listModel.addElement("joo");
+ this.listModel.addElement("jar");
+ this.listModel.addElement("jaz");
+ this.selectionModel.setSelectedValue("foo");
+ this.selectionModel.addSelectedValues(new Object[] {"bar", "jar"});
+ assertEquals(0, this.selectionModel.getMinSelectionIndex());
+ assertEquals(4, this.selectionModel.getMaxSelectionIndex());
+ assertTrue(this.selectionModel.isSelectedIndex(0));
+ assertTrue(this.selectionModel.isSelectedIndex(1));
+ assertFalse(this.selectionModel.isSelectedIndex(2));
+ assertFalse(this.selectionModel.isSelectedIndex(3));
+ assertTrue(this.selectionModel.isSelectedIndex(4));
+ assertFalse(this.selectionModel.isSelectedIndex(5));
+ }
+
+ public void testRemoveSelectedValue() {
+ this.listModel.addElement("joo");
+ this.listModel.addElement("jar");
+ this.listModel.addElement("jaz");
+ this.selectionModel.setSelectedValues(new Object[] {"foo", "baz", "jar"});
+ this.selectionModel.removeSelectedValue("jar");
+ assertEquals(0, this.selectionModel.getMinSelectionIndex());
+ assertEquals(2, this.selectionModel.getMaxSelectionIndex());
+ assertTrue(this.selectionModel.isSelectedIndex(0));
+ assertFalse(this.selectionModel.isSelectedIndex(1));
+ assertTrue(this.selectionModel.isSelectedIndex(2));
+ assertFalse(this.selectionModel.isSelectedIndex(3));
+ assertFalse(this.selectionModel.isSelectedIndex(4));
+ assertFalse(this.selectionModel.isSelectedIndex(5));
+ }
+
+ public void testRemoveSelectedValues() {
+ this.listModel.addElement("joo");
+ this.listModel.addElement("jar");
+ this.listModel.addElement("jaz");
+ this.selectionModel.setSelectedValues(new Object[] {"foo", "baz", "joo", "jar"});
+ this.selectionModel.removeSelectedValues(new Object[] {"foo", "joo"});
+ assertEquals(2, this.selectionModel.getMinSelectionIndex());
+ assertEquals(4, this.selectionModel.getMaxSelectionIndex());
+ assertFalse(this.selectionModel.isSelectedIndex(0));
+ assertFalse(this.selectionModel.isSelectedIndex(1));
+ assertTrue(this.selectionModel.isSelectedIndex(2));
+ assertFalse(this.selectionModel.isSelectedIndex(3));
+ assertTrue(this.selectionModel.isSelectedIndex(4));
+ assertFalse(this.selectionModel.isSelectedIndex(5));
+ }
+
+ public void testGetAnchorSelectedValue() {
+ this.selectionModel.setAnchorSelectionIndex(1);
+ assertEquals("bar", this.selectionModel.getAnchorSelectedValue());
+ }
+
+ public void testGetLeadSelectedValue() {
+ this.selectionModel.setSelectedValue("bar");
+ assertEquals("bar", this.selectionModel.getLeadSelectedValue());
+ this.selectionModel.setSelectedValues(new Object[] {"foo", "baz"});
+ assertEquals("baz", this.selectionModel.getLeadSelectedValue());
+ }
+
+ public void testGetMinMaxSelectedValue() {
+ this.listModel.addElement("joo");
+ this.listModel.addElement("jar");
+ this.listModel.addElement("jaz");
+ this.selectionModel.setSelectedValue("foo");
+ this.selectionModel.addSelectedValues(new Object[] {"bar", "jar"});
+ assertEquals("foo", this.selectionModel.getMinSelectedValue());
+ assertEquals("jar", this.selectionModel.getMaxSelectedValue());
+ }
+
+ public void testValueIsSelected() {
+ this.listModel.addElement("joo");
+ this.listModel.addElement("jar");
+ this.listModel.addElement("jaz");
+ this.selectionModel.setSelectedValue("foo");
+ this.selectionModel.addSelectedValues(new Object[] {"bar", "jar"});
+ assertTrue(this.selectionModel.valueIsSelected("foo"));
+ assertTrue(this.selectionModel.valueIsSelected("bar"));
+ assertTrue(this.selectionModel.valueIsSelected("jar"));
+ assertFalse(this.selectionModel.valueIsSelected("baz"));
+ }
+
+ public void testHasListeners() throws Exception {
+ ListSelectionListener listener = this.buildListSelectionListener();
+ assertEquals(0, this.listModel.getListDataListeners().length);
+ this.selectionModel.addListSelectionListener(listener);
+ assertEquals(1, this.listModel.getListDataListeners().length);
+ this.selectionModel.removeListSelectionListener(listener);
+ assertEquals(0, this.listModel.getListDataListeners().length);
+ }
+
+ private ListSelectionListener buildListSelectionListener() {
+ return new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ // do nothing for now...
+ }
+ };
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/PrimitiveListTreeModelTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/PrimitiveListTreeModelTests.java
new file mode 100644
index 0000000..12d1e06
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/PrimitiveListTreeModelTests.java
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeModel;
+
+import org.eclipse.jpt.utility.internal.iterators.ReadOnlyListIterator;
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.ListAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.PrimitiveListTreeModel;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class PrimitiveListTreeModelTests extends TestCase {
+ TestModel testModel;
+ private TreeModel treeModel;
+
+ public PrimitiveListTreeModelTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.testModel = this.buildTestModel();
+ this.treeModel = this.buildTreeModel();
+ }
+
+ private TestModel buildTestModel() {
+ return new TestModel();
+ }
+
+ private TreeModel buildTreeModel() {
+ return new PrimitiveListTreeModel(this.buildListValueModel()) {
+ @Override
+ protected void primitiveChanged(int index, Object newValue) {
+ if ( ! newValue.equals("")) {
+ PrimitiveListTreeModelTests.this.testModel.replaceName(index, (String) newValue);
+ }
+ }
+ };
+ }
+
+ private ListValueModel buildListValueModel() {
+ return new ListAspectAdapter(TestModel.NAMES_LIST, this.testModel) {
+ @Override
+ protected ListIterator getValueFromSubject() {
+ return ((TestModel) this.subject).names();
+ }
+ @Override
+ public Object getItem(int index) {
+ return ((TestModel) this.subject).getName(index);
+ }
+ @Override
+ public int size() {
+ return ((TestModel) this.subject).namesSize();
+ }
+ @Override
+ public void addItem(int index, Object item) {
+ ((TestModel) this.subject).addName(index, (String) item);
+ }
+ @Override
+ public void addItems(int index, List items) {
+ ((TestModel) this.subject).addNames(index, items);
+ }
+ @Override
+ public Object removeItem(int index) {
+ return ((TestModel) this.subject).removeName(index);
+ }
+ @Override
+ public List removeItems(int index, int length) {
+ return ((TestModel) this.subject).removeNames(index, length);
+ }
+ @Override
+ public Object replaceItem(int index, Object item) {
+ return ((TestModel) this.subject).replaceName(index, (String) item);
+ }
+ };
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testAddPrimitive() {
+ this.treeModel.addTreeModelListener(new TestTreeModelListener() {
+ @Override
+ public void treeNodesInserted(TreeModelEvent e) {
+ PrimitiveListTreeModelTests.this.verifyTreeModelEvent(e, new int[] {0}, new String[] {"foo"});
+ }
+ });
+ this.testModel.addName("foo");
+ }
+
+ public void testRemovePrimitive() {
+ this.testModel.addName("foo");
+ this.testModel.addName("bar");
+ this.testModel.addName("baz");
+ this.treeModel.addTreeModelListener(new TestTreeModelListener() {
+ @Override
+ public void treeNodesRemoved(TreeModelEvent e) {
+ PrimitiveListTreeModelTests.this.verifyTreeModelEvent(e, new int[] {1}, new String[] {"bar"});
+ }
+ });
+ String name = this.testModel.removeName(1);
+ assertEquals("bar", name);
+ }
+
+ public void testReplacePrimitive() {
+ this.testModel.addName("foo");
+ this.testModel.addName("bar");
+ this.testModel.addName("baz");
+ this.treeModel.addTreeModelListener(new TestTreeModelListener() {
+ @Override
+ public void treeNodesChanged(TreeModelEvent e) {
+ PrimitiveListTreeModelTests.this.verifyTreeModelEvent(e, new int[] {1}, new String[] {"jar"});
+ }
+ });
+ String name = this.testModel.replaceName(1, "jar");
+ assertEquals("bar", name);
+ }
+
+ void verifyTreeModelEvent(TreeModelEvent e, int[] expectedChildIndices, String[] expectedNames) {
+ assertTrue(Arrays.equals(expectedChildIndices, e.getChildIndices()));
+ Object[] actualChildren = e.getChildren();
+ assertEquals(expectedNames.length, actualChildren.length);
+ for (int i = 0; i < expectedNames.length; i++) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) actualChildren[i];
+ assertEquals(expectedNames[i], node.getUserObject());
+ }
+ assertEquals(1, e.getPath().length);
+ assertEquals(this.treeModel.getRoot(), e.getPath()[0]);
+ assertEquals(this.treeModel, e.getSource());
+ }
+
+
+// ********** inner classes **********
+
+ private class TestModel extends AbstractModel {
+ private final List<String> names;
+ static final String NAMES_LIST = "names";
+
+ TestModel() {
+ super();
+ this.names = new ArrayList<String>();
+ }
+
+ public ListIterator<String> names() {
+ return new ReadOnlyListIterator<String>(this.names);
+ }
+ public int namesSize() {
+ return this.names.size();
+ }
+ public String getName(int index) {
+ return this.names.get(index);
+ }
+ public void addName(int index, String name) {
+ this.addItemToList(index, name, this.names, NAMES_LIST);
+ }
+ public void addName(String name) {
+ this.addName(this.namesSize(), name);
+ }
+ public void addNames(int index, List list) {
+ this.addItemsToList(index, this.names, list, NAMES_LIST);
+ }
+ public void addNames(List list) {
+ this.addNames(this.namesSize(), list);
+ }
+ public String removeName(int index) {
+ return (String) this.removeItemFromList(index, this.names, NAMES_LIST);
+ }
+ public List removeNames(int index, int length) {
+ return this.removeItemsFromList(index, length, this.names, NAMES_LIST);
+ }
+ public String replaceName(int index, String newName) {
+ return this.setItemInList(index, newName, this.names, NAMES_LIST);
+ }
+ }
+
+
+ public class TestTreeModelListener implements TreeModelListener {
+ public void treeNodesChanged(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ public void treeNodesInserted(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ public void treeNodesRemoved(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ public void treeStructureChanged(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/RadioButtonModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/RadioButtonModelAdapterTests.java
new file mode 100644
index 0000000..dc6791e
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/RadioButtonModelAdapterTests.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.ButtonModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.RadioButtonModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class RadioButtonModelAdapterTests extends TestCase {
+ private PropertyValueModel valueHolder;
+
+ private ButtonModel redButtonModelAdapter;
+ private ChangeListener redListener;
+ boolean redEventFired;
+
+ private ButtonModel greenButtonModelAdapter;
+ private ChangeListener greenListener;
+ boolean greenEventFired;
+
+ private ButtonModel blueButtonModelAdapter;
+ private ChangeListener blueListener;
+ boolean blueEventFired;
+
+// private ButtonGroup buttonGroup; // DO NOT use a ButtonGroup
+
+ private static final String RED = "red";
+ private static final String GREEN = "green";
+ private static final String BLUE = "blue";
+
+ public RadioButtonModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.valueHolder = new SimplePropertyValueModel(null);
+// buttonGroup = new ButtonGroup();
+
+ this.redButtonModelAdapter = new RadioButtonModelAdapter(this.valueHolder, RED);
+// this.redButtonModelAdapter.setGroup(buttonGroup);
+ this.redListener = new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ RadioButtonModelAdapterTests.this.redEventFired = true;
+ }
+ };
+
+ this.greenButtonModelAdapter = new RadioButtonModelAdapter(this.valueHolder, GREEN);
+// this.greenButtonModelAdapter.setGroup(buttonGroup);
+ this.greenListener = new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ RadioButtonModelAdapterTests.this.greenEventFired = true;
+ }
+ };
+
+ this.blueButtonModelAdapter = new RadioButtonModelAdapter(this.valueHolder, BLUE);
+// this.blueButtonModelAdapter.setGroup(buttonGroup);
+ this.blueListener = new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ RadioButtonModelAdapterTests.this.blueEventFired = true;
+ }
+ };
+
+ this.clearFlags();
+ }
+
+ private void listenToModelAdapters() {
+ this.redButtonModelAdapter.addChangeListener(this.redListener);
+ this.greenButtonModelAdapter.addChangeListener(this.greenListener);
+ this.blueButtonModelAdapter.addChangeListener(this.blueListener);
+ }
+
+ private void clearFlags() {
+ this.redEventFired = false;
+ this.greenEventFired = false;
+ this.blueEventFired = false;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testSetSelected() throws Exception {
+ this.listenToModelAdapters();
+
+ this.greenButtonModelAdapter.setSelected(true);
+ assertFalse(this.redEventFired);
+ assertTrue(this.greenEventFired);
+ assertFalse(this.blueEventFired);
+ assertEquals(GREEN, this.valueHolder.getValue());
+
+ this.clearFlags();
+ this.blueButtonModelAdapter.setSelected(true);
+ assertFalse(this.redEventFired);
+ assertTrue(this.greenEventFired);
+ assertTrue(this.blueEventFired);
+ assertEquals(BLUE, this.valueHolder.getValue());
+
+ this.clearFlags();
+ this.redButtonModelAdapter.setSelected(true);
+ assertTrue(this.redEventFired);
+ assertFalse(this.greenEventFired);
+ assertTrue(this.blueEventFired);
+ assertEquals(RED, this.valueHolder.getValue());
+ }
+
+ public void testSetValue() throws Exception {
+ this.listenToModelAdapters();
+
+ this.greenButtonModelAdapter.setSelected(true);
+
+ this.clearFlags();
+ this.valueHolder.setValue(BLUE);
+ assertFalse(this.redEventFired);
+ assertTrue(this.greenEventFired);
+ assertTrue(this.blueEventFired);
+ assertFalse(this.redButtonModelAdapter.isSelected());
+ assertFalse(this.greenButtonModelAdapter.isSelected());
+ assertTrue(this.blueButtonModelAdapter.isSelected());
+
+ this.clearFlags();
+ this.valueHolder.setValue(RED);
+ assertTrue(this.redEventFired);
+ assertFalse(this.greenEventFired);
+ assertTrue(this.blueEventFired);
+ assertTrue(this.redButtonModelAdapter.isSelected());
+ assertFalse(this.greenButtonModelAdapter.isSelected());
+ assertFalse(this.blueButtonModelAdapter.isSelected());
+ }
+
+ public void testDefaultValue() throws Exception {
+ this.listenToModelAdapters();
+
+ this.valueHolder.setValue(GREEN);
+ assertFalse(this.redButtonModelAdapter.isSelected());
+ assertTrue(this.greenButtonModelAdapter.isSelected());
+ assertFalse(this.blueButtonModelAdapter.isSelected());
+
+ this.clearFlags();
+ this.valueHolder.setValue(null);
+ assertFalse(this.redEventFired);
+ assertTrue(this.greenEventFired);
+ assertFalse(this.blueEventFired);
+ assertFalse(this.redButtonModelAdapter.isSelected());
+ assertFalse(this.greenButtonModelAdapter.isSelected());
+ assertFalse(this.blueButtonModelAdapter.isSelected());
+
+ this.clearFlags();
+ this.valueHolder.setValue(BLUE);
+ assertFalse(this.redEventFired);
+ assertFalse(this.greenEventFired);
+ assertTrue(this.blueEventFired);
+ assertFalse(this.redButtonModelAdapter.isSelected());
+ assertFalse(this.greenButtonModelAdapter.isSelected());
+ assertTrue(this.blueButtonModelAdapter.isSelected());
+ }
+
+ public void testHasListeners() throws Exception {
+ SimplePropertyValueModel localValueHolder = (SimplePropertyValueModel) this.valueHolder;
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.redButtonModelAdapter);
+ this.verifyHasNoListeners(this.greenButtonModelAdapter);
+ this.verifyHasNoListeners(this.blueButtonModelAdapter);
+
+ ChangeListener listener = new TestChangeListener();
+ this.redButtonModelAdapter.addChangeListener(listener);
+ assertTrue(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(this.redButtonModelAdapter);
+ this.verifyHasNoListeners(this.greenButtonModelAdapter);
+ this.verifyHasNoListeners(this.blueButtonModelAdapter);
+
+ this.redButtonModelAdapter.removeChangeListener(listener);
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.redButtonModelAdapter);
+ this.verifyHasNoListeners(this.greenButtonModelAdapter);
+ this.verifyHasNoListeners(this.blueButtonModelAdapter);
+ }
+
+ private void verifyHasNoListeners(Object model) throws Exception {
+ EventListenerList listenerList = (EventListenerList) ClassTools.getFieldValue(model, "listenerList");
+ assertEquals(0, listenerList.getListenerList().length);
+ }
+
+ private void verifyHasListeners(Object model) throws Exception {
+ EventListenerList listenerList = (EventListenerList) ClassTools.getFieldValue(model, "listenerList");
+ assertFalse(listenerList.getListenerList().length == 0);
+ }
+
+
+ private class TestChangeListener implements ChangeListener {
+ TestChangeListener() {
+ super();
+ }
+ public void stateChanged(ChangeEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/RadioButtonModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/RadioButtonModelAdapterUITest.java
new file mode 100644
index 0000000..39074f2
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/RadioButtonModelAdapterUITest.java
@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ButtonModel;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.WindowConstants;
+
+import org.eclipse.jpt.utility.internal.CollectionTools;
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.RadioButtonModelAdapter;
+
+
+/**
+ * Play around with a set of radio buttons.
+ */
+public class RadioButtonModelAdapterUITest {
+
+ private TestModel testModel;
+ private PropertyValueModel testModelHolder;
+ private PropertyValueModel colorHolder;
+ private ButtonModel redButtonModel;
+ private ButtonModel greenButtonModel;
+ private ButtonModel blueButtonModel;
+
+ public static void main(String[] args) throws Exception {
+ new RadioButtonModelAdapterUITest().exec(args);
+ }
+
+ private RadioButtonModelAdapterUITest() {
+ super();
+ }
+
+ private void exec(String[] args) throws Exception {
+ this.testModel = new TestModel();
+ this.testModelHolder = new SimplePropertyValueModel(this.testModel);
+ this.colorHolder = this.buildColorHolder(this.testModelHolder);
+ this.redButtonModel = this.buildRadioButtonModelAdapter(this.colorHolder, TestModel.RED);
+ this.greenButtonModel = this.buildRadioButtonModelAdapter(this.colorHolder, TestModel.GREEN);
+ this.blueButtonModel = this.buildRadioButtonModelAdapter(this.colorHolder, TestModel.BLUE);
+ this.openWindow();
+ }
+
+ private PropertyValueModel buildColorHolder(ValueModel subjectHolder) {
+ return new PropertyAspectAdapter(subjectHolder, TestModel.COLOR_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((TestModel) this.subject).getColor();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setColor((String) value);
+ }
+ };
+ }
+
+ private ButtonModel buildRadioButtonModelAdapter(PropertyValueModel colorPVM, String color) {
+ return new RadioButtonModelAdapter(colorPVM, color);
+ }
+
+ private void openWindow() {
+ JFrame window = new JFrame(this.getClass().getName());
+ window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ window.addWindowListener(this.buildWindowListener());
+ window.getContentPane().add(this.buildMainPanel(), "Center");
+ window.setSize(400, 100);
+ window.setLocation(200, 200);
+ window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildRadioButtonPanel(), BorderLayout.NORTH);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ private Component buildRadioButtonPanel() {
+ JPanel taskListPanel = new JPanel(new GridLayout(1, 0));
+ taskListPanel.add(this.buildRedRadioButton());
+ taskListPanel.add(this.buildGreenRadioButton());
+ taskListPanel.add(this.buildBlueRadioButton());
+ return taskListPanel;
+ }
+
+ private JRadioButton buildRedRadioButton() {
+ JRadioButton radioButton = new JRadioButton();
+ radioButton.setText("red");
+ radioButton.setModel(this.redButtonModel);
+ return radioButton;
+ }
+
+ private JRadioButton buildGreenRadioButton() {
+ JRadioButton radioButton = new JRadioButton();
+ radioButton.setText("green");
+ radioButton.setModel(this.greenButtonModel);
+ return radioButton;
+ }
+
+ private JRadioButton buildBlueRadioButton() {
+ JRadioButton radioButton = new JRadioButton();
+ radioButton.setText("blue");
+ radioButton.setModel(this.blueButtonModel);
+ return radioButton;
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new GridLayout(1, 0));
+ controlPanel.add(this.buildResetColorButton());
+ controlPanel.add(this.buildClearModelButton());
+ controlPanel.add(this.buildRestoreModelButton());
+ controlPanel.add(this.buildPrintModelButton());
+ return controlPanel;
+ }
+
+ private JButton buildResetColorButton() {
+ return new JButton(this.buildResetColorAction());
+ }
+
+ private Action buildResetColorAction() {
+ Action action = new AbstractAction("reset color") {
+ public void actionPerformed(ActionEvent event) {
+ RadioButtonModelAdapterUITest.this.resetColor();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void resetColor() {
+ this.testModel.setColor(TestModel.DEFAULT_COLOR);
+ }
+
+ private JButton buildClearModelButton() {
+ return new JButton(this.buildClearModelAction());
+ }
+
+ private Action buildClearModelAction() {
+ Action action = new AbstractAction("clear model") {
+ public void actionPerformed(ActionEvent event) {
+ RadioButtonModelAdapterUITest.this.clearModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void clearModel() {
+ this.testModelHolder.setValue(null);
+ }
+
+ private JButton buildRestoreModelButton() {
+ return new JButton(this.buildRestoreModelAction());
+ }
+
+ private Action buildRestoreModelAction() {
+ Action action = new AbstractAction("restore model") {
+ public void actionPerformed(ActionEvent event) {
+ RadioButtonModelAdapterUITest.this.restoreModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void restoreModel() {
+ this.testModelHolder.setValue(this.testModel);
+ }
+
+ private JButton buildPrintModelButton() {
+ return new JButton(this.buildPrintModelAction());
+ }
+
+ private Action buildPrintModelAction() {
+ Action action = new AbstractAction("print model") {
+ public void actionPerformed(ActionEvent event) {
+ RadioButtonModelAdapterUITest.this.printModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void printModel() {
+ System.out.println(this.testModel);
+ }
+
+
+ private static class TestModel extends AbstractModel {
+ private String color;
+ public static final String COLOR_PROPERTY = "color";
+ public static final String RED = "red";
+ public static final String GREEN = "green";
+ public static final String BLUE = "blue";
+ public static final String DEFAULT_COLOR = RED;
+ public static final String[] VALID_COLORS = {
+ RED,
+ GREEN,
+ BLUE
+ };
+
+ public TestModel() {
+ this(DEFAULT_COLOR);
+ }
+ public TestModel(String color) {
+ this.color = color;
+ }
+ public String getColor() {
+ return this.color;
+ }
+ public void setColor(String color) {
+ if ( ! CollectionTools.contains(VALID_COLORS, color)) {
+ throw new IllegalArgumentException(color);
+ }
+ Object old = this.color;
+ this.color = color;
+ this.firePropertyChanged(COLOR_PROPERTY, old, color);
+ }
+ @Override
+ public String toString() {
+ return "TestModel(" + this.color + ")";
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ReadOnlyTableModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ReadOnlyTableModelAdapterUITest.java
new file mode 100644
index 0000000..827b625
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/ReadOnlyTableModelAdapterUITest.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import org.eclipse.jpt.utility.internal.model.value.swing.ColumnAdapter;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TableModelAdapterTests.PersonColumnAdapter;
+
+/**
+ * Make it easy to test the table model adapter and
+ * renderers without any editing allowed.
+ */
+public class ReadOnlyTableModelAdapterUITest extends TableModelAdapterUITest {
+
+ public static void main(String[] args) throws Exception {
+ new ReadOnlyTableModelAdapterUITest().exec(args);
+ }
+
+ protected ReadOnlyTableModelAdapterUITest() {
+ super();
+ }
+
+ @Override
+ protected ColumnAdapter buildColumnAdapter() {
+ return new PersonColumnAdapter() {
+ @Override
+ public boolean isColumnEditable(int index) {
+ return false;
+ }
+ };
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/SpinnerModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/SpinnerModelAdapterTests.java
new file mode 100644
index 0000000..e5dce8d
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/SpinnerModelAdapterTests.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import javax.swing.SpinnerModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.SpinnerModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+public class SpinnerModelAdapterTests extends TestCase {
+ private PropertyValueModel valueHolder;
+ SpinnerModel spinnerModelAdapter;
+ boolean eventFired;
+
+ public SpinnerModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.valueHolder = new SimplePropertyValueModel(new Integer(0));
+ this.spinnerModelAdapter = new SpinnerModelAdapter(this.valueHolder);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testSetValueSpinnerModel() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ SpinnerModelAdapterTests.this.eventFired = true;
+ assertEquals(SpinnerModelAdapterTests.this.spinnerModelAdapter, e.getSource());
+ }
+ });
+ this.spinnerModelAdapter.setValue(new Integer(5));
+ assertTrue(this.eventFired);
+ assertEquals(new Integer(5), this.valueHolder.getValue());
+ }
+
+ public void testSetValueValueHolder() throws Exception {
+ this.eventFired = false;
+ this.spinnerModelAdapter.addChangeListener(new TestChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ SpinnerModelAdapterTests.this.eventFired = true;
+ assertEquals(SpinnerModelAdapterTests.this.spinnerModelAdapter, e.getSource());
+ }
+ });
+ assertEquals(new Integer(0), this.spinnerModelAdapter.getValue());
+ this.valueHolder.setValue(new Integer(7));
+ assertTrue(this.eventFired);
+ assertEquals(new Integer(7), this.spinnerModelAdapter.getValue());
+ }
+
+ public void testHasListeners() throws Exception {
+ SimplePropertyValueModel localValueHolder = (SimplePropertyValueModel) this.valueHolder;
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+
+ ChangeListener listener = new TestChangeListener();
+ this.spinnerModelAdapter.addChangeListener(listener);
+ assertTrue(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasListeners(this.spinnerModelAdapter);
+
+ this.spinnerModelAdapter.removeChangeListener(listener);
+ assertFalse(localValueHolder.hasAnyPropertyChangeListeners(ValueModel.VALUE));
+ this.verifyHasNoListeners(this.spinnerModelAdapter);
+ }
+
+ private void verifyHasNoListeners(Object adapter) throws Exception {
+ Object delegate = ClassTools.getFieldValue(adapter, "delegate");
+ Object[] listeners = (Object[]) ClassTools.executeMethod(delegate, "getChangeListeners");
+ assertEquals(0, listeners.length);
+ }
+
+ private void verifyHasListeners(Object adapter) throws Exception {
+ Object delegate = ClassTools.getFieldValue(adapter, "delegate");
+ Object[] listeners = (Object[]) ClassTools.executeMethod(delegate, "getChangeListeners");
+ assertFalse(listeners.length == 0);
+ }
+
+
+ private class TestChangeListener implements ChangeListener {
+ TestChangeListener() {
+ super();
+ }
+ public void stateChanged(ChangeEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/SpinnerModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/SpinnerModelAdapterUITest.java
new file mode 100644
index 0000000..7f96e3c
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/SpinnerModelAdapterUITest.java
@@ -0,0 +1,342 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerModel;
+import javax.swing.WindowConstants;
+
+import org.eclipse.jpt.utility.internal.CollectionTools;
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.DateSpinnerModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.ListSpinnerModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.NumberSpinnerModelAdapter;
+
+/**
+ * Play around with a set of spinners.
+ */
+public class SpinnerModelAdapterUITest {
+
+ private TestModel testModel;
+ private PropertyValueModel testModelHolder;
+
+ private PropertyValueModel birthDateHolder;
+ private SpinnerModel birthDateSpinnerModel;
+
+ private PropertyValueModel ageHolder;
+ private SpinnerModel ageSpinnerModel;
+
+ private PropertyValueModel eyeColorHolder;
+ private SpinnerModel eyeColorSpinnerModel;
+
+
+ public static void main(String[] args) throws Exception {
+ new SpinnerModelAdapterUITest().exec(args);
+ }
+
+ private SpinnerModelAdapterUITest() {
+ super();
+ }
+
+ private void exec(String[] args) throws Exception {
+ this.testModel = new TestModel();
+ this.testModelHolder = new SimplePropertyValueModel(this.testModel);
+
+ this.birthDateHolder = this.buildBirthDateHolder(this.testModelHolder);
+ this.birthDateSpinnerModel = this.buildBirthDateSpinnerModel(this.birthDateHolder);
+
+ this.ageHolder = this.buildAgeHolder(this.testModelHolder);
+ this.ageSpinnerModel = this.buildAgeSpinnerModel(this.ageHolder);
+
+ this.eyeColorHolder = this.buildEyeColorHolder(this.testModelHolder);
+ this.eyeColorSpinnerModel = this.buildEyeColorSpinnerModel(this.eyeColorHolder);
+
+ this.openWindow();
+ }
+
+ private PropertyValueModel buildBirthDateHolder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.BIRTH_DATE_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((TestModel) this.subject).getBirthDate();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setBirthDate((Date) value);
+ }
+ };
+ }
+
+ private SpinnerModel buildBirthDateSpinnerModel(PropertyValueModel valueHolder) {
+ return new DateSpinnerModelAdapter(valueHolder);
+ }
+
+ private PropertyValueModel buildAgeHolder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.AGE_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return new Integer(((TestModel) this.subject).getAge());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setAge(((Number) value).intValue());
+ }
+ };
+ }
+
+ private SpinnerModel buildAgeSpinnerModel(PropertyValueModel valueHolder) {
+ return new NumberSpinnerModelAdapter(valueHolder, ((Integer) valueHolder.getValue()).intValue(), TestModel.MIN_AGE, TestModel.MAX_AGE, 1);
+ }
+
+ private PropertyValueModel buildEyeColorHolder(ValueModel vm) {
+ return new PropertyAspectAdapter(vm, TestModel.EYE_COLOR_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((TestModel) this.subject).getEyeColor();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setEyeColor((String) value);
+ }
+ };
+ }
+
+ private SpinnerModel buildEyeColorSpinnerModel(PropertyValueModel valueHolder) {
+ return new ListSpinnerModelAdapter(valueHolder, TestModel.VALID_EYE_COLORS);
+ }
+
+ private void openWindow() {
+ JFrame window = new JFrame(this.getClass().getName());
+ window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ window.addWindowListener(this.buildWindowListener());
+ window.getContentPane().add(this.buildMainPanel(), "Center");
+ window.setSize(600, 100);
+ window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildSpinnerPanel(), BorderLayout.NORTH);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ private Component buildSpinnerPanel() {
+ JPanel taskListPanel = new JPanel(new GridLayout(1, 0));
+ taskListPanel.add(this.buildBirthDateSpinner());
+ taskListPanel.add(this.buildAgeSpinner());
+ taskListPanel.add(this.buildEyeColorSpinner());
+ return taskListPanel;
+ }
+
+ private JSpinner buildBirthDateSpinner() {
+ return new JSpinner(this.birthDateSpinnerModel);
+ }
+
+ private JSpinner buildAgeSpinner() {
+ return new JSpinner(this.ageSpinnerModel);
+ }
+
+ private JSpinner buildEyeColorSpinner() {
+ return new JSpinner(this.eyeColorSpinnerModel);
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new GridLayout(1, 0));
+ controlPanel.add(this.buildResetModelButton());
+ controlPanel.add(this.buildClearModelButton());
+ controlPanel.add(this.buildRestoreModelButton());
+ controlPanel.add(this.buildPrintModelButton());
+ return controlPanel;
+ }
+
+ private JButton buildResetModelButton() {
+ return new JButton(this.buildResetModelAction());
+ }
+
+ private Action buildResetModelAction() {
+ Action action = new AbstractAction("reset model") {
+ public void actionPerformed(ActionEvent event) {
+ SpinnerModelAdapterUITest.this.resetModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void resetModel() {
+ this.testModel.setBirthDate(TestModel.DEFAULT_BIRTH_DATE);
+ this.testModel.setEyeColor(TestModel.DEFAULT_EYE_COLOR);
+ }
+
+ private JButton buildClearModelButton() {
+ return new JButton(this.buildClearModelAction());
+ }
+
+ private Action buildClearModelAction() {
+ Action action = new AbstractAction("clear model") {
+ public void actionPerformed(ActionEvent event) {
+ SpinnerModelAdapterUITest.this.clearModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void clearModel() {
+ this.testModelHolder.setValue(null);
+ }
+
+ private JButton buildRestoreModelButton() {
+ return new JButton(this.buildRestoreModelAction());
+ }
+
+ private Action buildRestoreModelAction() {
+ Action action = new AbstractAction("restore model") {
+ public void actionPerformed(ActionEvent event) {
+ SpinnerModelAdapterUITest.this.restoreModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void restoreModel() {
+ this.testModelHolder.setValue(this.testModel);
+ }
+
+ private JButton buildPrintModelButton() {
+ return new JButton(this.buildPrintModelAction());
+ }
+
+ private Action buildPrintModelAction() {
+ Action action = new AbstractAction("print model") {
+ public void actionPerformed(ActionEvent event) {
+ SpinnerModelAdapterUITest.this.printModel();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void printModel() {
+ System.out.println("birth date: " + this.testModel.getBirthDate());
+ System.out.println("age: " + this.testModel.getAge());
+ System.out.println("eyes: " + this.testModel.getEyeColor());
+ }
+
+
+ private static class TestModel extends AbstractModel {
+ private Calendar birthCal = Calendar.getInstance();
+ // "virtual" properties
+ public static final String BIRTH_DATE_PROPERTY = "birthDate";
+ public static final String AGE_PROPERTY = "age";
+ public static final Date DEFAULT_BIRTH_DATE = new Date();
+ public static final int DEFAULT_AGE = 0;
+ public static final int MIN_AGE = 0;
+ public static final int MAX_AGE = 150;
+ private String eyeColor;
+ public static final String EYE_COLOR_PROPERTY = "eyeColor";
+ public static final String[] VALID_EYE_COLORS = {"blue", "brown", "green", "hazel", "pink"};
+ public static final String DEFAULT_EYE_COLOR = VALID_EYE_COLORS[3];
+
+ public TestModel() {
+ this(DEFAULT_BIRTH_DATE, DEFAULT_EYE_COLOR);
+ }
+ public TestModel(Date birthDate, String eyeColor) {
+ this.setBirthDate(birthDate);
+ this.setEyeColor(eyeColor);
+ }
+ public Date getBirthDate() {
+ return (Date) this.birthCal.getTime().clone();
+ }
+ public void setBirthDate(Date birthDate) {
+ Date oldBirthDate = this.getBirthDate();
+ int oldAge = this.getAge();
+ this.birthCal.setTimeInMillis(birthDate.getTime());
+ int newAge = this.getAge();
+ if (newAge < MIN_AGE || newAge > MAX_AGE) {
+ throw new IllegalArgumentException(birthDate.toString());
+ }
+ this.firePropertyChanged(BIRTH_DATE_PROPERTY, oldBirthDate, this.getBirthDate());
+ this.firePropertyChanged(AGE_PROPERTY, oldAge, newAge);
+ }
+ public int getAge() {
+ Calendar currentCal = Calendar.getInstance();
+ int age = currentCal.get(Calendar.YEAR) - this.birthCal.get(Calendar.YEAR);
+ if (currentCal.get(Calendar.MONTH) < this.birthCal.get(Calendar.MONTH)) {
+ age--;
+ } else if (currentCal.get(Calendar.MONTH) == this.birthCal.get(Calendar.MONTH)) {
+ if (currentCal.get(Calendar.DAY_OF_MONTH) < this.birthCal.get(Calendar.DAY_OF_MONTH)) {
+ age--;
+ }
+ }
+ return age;
+ }
+ public void setAge(int newAge) {
+ if (newAge < MIN_AGE || newAge > MAX_AGE) {
+ throw new IllegalArgumentException(String.valueOf(newAge));
+ }
+
+ int oldAge = this.getAge();
+ int delta = newAge - oldAge;
+
+ Calendar newBirthCal = Calendar.getInstance();
+ newBirthCal.setTimeInMillis(this.birthCal.getTime().getTime());
+ // if the age increased, the birth date must be "decreased"; and vice versa
+ newBirthCal.set(Calendar.YEAR, newBirthCal.get(Calendar.YEAR) - delta);
+ this.setBirthDate(newBirthCal.getTime());
+ }
+ public String getEyeColor() {
+ return this.eyeColor;
+ }
+ public void setEyeColor(String eyeColor) {
+ if ( ! CollectionTools.contains(VALID_EYE_COLORS, eyeColor)) {
+ throw new IllegalArgumentException(eyeColor);
+ }
+ Object old = this.eyeColor;
+ this.eyeColor = eyeColor;
+ this.firePropertyChanged(EYE_COLOR_PROPERTY, old, eyeColor);
+ }
+ public String toString() {
+ return "TestModel(birth: " + this.getBirthDate() + " - eyes: " + this.eyeColor + ")";
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TableModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TableModelAdapterTests.java
new file mode 100644
index 0000000..4d6783e
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TableModelAdapterTests.java
@@ -0,0 +1,634 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+import org.eclipse.jpt.utility.internal.CollectionTools;
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.iterators.CloneIterator;
+import org.eclipse.jpt.utility.internal.iterators.TransformationIterator;
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.value.CollectionAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.ColumnAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.TableModelAdapter;
+import org.eclipse.jpt.utility.tests.internal.TestTools;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ */
+public class TableModelAdapterTests extends TestCase {
+ private Crowd crowd;
+ TableModelEvent event;
+
+ public TableModelAdapterTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.crowd = this.buildCrowd();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestTools.clear(this);
+ super.tearDown();
+ }
+
+ public void testGetRowCount() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ assertEquals(0, tableModelAdapter.getRowCount());
+ // we need to add a listener to wake up the adapter
+ tableModelAdapter.addTableModelListener(this.buildTableModelListener());
+ assertEquals(this.crowd.peopleSize(), tableModelAdapter.getRowCount());
+ }
+
+ public void testGetColumnCount() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ assertEquals(PersonColumnAdapter.COLUMN_COUNT, tableModelAdapter.getColumnCount());
+ }
+
+ public void testGetValueAt() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ tableModelAdapter.addTableModelListener(this.buildTableModelListener());
+
+ List<String> sortedNames = this.sortedNames();
+ for (int i = 0; i < this.crowd.peopleSize(); i++) {
+ assertEquals(sortedNames.get(i), tableModelAdapter.getValueAt(i, PersonColumnAdapter.NAME_COLUMN));
+ }
+ }
+
+ public void testSetValueAt() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ this.event = null;
+ tableModelAdapter.addTableModelListener(new TestTableModelListener() {
+ @Override
+ public void tableChanged(TableModelEvent e) {
+ TableModelAdapterTests.this.event = e;
+ }
+ });
+
+ Person person = this.crowd.personNamed("Gollum");
+ assertEquals(Person.EYE_COLOR_BLUE, person.getEyeColor());
+ assertFalse(person.isEvil());
+ assertEquals(0, person.getRank());
+
+ for (int i = 0; i < tableModelAdapter.getRowCount(); i++) {
+ if (tableModelAdapter.getValueAt(i, PersonColumnAdapter.NAME_COLUMN).equals("Gollum")) {
+ tableModelAdapter.setValueAt(Person.EYE_COLOR_HAZEL, i, PersonColumnAdapter.EYE_COLOR_COLUMN);
+ tableModelAdapter.setValueAt(Boolean.TRUE, i, PersonColumnAdapter.EVIL_COLUMN);
+ tableModelAdapter.setValueAt(new Integer(-1), i, PersonColumnAdapter.RANK_COLUMN);
+ break;
+ }
+ }
+ assertNotNull(this.event);
+ assertEquals(Person.EYE_COLOR_HAZEL, person.getEyeColor());
+ assertTrue(person.isEvil());
+ assertEquals(-1, person.getRank());
+ }
+
+ public void testAddRow() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ this.event = null;
+ tableModelAdapter.addTableModelListener(this.buildSingleEventListener());
+ // add a person to the end of the list so we only trigger one event
+ this.crowd.addPerson("Zzzzz");
+ assertNotNull(this.event);
+ assertEquals(TableModelEvent.INSERT, this.event.getType());
+ assertEquals(TableModelEvent.ALL_COLUMNS, this.event.getColumn());
+ }
+
+ public void testRemoveRow() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ this.event = null;
+ tableModelAdapter.addTableModelListener(this.buildSingleEventListener());
+ // removing a person should only trigger one event, since a re-sort is not needed
+ this.crowd.removePerson(this.crowd.personNamed("Gollum"));
+ assertNotNull(this.event);
+ assertEquals(TableModelEvent.DELETE, this.event.getType());
+ assertEquals(TableModelEvent.ALL_COLUMNS, this.event.getColumn());
+ }
+
+ public void testChangeCell() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ this.event = null;
+ tableModelAdapter.addTableModelListener(this.buildSingleEventListener());
+ // add a person to the end of the list so we only trigger one event
+ Person person = this.crowd.personNamed("Gollum");
+ person.setEvil(true);
+ assertNotNull(this.event);
+ assertEquals(TableModelEvent.UPDATE, this.event.getType());
+ assertEquals(PersonColumnAdapter.EVIL_COLUMN, this.event.getColumn());
+ }
+
+ public void testLazyListListener() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ TableModelListener listener = this.buildTableModelListener();
+ assertTrue(this.crowd.hasNoCollectionChangeListeners(Crowd.PEOPLE_COLLECTION));
+ tableModelAdapter.addTableModelListener(listener);
+ assertTrue(this.crowd.hasAnyCollectionChangeListeners(Crowd.PEOPLE_COLLECTION));
+ tableModelAdapter.removeTableModelListener(listener);
+ assertTrue(this.crowd.hasNoCollectionChangeListeners(Crowd.PEOPLE_COLLECTION));
+ }
+
+ public void testLazyCellListener() throws Exception {
+ TableModelAdapter tableModelAdapter = this.buildTableModelAdapter();
+ TableModelListener listener = this.buildTableModelListener();
+ Person person = this.crowd.personNamed("Gollum");
+ assertTrue(person.hasNoPropertyChangeListeners(Person.NAME_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.BIRTH_DATE_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.EYE_COLOR_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.EVIL_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.RANK_PROPERTY));
+
+ tableModelAdapter.addTableModelListener(listener);
+ assertTrue(person.hasAnyPropertyChangeListeners(Person.NAME_PROPERTY));
+ assertTrue(person.hasAnyPropertyChangeListeners(Person.BIRTH_DATE_PROPERTY));
+ assertTrue(person.hasAnyPropertyChangeListeners(Person.EYE_COLOR_PROPERTY));
+ assertTrue(person.hasAnyPropertyChangeListeners(Person.EVIL_PROPERTY));
+ assertTrue(person.hasAnyPropertyChangeListeners(Person.RANK_PROPERTY));
+
+ tableModelAdapter.removeTableModelListener(listener);
+ assertTrue(person.hasNoPropertyChangeListeners(Person.NAME_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.BIRTH_DATE_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.EYE_COLOR_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.EVIL_PROPERTY));
+ assertTrue(person.hasNoPropertyChangeListeners(Person.RANK_PROPERTY));
+ }
+
+ private TableModelAdapter buildTableModelAdapter() {
+ return new TableModelAdapter(this.buildSortedPeopleAdapter(), this.buildColumnAdapter());
+ }
+
+ private ListValueModel buildSortedPeopleAdapter() {
+ return new SortedListValueModelAdapter(this.buildPeopleAdapter());
+ }
+
+ private CollectionValueModel buildPeopleAdapter() {
+ return new CollectionAspectAdapter(Crowd.PEOPLE_COLLECTION, this.crowd) {
+ @Override
+ protected Iterator<Person> getValueFromSubject() {
+ return ((Crowd) this.subject).people();
+ }
+ @Override
+ protected int sizeFromSubject() {
+ return ((Crowd) this.subject).peopleSize();
+ }
+ };
+ }
+
+ private Crowd buildCrowd() {
+ Crowd result = new Crowd();
+ result.addPerson("Bilbo");
+ result.addPerson("Gollum");
+ result.addPerson("Frodo");
+ result.addPerson("Samwise");
+ return result;
+ }
+
+ private ColumnAdapter buildColumnAdapter() {
+ return new PersonColumnAdapter();
+ }
+
+ private TableModelListener buildTableModelListener() {
+ return new TestTableModelListener();
+ }
+
+ private List<String> sortedNames() {
+ return new ArrayList<String>(CollectionTools.sortedSet(this.crowd.peopleNames()));
+ }
+
+ private TableModelListener buildSingleEventListener() {
+ return new TestTableModelListener() {
+ @Override
+ public void tableChanged(TableModelEvent e) {
+ // we expect only a single event
+ if (TableModelAdapterTests.this.event == null) {
+ TableModelAdapterTests.this.event = e;
+ } else {
+ fail("unexpected event");
+ }
+ }
+ };
+ }
+
+
+ // ********** classes **********
+
+ public static class PersonColumnAdapter implements ColumnAdapter {
+ public static final int COLUMN_COUNT = 7;
+
+ public static final int NAME_COLUMN = 0;
+ public static final int BIRTH_DATE_COLUMN = 1;
+ public static final int GONE_WEST_DATE_COLUMN = 2;
+ public static final int EYE_COLOR_COLUMN = 3;
+ public static final int EVIL_COLUMN = 4;
+ public static final int RANK_COLUMN = 5;
+ public static final int ADVENTURE_COUNT_COLUMN = 6;
+
+ private static final String[] COLUMN_NAMES = new String[] {
+ "Name",
+ "Birth",
+ "Gone West",
+ "Eyes",
+ "Evil",
+ "Rank",
+ "Adventures"
+ };
+
+
+ public int getColumnCount() {
+ return COLUMN_COUNT;
+ }
+
+ public String getColumnName(int index) {
+ return COLUMN_NAMES[index];
+ }
+
+ public Class<?> getColumnClass(int index) {
+ switch (index) {
+ case NAME_COLUMN: return Object.class;
+ case BIRTH_DATE_COLUMN: return Date.class;
+ case GONE_WEST_DATE_COLUMN: return Date.class;
+ case EYE_COLOR_COLUMN: return Object.class;
+ case EVIL_COLUMN: return Boolean.class;
+ case RANK_COLUMN: return Integer.class;
+ case ADVENTURE_COUNT_COLUMN:return Integer.class;
+ default: return Object.class;
+ }
+ }
+
+ public boolean isColumnEditable(int index) {
+ return index != NAME_COLUMN;
+ }
+
+ public PropertyValueModel[] cellModels(Object subject) {
+ Person person = (Person) subject;
+ PropertyValueModel[] result = new PropertyValueModel[COLUMN_COUNT];
+
+ result[NAME_COLUMN] = this.buildNameAdapter(person);
+ result[BIRTH_DATE_COLUMN] = this.buildBirthDateAdapter(person);
+ result[GONE_WEST_DATE_COLUMN] = this.buildGoneWestDateAdapter(person);
+ result[EYE_COLOR_COLUMN] = this.buildEyeColorAdapter(person);
+ result[EVIL_COLUMN] = this.buildEvilAdapter(person);
+ result[RANK_COLUMN] = this.buildRankAdapter(person);
+ result[ADVENTURE_COUNT_COLUMN] = this.buildAdventureCountAdapter(person);
+
+ return result;
+ }
+
+ private PropertyValueModel buildNameAdapter(Person person) {
+ return new PropertyAspectAdapter(Person.NAME_PROPERTY, person) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getName();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setName((String) value);
+ }
+ };
+ }
+
+ private PropertyValueModel buildBirthDateAdapter(Person person) {
+ return new PropertyAspectAdapter(Person.BIRTH_DATE_PROPERTY, person) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getBirthDate();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setBirthDate((Date) value);
+ }
+ };
+ }
+
+ private PropertyValueModel buildGoneWestDateAdapter(Person person) {
+ return new PropertyAspectAdapter(Person.GONE_WEST_DATE_PROPERTY, person) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getGoneWestDate();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setGoneWestDate((Date) value);
+ }
+ };
+ }
+
+ private PropertyValueModel buildEyeColorAdapter(Person person) {
+ return new PropertyAspectAdapter(Person.EYE_COLOR_PROPERTY, person) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getEyeColor();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setEyeColor((String) value);
+ }
+ };
+ }
+
+ private PropertyValueModel buildEvilAdapter(Person person) {
+ return new PropertyAspectAdapter(Person.EVIL_PROPERTY, person) {
+ @Override
+ protected Object getValueFromSubject() {
+ return Boolean.valueOf(((Person) this.subject).isEvil());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setEvil(((Boolean) value).booleanValue());
+ }
+ };
+ }
+
+ private PropertyValueModel buildRankAdapter(Person person) {
+ return new PropertyAspectAdapter(Person.RANK_PROPERTY, person) {
+ @Override
+ protected Object getValueFromSubject() {
+ return new Integer(((Person) this.subject).getRank());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setRank(((Integer) value).intValue());
+ }
+ };
+ }
+
+ private PropertyValueModel buildAdventureCountAdapter(Person person) {
+ return new PropertyAspectAdapter(Person.ADVENTURE_COUNT_PROPERTY, person) {
+ @Override
+ protected Object getValueFromSubject() {
+ return new Integer(((Person) this.subject).getAdventureCount());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setAdventureCount(((Integer) value).intValue());
+ }
+ };
+ }
+
+ }
+
+
+ public static class Crowd extends AbstractModel {
+ private final Collection<Person> people;
+ public static final String PEOPLE_COLLECTION = "people";
+
+ public Crowd() {
+ super();
+ this.people = new ArrayList<Person>();
+ }
+
+
+ public Iterator<Person> people() {
+ return new CloneIterator<Person>(this.people) {
+ @Override
+ protected void remove(Person person) {
+ Crowd.this.removePerson(person);
+ }
+ };
+ }
+
+ public int peopleSize() {
+ return this.people.size();
+ }
+
+ public Person addPerson(String name) {
+ this.checkPersonName(name);
+ return this.addPerson(new Person(this, name));
+ }
+
+ private Person addPerson(Person person) {
+ this.addItemToCollection(person, this.people, PEOPLE_COLLECTION);
+ return person;
+ }
+
+ public void removePerson(Person person) {
+ this.removeItemFromCollection(person, this.people, PEOPLE_COLLECTION);
+ }
+
+ public void removePeople(Collection<Person> persons) {
+ this.removeItemsFromCollection(persons, this.people, PEOPLE_COLLECTION);
+ }
+
+ public void removePeople(Iterator<Person> persons) {
+ this.removeItemsFromCollection(persons, this.people, PEOPLE_COLLECTION);
+ }
+
+ void checkPersonName(String personName) {
+ if (personName == null) {
+ throw new NullPointerException();
+ }
+ if (CollectionTools.contains(this.peopleNames(), personName)) {
+ throw new IllegalArgumentException(personName);
+ }
+ }
+
+ public Iterator<String> peopleNames() {
+ return new TransformationIterator<Person, String>(this.people.iterator()) {
+ @Override
+ protected String transform(Person person) {
+ return person.getName();
+ }
+ };
+ }
+
+ public Person personNamed(String name) {
+ for (Iterator<Person> stream = this.people.iterator(); stream.hasNext(); ) {
+ Person person = stream.next();
+ if (person.getName().equals(name)) {
+ return person;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, String.valueOf(this.people.size()) + " people");
+ }
+
+ }
+
+
+ public static class Person extends AbstractModel implements Comparable<Person> {
+ private Crowd crowd;
+ private String name;
+ public static final String NAME_PROPERTY= "name";
+ private Date birthDate;
+ public static final String BIRTH_DATE_PROPERTY= "birthDate";
+ private Date goneWestDate;
+ public static final String GONE_WEST_DATE_PROPERTY= "goneWestDate";
+ private String eyeColor;
+ public static final String EYE_COLOR_PROPERTY= "eyeColor";
+ public static final String EYE_COLOR_BLUE = "blue";
+ public static final String EYE_COLOR_GREEN = "green";
+ public static final String EYE_COLOR_BROWN = "brown";
+ public static final String EYE_COLOR_HAZEL = "hazel";
+ public static final String EYE_COLOR_PINK = "pink";
+ private static Collection<String> validEyeColors;
+ public static final String DEFAULT_EYE_COLOR = EYE_COLOR_BLUE;
+ private boolean evil;
+ public static final String EVIL_PROPERTY= "evil";
+ private int rank;
+ public static final String RANK_PROPERTY= "rank";
+ private int adventureCount;
+ public static final String ADVENTURE_COUNT_PROPERTY= "adventureCount";
+
+ Person(Crowd crowd, String name) {
+ super();
+ this.crowd = crowd;
+ this.name = name;
+ this.birthDate = new Date();
+ Calendar c = Calendar.getInstance();
+ c.add(Calendar.YEAR, 250);
+ this.goneWestDate = new Date(c.getTimeInMillis());
+ this.eyeColor = DEFAULT_EYE_COLOR;
+ this.evil = false;
+ this.rank = 0;
+ this.adventureCount = 0;
+ }
+
+ public static Collection<String> getValidEyeColors() {
+ if (validEyeColors == null) {
+ validEyeColors = buildValidEyeColors();
+ }
+ return validEyeColors;
+ }
+
+ private static Collection<String> buildValidEyeColors() {
+ Collection<String> result = new ArrayList<String>();
+ result.add(EYE_COLOR_BLUE);
+ result.add(EYE_COLOR_GREEN);
+ result.add(EYE_COLOR_BROWN);
+ result.add(EYE_COLOR_HAZEL);
+ result.add(EYE_COLOR_PINK);
+ return result;
+ }
+
+ public Crowd getCrowd() {
+ return this.crowd;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ this.crowd.checkPersonName(name);
+ Object old = this.name;
+ this.name = name;
+ this.firePropertyChanged(NAME_PROPERTY, old, name);
+ }
+
+ public Date getBirthDate() {
+ return this.birthDate;
+ }
+ public void setBirthDate(Date birthDate) {
+ Object old = this.birthDate;
+ this.birthDate = birthDate;
+ this.firePropertyChanged(BIRTH_DATE_PROPERTY, old, birthDate);
+ }
+
+ public Date getGoneWestDate() {
+ return this.goneWestDate;
+ }
+ public void setGoneWestDate(Date goneWestDate) {
+ Object old = this.goneWestDate;
+ this.goneWestDate = goneWestDate;
+ this.firePropertyChanged(GONE_WEST_DATE_PROPERTY, old, goneWestDate);
+ }
+
+ public String getEyeColor() {
+ return this.eyeColor;
+ }
+ public void setEyeColor(String eyeColor) {
+ if (! getValidEyeColors().contains(eyeColor)) {
+ throw new IllegalArgumentException(eyeColor);
+ }
+ Object old = this.eyeColor;
+ this.eyeColor = eyeColor;
+ this.firePropertyChanged(EYE_COLOR_PROPERTY, old, eyeColor);
+ }
+
+ public boolean isEvil() {
+ return this.evil;
+ }
+ public void setEvil(boolean evil) {
+ boolean old = this.evil;
+ this.evil = evil;
+ this.firePropertyChanged(EVIL_PROPERTY, old, evil);
+ }
+
+ public int getRank() {
+ return this.rank;
+ }
+ public void setRank(int rank) {
+ int old = this.rank;
+ this.rank = rank;
+ this.firePropertyChanged(RANK_PROPERTY, old, rank);
+ }
+
+ public int getAdventureCount() {
+ return this.adventureCount;
+ }
+ public void setAdventureCount(int adventureCount) {
+ int old = this.adventureCount;
+ this.adventureCount = adventureCount;
+ this.firePropertyChanged(ADVENTURE_COUNT_PROPERTY, old, adventureCount);
+ }
+
+ public int compareTo(Person p) {
+ return this.name.compareToIgnoreCase(p.name);
+ }
+
+ @Override
+ public String toString() {
+ return this.name +
+ "\tborn: " + DateFormat.getDateInstance().format(this.birthDate) +
+ "\tgone west: " + DateFormat.getDateInstance().format(this.goneWestDate) +
+ "\teyes: " + this.eyeColor +
+ "\tevil: " + this.evil +
+ "\trank: " + this.rank +
+ "\tadventures: " + this.adventureCount
+ ;
+ }
+
+ }
+
+
+ private class TestTableModelListener implements TableModelListener {
+ TestTableModelListener() {
+ super();
+ }
+ public void tableChanged(TableModelEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TableModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TableModelAdapterUITest.java
new file mode 100644
index 0000000..3e02eaa
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TableModelAdapterUITest.java
@@ -0,0 +1,732 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.util.Date;
+import java.util.Iterator;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ButtonModel;
+import javax.swing.ComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+import javax.swing.SpinnerModel;
+import javax.swing.UIManager;
+import javax.swing.WindowConstants;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableModel;
+import javax.swing.text.Document;
+
+import org.eclipse.jpt.utility.internal.ClassTools;
+import org.eclipse.jpt.utility.internal.CollectionTools;
+import org.eclipse.jpt.utility.internal.model.value.CollectionAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ItemPropertyListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimpleCollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.CheckBoxModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.ColumnAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.ComboBoxModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.DateSpinnerModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.DocumentAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.ListModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.NumberSpinnerModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.swing.ObjectListSelectionModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.TableModelAdapter;
+import org.eclipse.jpt.utility.internal.swing.CheckBoxTableCellRenderer;
+import org.eclipse.jpt.utility.internal.swing.ComboBoxTableCellRenderer;
+import org.eclipse.jpt.utility.internal.swing.SpinnerTableCellRenderer;
+import org.eclipse.jpt.utility.internal.swing.TableCellEditorAdapter;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TableModelAdapterTests.Crowd;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TableModelAdapterTests.Person;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TableModelAdapterTests.PersonColumnAdapter;
+
+/**
+ * an example UI for testing the TableModelAdapter
+ * "name" column is read-only text field
+ * "birth date" column is date text field
+ * "gone west date" column is date spinner
+ * "eye color" column is combo-box
+ * "evil" column is check box
+ * "rank" column is number text field
+ * "adventure count" column is number spinner
+ *
+ * Note that the table model and row selection model share the same
+ * list value model (the sorted people adapter)
+ */
+public class TableModelAdapterUITest {
+ private CollectionValueModel eyeColorListHolder;
+ private PropertyValueModel crowdHolder;
+ private PropertyValueModel selectedPersonHolder;
+ private ListValueModel sortedPeopleAdapter;
+ private TableModel tableModel;
+ private ObjectListSelectionModel rowSelectionModel;
+ private Action removeAction;
+ private Action renameAction;
+
+ public static void main(String[] args) throws Exception {
+ new TableModelAdapterUITest().exec(args);
+ }
+
+ protected TableModelAdapterUITest() {
+ super();
+ }
+
+ protected void exec(String[] args) throws Exception {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ this.eyeColorListHolder = this. buildEyeColorCollectionHolder();
+ this.crowdHolder = this.buildCrowdHolder();
+ this.selectedPersonHolder = this.buildSelectedPersonHolder();
+ this.sortedPeopleAdapter = this.buildSortedPeopleAdapter();
+ this.tableModel = this.buildTableModel();
+ this.rowSelectionModel = this.buildRowSelectionModel();
+ this.openWindow();
+ }
+
+ private CollectionValueModel buildEyeColorCollectionHolder() {
+ return new SimpleCollectionValueModel(Person.getValidEyeColors());
+ }
+
+ private PropertyValueModel buildCrowdHolder() {
+ return new SimplePropertyValueModel(this.buildCrowd());
+ }
+
+ private Crowd buildCrowd() {
+ Crowd crowd = new Crowd();
+
+ Person p = crowd.addPerson("Bilbo");
+ p.setEyeColor(Person.EYE_COLOR_BROWN);
+ p.setRank(22);
+ p.setAdventureCount(1);
+
+ p = crowd.addPerson("Gollum");
+ p.setEyeColor(Person.EYE_COLOR_PINK);
+ p.setEvil(true);
+ p.setRank(2);
+ p.setAdventureCount(50);
+
+ p = crowd.addPerson("Frodo");
+ p.setEyeColor(Person.EYE_COLOR_BLUE);
+ p.setRank(34);
+ p.setAdventureCount(1);
+
+ p = crowd.addPerson("Samwise");
+ p.setEyeColor(Person.EYE_COLOR_GREEN);
+ p.setRank(19);
+ p.setAdventureCount(1);
+
+ return crowd;
+ }
+
+ private PropertyValueModel buildSelectedPersonHolder() {
+ return new SimplePropertyValueModel();
+ }
+
+ private ListValueModel buildSortedPeopleAdapter() {
+ return new SortedListValueModelAdapter(this.buildPeopleNameAdapter());
+ }
+
+ // the list will need to be re-sorted if a name changes
+ private ListValueModel buildPeopleNameAdapter() {
+ return new ItemPropertyListValueModelAdapter(this.buildPeopleAdapter(), Person.NAME_PROPERTY);
+ }
+
+ private CollectionValueModel buildPeopleAdapter() {
+ return new CollectionAspectAdapter(this.crowdHolder, Crowd.PEOPLE_COLLECTION) {
+ @Override
+ protected Iterator getValueFromSubject() {
+ return ((Crowd) this.subject).people();
+ }
+ @Override
+ protected int sizeFromSubject() {
+ return ((Crowd) this.subject).peopleSize();
+ }
+ };
+ }
+
+ private TableModel buildTableModel() {
+ return new TableModelAdapter(this.sortedPeopleAdapter, this.buildColumnAdapter());
+ }
+
+ protected ColumnAdapter buildColumnAdapter() {
+ return new PersonColumnAdapter();
+ }
+
+ private ObjectListSelectionModel buildRowSelectionModel() {
+ ObjectListSelectionModel rsm = new ObjectListSelectionModel(new ListModelAdapter(this.sortedPeopleAdapter));
+ rsm.addListSelectionListener(this.buildRowSelectionListener());
+ rsm.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ return rsm;
+ }
+
+ private ListSelectionListener buildRowSelectionListener() {
+ return new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ TableModelAdapterUITest.this.rowSelectionChanged(e);
+ }
+ };
+ }
+
+ void rowSelectionChanged(ListSelectionEvent e) {
+ Object selection = this.rowSelectionModel.getSelectedValue();
+ this.selectedPersonHolder.setValue(selection);
+ boolean personSelected = (selection != null);
+ this.removeAction.setEnabled(personSelected);
+ this.renameAction.setEnabled(personSelected);
+ }
+
+ private void openWindow() {
+ JFrame window = new JFrame(ClassTools.shortClassNameForObject(this));
+ window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ window.addWindowListener(this.buildWindowListener());
+ window.getContentPane().add(this.buildMainPanel(), "Center");
+ window.setLocation(200, 200);
+ window.setSize(600, 400);
+ window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildTablePane(), BorderLayout.CENTER);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ private Component buildTablePane() {
+ return new JScrollPane(this.buildTable());
+ }
+
+ private JTable buildTable() {
+ JTable table = new JTable(this.tableModel);
+ table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); // see Java bug 5007652
+ table.setSelectionModel(this.rowSelectionModel);
+ table.setDoubleBuffered(true);
+ table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
+ int rowHeight = 20; // start with minimum of 20
+
+ // gone west column (spinner)
+ TableColumn column = table.getColumnModel().getColumn(PersonColumnAdapter.GONE_WEST_DATE_COLUMN);
+ SpinnerTableCellRenderer spinnerRenderer = this.buildDateSpinnerRenderer();
+ column.setCellRenderer(spinnerRenderer);
+ column.setCellEditor(new TableCellEditorAdapter(this.buildDateSpinnerRenderer()));
+ rowHeight = Math.max(rowHeight, spinnerRenderer.getPreferredHeight());
+
+ // eye color column (combo-box)
+ // the jdk combo-box renderer looks like a text field
+ // until the user starts an edit - use a custom one
+ column = table.getColumnModel().getColumn(PersonColumnAdapter.EYE_COLOR_COLUMN);
+ ComboBoxTableCellRenderer eyeColorRenderer = this.buildEyeColorComboBoxRenderer();
+ column.setCellRenderer(eyeColorRenderer);
+ column.setCellEditor(new TableCellEditorAdapter(this.buildEyeColorComboBoxRenderer()));
+ rowHeight = Math.max(rowHeight, eyeColorRenderer.getPreferredHeight());
+
+ // evil (check box)
+ // the jdk check box renderer and editor suck - use a custom ones
+ column = table.getColumnModel().getColumn(PersonColumnAdapter.EVIL_COLUMN);
+ CheckBoxTableCellRenderer evilRenderer = new CheckBoxTableCellRenderer();
+ column.setCellRenderer(evilRenderer);
+ column.setCellEditor(new TableCellEditorAdapter(new CheckBoxTableCellRenderer()));
+ rowHeight = Math.max(rowHeight, evilRenderer.getPreferredHeight());
+
+ // adventure count column (spinner)
+ column = table.getColumnModel().getColumn(PersonColumnAdapter.ADVENTURE_COUNT_COLUMN);
+ spinnerRenderer = this.buildNumberSpinnerRenderer();
+ column.setCellRenderer(spinnerRenderer);
+ column.setCellEditor(new TableCellEditorAdapter(this.buildNumberSpinnerRenderer()));
+ rowHeight = Math.max(rowHeight, spinnerRenderer.getPreferredHeight());
+
+ table.setRowHeight(rowHeight);
+ return table;
+ }
+
+ private SpinnerTableCellRenderer buildDateSpinnerRenderer() {
+ return new SpinnerTableCellRenderer(new DateSpinnerModelAdapter(new SimplePropertyValueModel()));
+ }
+
+ private SpinnerTableCellRenderer buildNumberSpinnerRenderer() {
+ return new SpinnerTableCellRenderer(new NumberSpinnerModelAdapter(new SimplePropertyValueModel()));
+ }
+
+ private ComboBoxTableCellRenderer buildEyeColorComboBoxRenderer() {
+ return new ComboBoxTableCellRenderer(this.buildReadOnlyEyeColorComboBoxModel(), this.buildEyeColorRenderer());
+ }
+
+ private ComboBoxModel buildReadOnlyEyeColorComboBoxModel() {
+ return new ComboBoxModelAdapter(this.eyeColorListHolder, new SimplePropertyValueModel());
+ }
+
+ private ListCellRenderer buildEyeColorRenderer() {
+ return new EyeColorRenderer();
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new GridLayout(0, 1));
+ controlPanel.add(this.buildButtonPanel());
+ controlPanel.add(this.buildPersonPanel());
+ return controlPanel;
+ }
+
+ private Component buildButtonPanel() {
+ JPanel buttonPanel = new JPanel(new GridLayout(1, 0));
+ buttonPanel.add(this.buildAddButton());
+ buttonPanel.add(this.buildRemoveButton());
+ buttonPanel.add(this.buildRenameButton());
+ buttonPanel.add(this.buildAddEyeColorButton());
+ buttonPanel.add(this.buildPrintButton());
+ buttonPanel.add(this.buildResetButton());
+ return buttonPanel;
+ }
+
+ private Component buildPersonPanel() {
+ JPanel personPanel = new JPanel(new GridLayout(1, 0));
+ personPanel.add(this.buildNameTextField());
+ personPanel.add(this.buildBirthDateSpinner());
+ personPanel.add(this.buildGoneWestDateSpinner());
+ personPanel.add(this.buildEyeColorComboBox());
+ personPanel.add(this.buildEvilCheckBox());
+ personPanel.add(this.buildRankSpinner());
+ personPanel.add(this.buildAdventureCountSpinner());
+ return personPanel;
+ }
+
+
+ // ********** add button **********
+
+ private JButton buildAddButton() {
+ return new JButton(this.buildAddAction());
+ }
+
+ private Action buildAddAction() {
+ Action action = new AbstractAction("add") {
+ public void actionPerformed(ActionEvent event) {
+ TableModelAdapterUITest.this.addPerson();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void addPerson() {
+ String name = this.getNameFromUser();
+ if (name != null) {
+ this.setSelectedPerson(this.crowd().addPerson(name));
+ }
+ }
+
+
+ // ********** remove button **********
+
+ private JButton buildRemoveButton() {
+ return new JButton(this.buildRemoveAction());
+ }
+
+ private Action buildRemoveAction() {
+ this.removeAction = new AbstractAction("remove") {
+ public void actionPerformed(ActionEvent event) {
+ TableModelAdapterUITest.this.removePerson();
+ }
+ };
+ this.removeAction.setEnabled(false);
+ return this.removeAction;
+ }
+
+ void removePerson() {
+ Person person = this.selectedPerson();
+ if (person != null) {
+ this.crowd().removePerson(person);
+ }
+ }
+
+
+ // ********** rename button **********
+
+ private JButton buildRenameButton() {
+ return new JButton(this.buildRenameAction());
+ }
+
+ private Action buildRenameAction() {
+ this.renameAction = new AbstractAction("rename") {
+ public void actionPerformed(ActionEvent event) {
+ TableModelAdapterUITest.this.renamePerson();
+ }
+ };
+ this.renameAction.setEnabled(false);
+ return this.renameAction;
+ }
+
+ void renamePerson() {
+ Person person = this.selectedPerson();
+ if (person != null) {
+ String name = this.promptUserForName(person.getName());
+ if (name != null) {
+ person.setName(name);
+ this.setSelectedPerson(person);
+ }
+ }
+ }
+
+
+ // ********** add eye color button **********
+
+ private JButton buildAddEyeColorButton() {
+ return new JButton(this.buildAddEyeColorAction());
+ }
+
+ private Action buildAddEyeColorAction() {
+ Action action = new AbstractAction("add eye color") {
+ public void actionPerformed(ActionEvent event) {
+ TableModelAdapterUITest.this.addEyeColor();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void addEyeColor() {
+ String color = this.promptUserForEyeColor();
+ if (color != null) {
+ this.eyeColorListHolder.addItem(color);
+ }
+ }
+
+ private String promptUserForEyeColor() {
+ while (true) {
+ String eyeColor = JOptionPane.showInputDialog("Eye Color");
+ if (eyeColor == null) {
+ return null; // user pressed <Cancel>
+ }
+ if ((eyeColor.length() == 0)) {
+ JOptionPane.showMessageDialog(null, "The eye color is required.", "Invalid Eye Color", JOptionPane.ERROR_MESSAGE);
+ } else if (CollectionTools.contains((Iterator) this.eyeColorListHolder.getValue(), eyeColor)) {
+ JOptionPane.showMessageDialog(null, "The eye color already exists.", "Invalid Eye Color", JOptionPane.ERROR_MESSAGE);
+ } else {
+ return eyeColor;
+ }
+ }
+ }
+
+
+ // ********** print button **********
+
+ private JButton buildPrintButton() {
+ return new JButton(this.buildPrintAction());
+ }
+
+ private Action buildPrintAction() {
+ Action action = new AbstractAction("print") {
+ public void actionPerformed(ActionEvent event) {
+ TableModelAdapterUITest.this.printCrowd();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void printCrowd() {
+ System.out.println(this.crowd());
+ for (Iterator<Person> stream = this.crowd().people(); stream.hasNext(); ) {
+ System.out.println("\t" + stream.next());
+ }
+ }
+
+
+ // ********** reset button **********
+
+ private JButton buildResetButton() {
+ return new JButton(this.buildResetAction());
+ }
+
+ private Action buildResetAction() {
+ Action action = new AbstractAction("reset") {
+ public void actionPerformed(ActionEvent event) {
+ TableModelAdapterUITest.this.reset();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ void reset() {
+ this.crowdHolder.setValue(this.buildCrowd());
+ }
+
+
+ // ********** new name dialog **********
+
+ private String getNameFromUser() {
+ return this.promptUserForName(null);
+ }
+
+ private String promptUserForName(String originalName) {
+ while (true) {
+ String name = JOptionPane.showInputDialog("Person Name");
+ if (name == null) {
+ return null; // user pressed <Cancel>
+ }
+ if ((name.length() == 0)) {
+ JOptionPane.showMessageDialog(null, "The name is required.", "Invalid Name", JOptionPane.ERROR_MESSAGE);
+ } else if (CollectionTools.contains(this.crowd().peopleNames(), name)) {
+ JOptionPane.showMessageDialog(null, "The name already exists.", "Invalid Name", JOptionPane.ERROR_MESSAGE);
+ } else {
+ return name;
+ }
+ }
+ }
+
+
+ // ********** name text field **********
+
+ private Component buildNameTextField() {
+ JTextField textField = new JTextField(this.buildNameDocument(), null, 0);
+ textField.setEditable(false);
+ return textField;
+ }
+
+ private Document buildNameDocument() {
+ return new DocumentAdapter(this.buildNameAdapter());
+ }
+
+ private PropertyValueModel buildNameAdapter() {
+ return new PropertyAspectAdapter(this.selectedPersonHolder, Person.NAME_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getName();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setName((String) value);
+ }
+ };
+ }
+
+
+ // ********** birth date spinner **********
+
+ private JSpinner buildBirthDateSpinner() {
+ return new JSpinner(this.buildBirthDateSpinnerModel());
+ }
+
+ private SpinnerModel buildBirthDateSpinnerModel() {
+ return new DateSpinnerModelAdapter(this.buildBirthDateAdapter());
+ }
+
+ private PropertyValueModel buildBirthDateAdapter() {
+ return new PropertyAspectAdapter(this.selectedPersonHolder, Person.BIRTH_DATE_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getBirthDate();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setBirthDate((Date) value);
+ }
+ };
+ }
+
+
+ // ********** gone west date spinner **********
+
+ private JSpinner buildGoneWestDateSpinner() {
+ return new JSpinner(this.buildGoneWestDateSpinnerModel());
+ }
+
+ private SpinnerModel buildGoneWestDateSpinnerModel() {
+ return new DateSpinnerModelAdapter(this.buildGoneWestDateAdapter());
+ }
+
+ private PropertyValueModel buildGoneWestDateAdapter() {
+ return new PropertyAspectAdapter(this.selectedPersonHolder, Person.GONE_WEST_DATE_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getGoneWestDate();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setGoneWestDate((Date) value);
+ }
+ };
+ }
+
+
+ // ********** eye color combo-box **********
+
+ private JComboBox buildEyeColorComboBox() {
+ return new JComboBox(this.buildEyeColorComboBoxModel());
+ }
+
+ private ComboBoxModel buildEyeColorComboBoxModel() {
+ return new ComboBoxModelAdapter(this.eyeColorListHolder, this.buildEyeColorAdapter());
+ }
+
+ private PropertyValueModel buildEyeColorAdapter() {
+ return new PropertyAspectAdapter(this.selectedPersonHolder, Person.EYE_COLOR_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((Person) this.subject).getEyeColor();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setEyeColor((String) value);
+ }
+ };
+ }
+
+
+ // ********** evil check box **********
+
+ private JCheckBox buildEvilCheckBox() {
+ JCheckBox checkBox = new JCheckBox();
+ checkBox.setText("evil");
+ checkBox.setModel(this.buildEvilCheckBoxModel());
+ return checkBox;
+ }
+
+ private ButtonModel buildEvilCheckBoxModel() {
+ return new CheckBoxModelAdapter(this.buildEvilAdapter());
+ }
+
+ private PropertyValueModel buildEvilAdapter() {
+ return new PropertyAspectAdapter(this.selectedPersonHolder, Person.EVIL_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return Boolean.valueOf(((Person) this.subject).isEvil());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setEvil(((Boolean) value).booleanValue());
+ }
+ };
+ }
+
+
+ // ********** rank spinner **********
+
+ private JSpinner buildRankSpinner() {
+ return new JSpinner(this.buildRankSpinnerModel());
+ }
+
+ private SpinnerModel buildRankSpinnerModel() {
+ return new NumberSpinnerModelAdapter(this.buildRankAdapter());
+ }
+
+ private PropertyValueModel buildRankAdapter() {
+ return new PropertyAspectAdapter(this.selectedPersonHolder, Person.RANK_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return new Integer(((Person) this.subject).getRank());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setRank(((Integer) value).intValue());
+ }
+ };
+ }
+
+
+ // ********** adventure count spinner **********
+
+ private JSpinner buildAdventureCountSpinner() {
+ return new JSpinner(this.buildAdventureCountSpinnerModel());
+ }
+
+ private SpinnerModel buildAdventureCountSpinnerModel() {
+ return new NumberSpinnerModelAdapter(this.buildAdventureCountAdapter());
+ }
+
+ private PropertyValueModel buildAdventureCountAdapter() {
+ return new PropertyAspectAdapter(this.selectedPersonHolder, Person.ADVENTURE_COUNT_PROPERTY) {
+ @Override
+ protected Object getValueFromSubject() {
+ return new Integer(((Person) this.subject).getAdventureCount());
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((Person) this.subject).setAdventureCount(((Integer) value).intValue());
+ }
+ };
+ }
+
+
+ // ********** queries **********
+
+ private Crowd crowd() {
+ return (Crowd) this.crowdHolder.getValue();
+ }
+
+ private Person selectedPerson() {
+ if (this.rowSelectionModel.isSelectionEmpty()) {
+ return null;
+ }
+ return (Person) this.rowSelectionModel.getSelectedValue();
+ }
+
+ private void setSelectedPerson(Person person) {
+ this.rowSelectionModel.setSelectedValue(person);
+ }
+
+
+ // ********** custom renderer **********
+
+ /**
+ * This is simply an example of a renderer for the embedded combo-box.
+ * It does nothing special unless you uncomment the code below....
+ */
+ private class EyeColorRenderer extends DefaultListCellRenderer {
+ EyeColorRenderer() {
+ super();
+ }
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ // just do something to show the renderer is working...
+ // value = ">" + value;
+ return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TreeModelAdapterTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TreeModelAdapterTests.java
new file mode 100644
index 0000000..d70e7a7
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TreeModelAdapterTests.java
@@ -0,0 +1,831 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.swing.Icon;
+import javax.swing.JTree;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.TreeModel;
+
+import org.eclipse.jpt.utility.internal.HashBag;
+import org.eclipse.jpt.utility.internal.IndentingPrintWriter;
+import org.eclipse.jpt.utility.internal.iterators.ReadOnlyIterator;
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.jpt.utility.internal.model.event.PropertyChangeEvent;
+import org.eclipse.jpt.utility.internal.model.listener.PropertyChangeListener;
+import org.eclipse.jpt.utility.internal.model.value.AbstractTreeNodeValueModel;
+import org.eclipse.jpt.utility.internal.model.value.CollectionAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.CollectionValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ItemPropertyListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.ListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.NullListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SortedListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.TransformationListValueModelAdapter;
+import org.eclipse.jpt.utility.internal.model.value.TreeNodeValueModel;
+import org.eclipse.jpt.utility.internal.model.value.ValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.TreeModelAdapter;
+import org.eclipse.jpt.utility.internal.swing.Displayable;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ */
+public class TreeModelAdapterTests extends TestCase {
+ boolean eventFired;
+
+ public TreeModelAdapterTests(String name) {
+ super(name);
+ }
+
+ public void testGetRoot() {
+ TreeModel treeModel = this.buildSortedTreeModel();
+ treeModel.addTreeModelListener(new TestTreeModelListener());
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+ TestModel root = rootNode.getTestModel();
+ assertEquals("root", root.getName());
+// root.dump();
+// rootNode.dump();
+ }
+
+ public void testGetChild() {
+ TreeModel treeModel = this.buildSortedTreeModel();
+ treeModel.addTreeModelListener(new TestTreeModelListener());
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+
+ TestNode expected = rootNode.childNamed("node 1");
+ TestNode actual = (TestNode) treeModel.getChild(rootNode, 1);
+ assertEquals(expected, actual);
+
+ expected = rootNode.childNamed("node 2");
+ actual = (TestNode) treeModel.getChild(rootNode, 2);
+ assertEquals(expected, actual);
+ }
+
+ public void testGetChildCount() {
+ TreeModel treeModel = this.buildSortedTreeModel();
+ treeModel.addTreeModelListener(new TestTreeModelListener());
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+
+ assertEquals(5, treeModel.getChildCount(rootNode));
+
+ TestNode node = rootNode.childNamed("node 1");
+ assertEquals(1, treeModel.getChildCount(node));
+ }
+
+ public void testGetIndexOfChild() {
+ TreeModel treeModel = this.buildSortedTreeModel();
+ treeModel.addTreeModelListener(new TestTreeModelListener());
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+
+ TestNode child = rootNode.childNamed("node 0");
+ assertEquals(0, treeModel.getIndexOfChild(rootNode, child));
+
+ child = rootNode.childNamed("node 1");
+ assertEquals(1, treeModel.getIndexOfChild(rootNode, child));
+
+ child = rootNode.childNamed("node 2");
+ assertEquals(2, treeModel.getIndexOfChild(rootNode, child));
+ TestNode grandchild = child.childNamed("node 2.2");
+ assertEquals(2, treeModel.getIndexOfChild(child, grandchild));
+ }
+
+ public void testIsLeaf() {
+ TreeModel treeModel = this.buildSortedTreeModel();
+ treeModel.addTreeModelListener(new TestTreeModelListener());
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+ assertFalse(treeModel.isLeaf(rootNode));
+ TestNode node = rootNode.childNamed("node 1");
+ assertFalse(treeModel.isLeaf(node));
+ node = rootNode.childNamed("node 3");
+ assertTrue(treeModel.isLeaf(node));
+ }
+
+
+ public void testTreeNodesChanged() {
+ // the only way to trigger a "node changed" event is to use an unsorted tree;
+ // a sorted tree will will trigger only "node removed" and "node inserted" events
+ TreeModel treeModel = this.buildUnsortedTreeModel();
+ this.eventFired = false;
+ treeModel.addTreeModelListener(new TestTreeModelListener() {
+ @Override
+ public void treeNodesChanged(TreeModelEvent e) {
+ TreeModelAdapterTests.this.eventFired = true;
+ }
+ });
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+ TestNode node = rootNode.childNamed("node 1");
+ TestModel tm = node.getTestModel();
+ tm.setName("node 1++");
+ assertTrue(this.eventFired);
+
+ this.eventFired = false;
+ node = node.childNamed("node 1.1");
+ tm = node.getTestModel();
+ tm.setName("node 1.1++");
+ assertTrue(this.eventFired);
+ }
+
+ public void testTreeNodesInserted() {
+ // use an unsorted tree so the nodes are not re-shuffled...
+ TreeModel treeModel = this.buildUnsortedTreeModel();
+ this.eventFired = false;
+ treeModel.addTreeModelListener(new TestTreeModelListener() {
+ @Override
+ public void treeNodesInserted(TreeModelEvent e) {
+ TreeModelAdapterTests.this.eventFired = true;
+ }
+ });
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+ TestNode node = rootNode.childNamed("node 1");
+ TestModel tm = node.getTestModel();
+ tm.addChild("new child...");
+ assertTrue(this.eventFired);
+
+ this.eventFired = false;
+ node = node.childNamed("node 1.1");
+ tm = node.getTestModel();
+ tm.addChild("another new child...");
+ assertTrue(this.eventFired);
+ }
+
+ public void testTreeNodesRemoved() {
+ TreeModel treeModel = this.buildUnsortedTreeModel();
+ this.eventFired = false;
+ treeModel.addTreeModelListener(new TestTreeModelListener() {
+ @Override
+ public void treeNodesRemoved(TreeModelEvent e) {
+ TreeModelAdapterTests.this.eventFired = true;
+ }
+ });
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+ TestModel root = rootNode.getTestModel();
+ root.removeChild(root.childNamed("node 3"));
+ assertTrue(this.eventFired);
+
+ this.eventFired = false;
+ TestNode node = rootNode.childNamed("node 2");
+ TestModel tm = node.getTestModel();
+ tm.removeChild(tm.childNamed("node 2.2"));
+ assertTrue(this.eventFired);
+ }
+
+ public void testTreeStructureChanged() {
+ PropertyValueModel nodeHolder = new SimplePropertyValueModel(this.buildSortedRootNode());
+ TreeModel treeModel = new TreeModelAdapter(nodeHolder);
+ this.eventFired = false;
+ treeModel.addTreeModelListener(new TestTreeModelListener() {
+ @Override
+ public void treeNodesInserted(TreeModelEvent e) {
+ // do nothing
+ }
+ @Override
+ public void treeNodesRemoved(TreeModelEvent e) {
+ // do nothing
+ }
+ @Override
+ public void treeStructureChanged(TreeModelEvent e) {
+ TreeModelAdapterTests.this.eventFired = true;
+ }
+ });
+ nodeHolder.setValue(this.buildUnsortedRootNode());
+ assertTrue(this.eventFired);
+ }
+
+ /**
+ * test a problem we had where removing a child from a tree would cause
+ * the JTree to call #equals(Object) on each node removed (actually, it was
+ * TreePath, but that was because its own #equals(Object) was called by
+ * JTree); and since we had already removed the last listener from the
+ * aspect adapter, the aspect adapter would say its value was null; this
+ * would cause a NPE until we tweaked TreeModelAdapter to remove its
+ * listeners from a node only *after* the node had been completely
+ * removed from the JTree
+ * @see TreeModelAdapter#removeNode(Object[], int, TreeNodeValueModel)
+ * @see TreeModelAdapter#addNode(Object[], int, TreeNodeValueModel)
+ */
+ public void testLazyInitialization() {
+ TreeModel treeModel = this.buildSpecialTreeModel();
+ JTree jTree = new JTree(treeModel);
+ TestNode rootNode = (TestNode) treeModel.getRoot();
+ TestModel root = rootNode.getTestModel();
+ // this would cause a NPE:
+ root.removeChild(root.childNamed("node 3"));
+ assertEquals(treeModel, jTree.getModel());
+ }
+
+
+ private TreeModel buildSortedTreeModel() {
+ return new TreeModelAdapter(this.buildSortedRootNode());
+ }
+
+ private TestNode buildSortedRootNode() {
+ return new SortedTestNode(this.buildRoot());
+ }
+
+ private TreeModel buildUnsortedTreeModel() {
+ return new TreeModelAdapter(this.buildUnsortedRootNode());
+ }
+
+ private TestNode buildUnsortedRootNode() {
+ return new UnsortedTestNode(this.buildRoot());
+ }
+
+ private TreeModel buildSpecialTreeModel() {
+ return new TreeModelAdapter(this.buildSpecialRootNode());
+ }
+
+ private TestNode buildSpecialRootNode() {
+ return new SpecialTestNode(this.buildRoot());
+ }
+
+ private TestModel buildRoot() {
+ TestModel root = new TestModel("root");
+ /*Node node_0 = */root.addChild("node 0");
+ TestModel node_1 = root.addChild("node 1");
+ TestModel node_1_1 = node_1.addChild("node 1.1");
+ /*Node node_1_1_1 = */node_1_1.addChild("node 1.1.1");
+ TestModel node_2 = root.addChild("node 2");
+ /*Node node_2_0 = */node_2.addChild("node 2.0");
+ /*Node node_2_1 = */node_2.addChild("node 2.1");
+ /*Node node_2_2 = */node_2.addChild("node 2.2");
+ /*Node node_2_3 = */node_2.addChild("node 2.3");
+ /*Node node_2_4 = */node_2.addChild("node 2.4");
+ /*Node node_2_5 = */node_2.addChild("node 2.5");
+ /*Node node_3 = */root.addChild("node 3");
+ /*Node node_4 = */root.addChild("node 4");
+ return root;
+ }
+
+
+ // ********** member classes **********
+
+ /**
+ * This is a typical model class with the typical change notifications
+ * for #name and #children.
+ */
+ public static class TestModel extends AbstractModel {
+
+ // the parent is immutable; the root's parent is null
+ private TestModel parent;
+
+ // the name is mutable; so I guess it isn't the "primary key" :-)
+ private String name;
+ public static final String NAME_PROPERTY = "name";
+
+ private final Collection<TestModel> children;
+ public static final String CHILDREN_COLLECTION = "children";
+
+
+ public TestModel(String name) { // root ctor
+ super();
+ this.name = name;
+ this.children = new HashBag<TestModel>();
+ }
+ private TestModel(TestModel parent, String name) {
+ this(name);
+ this.parent = parent;
+ }
+
+ public TestModel getParent() {
+ return this.parent;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ Object old = this.name;
+ this.name = name;
+ this.firePropertyChanged(NAME_PROPERTY, old, name);
+ }
+
+ public Iterator<TestModel> children() {
+ return new ReadOnlyIterator<TestModel>(this.children);
+ }
+ public int childrenSize() {
+ return this.children.size();
+ }
+ public TestModel addChild(String childName) {
+ TestModel child = new TestModel(this, childName);
+ this.children.add(child);
+ this.fireItemAdded(CHILDREN_COLLECTION, child);
+ return child;
+ }
+ public TestModel[] addChildren(String[] childNames) {
+ TestModel[] newChildren = new TestModel[childNames.length];
+ for (int i = 0; i < childNames.length; i++) {
+ TestModel child = new TestModel(this, childNames[i]);
+ this.children.add(child);
+ newChildren[i] = child;
+ }
+ this.fireItemsAdded(CHILDREN_COLLECTION, Arrays.asList(newChildren));
+ return newChildren;
+ }
+ public void removeChild(TestModel child) {
+ if (this.children.remove(child)) {
+ this.fireItemRemoved(CHILDREN_COLLECTION, child);
+ }
+ }
+ public void removeChildren(TestModel[] testModels) {
+ Collection<TestModel> removedChildren = new ArrayList<TestModel>();
+ for (int i = 0; i < testModels.length; i++) {
+ if (this.children.remove(testModels[i])) {
+ removedChildren.add(testModels[i]);
+ } else {
+ throw new IllegalArgumentException(String.valueOf(testModels[i]));
+ }
+ }
+ if ( ! removedChildren.isEmpty()) {
+ this.fireItemsRemoved(CHILDREN_COLLECTION, removedChildren);
+ }
+ }
+ public void clearChildren() {
+ this.children.clear();
+ this.fireCollectionChanged(CHILDREN_COLLECTION);
+ }
+ public TestModel childNamed(String childName) {
+ for (Iterator<TestModel> stream = this.children(); stream.hasNext(); ) {
+ TestModel child = stream.next();
+ if (child.getName().equals(childName)) {
+ return child;
+ }
+ }
+ throw new RuntimeException("child not found: " + childName);
+ }
+
+ public String dumpString() {
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+ this.dumpOn(ipw);
+ return sw.toString();
+ }
+ public void dumpOn(IndentingPrintWriter writer) {
+ writer.println(this);
+ writer.indent();
+ for (Iterator<TestModel> stream = this.children(); stream.hasNext(); ) {
+ stream.next().dumpOn(writer);
+ }
+ writer.undent();
+ }
+ public void dumpOn(OutputStream stream) {
+ IndentingPrintWriter writer = new IndentingPrintWriter(new OutputStreamWriter(stream));
+ this.dumpOn(writer);
+ writer.flush();
+ }
+ public void dump() {
+ this.dumpOn(System.out);
+ }
+
+ @Override
+ public String toString() {
+ return "TestModel(" + this.name + ")";
+ }
+
+ }
+
+
+ /**
+ * This Node wraps a TestModel and converts into something that can
+ * be used by TreeModelAdapter. It converts changes to the TestModel's
+ * name into "state changes" to the Node; and converts the
+ * TestModel's children into a ListValueModel of Nodes whose order is
+ * determined by subclass implementations.
+ */
+ public static abstract class TestNode extends AbstractTreeNodeValueModel implements Displayable {
+ /** the model object wrapped by this node */
+ private TestModel testModel;
+ /** this node's parent node; null for the root node */
+ private TestNode parent;
+ /** this node's child nodes */
+ private ListValueModel childrenModel;
+ /** a listener that notifies us when the model object's "internal state" changes */
+ private PropertyChangeListener testModelListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * root node constructor
+ */
+ public TestNode(TestModel testModel) {
+ this(null, testModel);
+ }
+
+ /**
+ * branch or leaf node constructor
+ */
+ public TestNode(TestNode parent, TestModel testModel) {
+ super();
+ this.initialize(parent, testModel);
+ }
+
+
+ // ********** initialization **********
+
+ @Override
+ protected void initialize() {
+ super.initialize();
+ this.testModelListener = this.buildTestModelListener();
+ }
+
+ private PropertyChangeListener buildTestModelListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ TestNode.this.testModelChanged(e);
+ }
+ };
+ }
+
+ protected void initialize(TestNode p, TestModel tm) {
+ this.parent = p;
+ this.testModel = tm;
+ this.childrenModel = this.buildChildrenModel(tm);
+ }
+
+ /**
+ * subclasses decide the order of the child nodes
+ */
+ protected abstract ListValueModel buildChildrenModel(TestModel model);
+
+ /**
+ * used by subclasses;
+ * transform the test model children into nodes
+ */
+ protected ListValueModel buildNodeAdapter(TestModel model) {
+ return new TransformationListValueModelAdapter(this.buildChildrenAdapter(model)) {
+ @Override
+ protected Object transformItem(Object item) {
+ return TestNode.this.buildChildNode((TestModel) item);
+ }
+ };
+ }
+
+ /**
+ * subclasses must build a concrete node for the specified test model
+ */
+ protected abstract TestNode buildChildNode(TestModel childTestModel);
+
+ /**
+ * return a collection value model on the specified model's children
+ */
+ protected CollectionValueModel buildChildrenAdapter(TestModel model) {
+ return new CollectionAspectAdapter(TestModel.CHILDREN_COLLECTION, model) {
+ @Override
+ protected Iterator getValueFromSubject() {
+ return ((TestModel) this.subject).children();
+ }
+ @Override
+ protected int sizeFromSubject() {
+ return ((TestModel) this.subject).childrenSize();
+ }
+ };
+ }
+
+
+ // ********** TreeNodeValueModel implementation **********
+
+ public Object getValue() {
+ return this.testModel;
+ }
+
+ /**
+ * this will probably never be called...
+ */
+ @Override
+ public void setValue(Object value) {
+ Object old = this.testModel;
+ this.testModel = (TestModel) value;
+ this.firePropertyChanged(VALUE, old, this.testModel);
+ }
+
+ public TreeNodeValueModel getParent() {
+ return this.parent;
+ }
+
+ public ListValueModel getChildrenModel() {
+ return this.childrenModel;
+ }
+
+
+ // ********** AbstractTreeNodeValueModel implementation **********
+
+ @Override
+ protected void engageValue() {
+ this.testModel.addPropertyChangeListener(TestModel.NAME_PROPERTY, this.testModelListener);
+ }
+
+ @Override
+ protected void disengageValue() {
+ this.testModel.removePropertyChangeListener(TestModel.NAME_PROPERTY, this.testModelListener);
+ }
+
+
+ // ********** Displayable implementation **********
+
+ public String displayString() {
+ return this.testModel.getName();
+ }
+
+ public Icon icon() {
+ return null;
+ }
+
+
+ // ********** debugging support **********
+
+ public String dumpString() {
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+ this.dumpOn(ipw);
+ return sw.toString();
+ }
+
+ public void dumpOn(IndentingPrintWriter writer) {
+ writer.println(this);
+ writer.indent();
+ for (Iterator stream = (Iterator) this.childrenModel.getValue(); stream.hasNext(); ) {
+ ((TestNode) stream.next()).dumpOn(writer);
+ }
+ writer.undent();
+ }
+
+ public void dumpOn(OutputStream stream) {
+ IndentingPrintWriter writer = new IndentingPrintWriter(new OutputStreamWriter(stream));
+ this.dumpOn(writer);
+ writer.flush();
+ }
+
+ public void dump() {
+ this.dumpOn(System.out);
+ }
+
+
+ // ********** behavior **********
+
+ /**
+ * the model's name has changed, forward the event to our listeners
+ */
+ protected void testModelChanged(PropertyChangeEvent e) {
+ // we need to notify listeners that our "internal state" has changed
+ this.fireStateChanged();
+ // our display string stays in synch with the model's name
+ this.firePropertyChanged(DISPLAY_STRING_PROPERTY, e.oldValue(), e.newValue());
+ }
+
+
+ // ********** queries **********
+
+ public TestModel getTestModel() {
+ return this.testModel;
+ }
+
+ /**
+ * testing convenience method
+ */
+ public TestNode childNamed(String name) {
+ for (Iterator stream = (Iterator) this.childrenModel.getValue(); stream.hasNext(); ) {
+ TestNode childNode = (TestNode) stream.next();
+ if (childNode.getTestModel().getName().equals(name)) {
+ return childNode;
+ }
+ }
+ throw new IllegalArgumentException("child not found: " + name);
+ }
+
+
+ // ********** standard methods **********
+
+ /**
+ * use the standard Displayable comparator
+ */
+ public int compareTo(Displayable d) {
+ return DEFAULT_COMPARATOR.compare(this, d);
+ }
+
+ @Override
+ public String toString() {
+ return "Node(" + this.testModel + ")";
+ }
+
+ }
+
+ /**
+ * concrete implementation that keeps its children sorted
+ */
+ public static class SortedTestNode extends TestNode {
+
+ // ********** constructors **********
+ public SortedTestNode(TestModel testModel) {
+ super(testModel);
+ }
+ public SortedTestNode(TestNode parent, TestModel testModel) {
+ super(parent, testModel);
+ }
+
+ // ********** initialization **********
+ /** the list should be sorted */
+ @Override
+ protected ListValueModel buildChildrenModel(TestModel testModel) {
+ return new SortedListValueModelAdapter(this.buildDisplayStringAdapter(testModel));
+ }
+ /** the display string (name) of each node can change */
+ protected ListValueModel buildDisplayStringAdapter(TestModel testModel) {
+ return new ItemPropertyListValueModelAdapter(this.buildNodeAdapter(testModel), DISPLAY_STRING_PROPERTY);
+ }
+ /** children are also sorted nodes */
+ @Override
+ protected TestNode buildChildNode(TestModel childNode) {
+ return new SortedTestNode(this, childNode);
+ }
+
+ }
+
+
+ /**
+ * concrete implementation that leaves its children unsorted
+ */
+ public static class UnsortedTestNode extends TestNode {
+
+ // ********** constructors **********
+ public UnsortedTestNode(TestModel testModel) {
+ super(testModel);
+ }
+ public UnsortedTestNode(TestNode parent, TestModel testModel) {
+ super(parent, testModel);
+ }
+
+ // ********** initialization **********
+ /** the list should NOT be sorted */
+ @Override
+ protected ListValueModel buildChildrenModel(TestModel testModel) {
+ return this.buildNodeAdapter(testModel);
+ }
+ /** children are also unsorted nodes */
+ @Override
+ protected TestNode buildChildNode(TestModel childNode) {
+ return new UnsortedTestNode(this, childNode);
+ }
+
+ }
+
+
+ /**
+ * concrete implementation that leaves its children unsorted
+ * and has a special set of children for "node 3"
+ */
+ public static class SpecialTestNode extends UnsortedTestNode {
+
+ // ********** constructors **********
+ public SpecialTestNode(TestModel testModel) {
+ super(testModel);
+ }
+ public SpecialTestNode(TestNode parent, TestModel testModel) {
+ super(parent, testModel);
+ }
+
+ // ********** initialization **********
+ /** return a different list of children for "node 3" */
+ @Override
+ protected ListValueModel buildChildrenModel(TestModel testModel) {
+ if (testModel.getName().equals("node 3")) {
+ return this.buildSpecialChildrenModel(testModel);
+ }
+ return super.buildChildrenModel(testModel);
+ }
+ protected ListValueModel buildSpecialChildrenModel(TestModel testModel) {
+ Object[] children = new Object[1];
+ children[0] = new NameTestNode(this);
+ return new SimpleListValueModel(Arrays.asList(children));
+ }
+ /** children are also special nodes */
+ @Override
+ protected TestNode buildChildNode(TestModel childNode) {
+ return new SpecialTestNode(this, childNode);
+ }
+
+ }
+
+
+ public static class NameTestNode extends AbstractTreeNodeValueModel {
+ private PropertyValueModel nameAdapter;
+ private SpecialTestNode specialNode; // parent node
+ private PropertyChangeListener nameListener;
+
+ // ********** construction/initialization **********
+
+ public NameTestNode(SpecialTestNode specialNode) {
+ super();
+ this.initialize(specialNode);
+ }
+ @Override
+ protected void initialize() {
+ super.initialize();
+ this.nameListener = this.buildNameListener();
+ }
+ protected PropertyChangeListener buildNameListener() {
+ return new PropertyChangeListener() {
+ public void propertyChanged(PropertyChangeEvent e) {
+ NameTestNode.this.nameChanged(e);
+ }
+ };
+ }
+ protected void initialize(SpecialTestNode node) {
+ this.specialNode = node;
+ this.nameAdapter = this.buildNameAdapter();
+ }
+
+ protected PropertyValueModel buildNameAdapter() {
+ return new PropertyAspectAdapter(TestModel.NAME_PROPERTY, this.getTestModel()) {
+ @Override
+ protected Object getValueFromSubject() {
+ return ((TestModel) this.subject).getName();
+ }
+ @Override
+ protected void setValueOnSubject(Object value) {
+ ((TestModel) this.subject).setName((String) value);
+ }
+ };
+ }
+
+ public TestModel getTestModel() {
+ return this.specialNode.getTestModel();
+ }
+
+ // ********** TreeNodeValueModel implementation **********
+
+ public Object getValue() {
+ return this.nameAdapter.getValue();
+ }
+ @Override
+ public void setValue(Object value) {
+ this.nameAdapter.setValue(value);
+ }
+ public TreeNodeValueModel getParent() {
+ return this.specialNode;
+ }
+ public ListValueModel getChildrenModel() {
+ return NullListValueModel.instance();
+ }
+
+ // ********** AbstractTreeNodeValueModel implementation **********
+
+ @Override
+ protected void engageValue() {
+ this.nameAdapter.addPropertyChangeListener(ValueModel.VALUE, this.nameListener);
+ }
+ @Override
+ protected void disengageValue() {
+ this.nameAdapter.removePropertyChangeListener(ValueModel.VALUE, this.nameListener);
+ }
+
+ // ********** behavior **********
+
+ protected void nameChanged(PropertyChangeEvent e) {
+ // we need to notify listeners that our "value" has changed
+ this.firePropertyChanged(VALUE, e.oldValue(), e.newValue());
+ }
+ }
+
+
+
+ /**
+ * listener that will blow up with any event;
+ * override and implement expected event methods
+ */
+ public class TestTreeModelListener implements TreeModelListener {
+ public void treeNodesChanged(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ public void treeNodesInserted(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ public void treeNodesRemoved(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ public void treeStructureChanged(TreeModelEvent e) {
+ fail("unexpected event");
+ }
+ }
+
+}
diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TreeModelAdapterUITest.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TreeModelAdapterUITest.java
new file mode 100644
index 0000000..330ed6f
--- /dev/null
+++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/model/value/swing/TreeModelAdapterUITest.java
@@ -0,0 +1,426 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Contributors:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.utility.tests.internal.model.value.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.WindowConstants;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import org.eclipse.jpt.utility.internal.CollectionTools;
+import org.eclipse.jpt.utility.internal.iterators.EnumerationIterator;
+import org.eclipse.jpt.utility.internal.model.value.PropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel;
+import org.eclipse.jpt.utility.internal.model.value.swing.TreeModelAdapter;
+import org.eclipse.jpt.utility.internal.swing.Displayable;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TreeModelAdapterTests.SortedTestNode;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TreeModelAdapterTests.TestModel;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TreeModelAdapterTests.TestNode;
+import org.eclipse.jpt.utility.tests.internal.model.value.swing.TreeModelAdapterTests.UnsortedTestNode;
+
+/**
+ * an example UI for testing the TreeModelAdapter
+ */
+public class TreeModelAdapterUITest {
+
+ // hold the tree so we can restore its expansion state
+ private JTree tree;
+ private PropertyValueModel rootNodeHolder;
+ private boolean sorted;
+ private TreeModel treeModel;
+ private TreeSelectionModel treeSelectionModel;
+ private TextField nameTextField;
+
+ public static void main(String[] args) throws Exception {
+ new TreeModelAdapterUITest().exec(args);
+ }
+
+ private TreeModelAdapterUITest() {
+ super();
+ }
+
+ private void exec(String[] args) throws Exception {
+ this.rootNodeHolder = this.buildRootNodeHolder();
+ this.sorted = this.rootNodeHolder.getValue() instanceof SortedTestNode;
+ this.treeModel = this.buildTreeModel();
+ this.treeSelectionModel = this.buildTreeSelectionModel();
+ this.nameTextField = new TextField();
+ this.openWindow();
+ }
+
+ private PropertyValueModel buildRootNodeHolder() {
+ return new SimplePropertyValueModel(this.buildSortedRootNode());
+ }
+
+ private TestNode buildSortedRootNode() {
+ return new SortedTestNode(this.buildRoot());
+ }
+
+ private TestNode buildUnsortedRootNode() {
+ return new UnsortedTestNode(this.buildRoot());
+ }
+
+ private TestModel buildRoot() {
+ TestModel root = new TestModel("root");
+
+ TestModel node_1 = root.addChild("node 1");
+ /*Node node_1_1 = */node_1.addChild("node 1.1");
+
+ TestModel node_2 = root.addChild("node 2");
+ /*Node node_2_1 = */node_2.addChild("node 2.1");
+ TestModel node_2_2 = node_2.addChild("node 2.2");
+ /*Node node_2_2_1 = */node_2_2.addChild("node 2.2.1");
+ /*Node node_2_2_2 = */node_2_2.addChild("node 2.2.2");
+ /*Node node_2_3 = */node_2.addChild("node 2.3");
+ /*Node node_2_4 = */node_2.addChild("node 2.4");
+ /*Node node_2_5 = */node_2.addChild("node 2.5");
+
+ TestModel node_3 = root.addChild("node 3");
+ TestModel node_3_1 = node_3.addChild("node 3.1");
+ TestModel node_3_1_1 = node_3_1.addChild("node 3.1.1");
+ /*Node node_3_1_1_1 = */node_3_1_1.addChild("node 3.1.1.1");
+
+ /*Node node_4 = */root.addChild("node 4");
+
+ return root;
+ }
+
+ private TreeModel buildTreeModel() {
+ return new TreeModelAdapter(this.rootNodeHolder);
+ }
+
+ private TreeSelectionModel buildTreeSelectionModel() {
+ TreeSelectionModel tsm = new DefaultTreeSelectionModel();
+ tsm.addTreeSelectionListener(this.buildTreeSelectionListener());
+ tsm.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ return tsm;
+ }
+
+ private TreeSelectionListener buildTreeSelectionListener() {
+ return new TreeSelectionListener() {
+ public void valueChanged(TreeSelectionEvent e) {
+ TreeModelAdapterUITest.this.treeSelectionChanged(e);
+ }
+ };
+ }
+
+ void treeSelectionChanged(TreeSelectionEvent e) {
+ TestModel selectedTestModel = this.selectedTestModel();
+ if (selectedTestModel != null) {
+ this.nameTextField.setText(selectedTestModel.getName());
+ }
+ }
+
+ private void openWindow() {
+ JFrame window = new JFrame(this.getClass().getName());
+ window.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ window.addWindowListener(this.buildWindowListener());
+ window.getContentPane().add(this.buildMainPanel(), "Center");
+ window.setLocation(300, 300);
+ window.setSize(400, 400);
+ window.setVisible(true);
+ }
+
+ private WindowListener buildWindowListener() {
+ return new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ e.getWindow().setVisible(false);
+ System.exit(0);
+ }
+ };
+ }
+
+ private Component buildMainPanel() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.add(this.buildTreePane(), BorderLayout.CENTER);
+ mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
+ return mainPanel;
+ }
+
+ private Component buildTreePane() {
+ return new JScrollPane(this.buildTree());
+ }
+
+ private JTree buildTree() {
+ this.tree = new JTree(this.treeModel) {
+ @Override
+ public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+ return ((Displayable) value).displayString();
+ }
+ };
+ this.tree.setSelectionModel(this.treeSelectionModel);
+ this.tree.setRootVisible(true);
+ this.tree.setShowsRootHandles(true);
+ this.tree.setRowHeight(20);
+ this.tree.setDoubleBuffered(true);
+ return this.tree;
+ }
+
+ private Component buildControlPanel() {
+ JPanel controlPanel = new JPanel(new GridLayout(0, 1));
+ controlPanel.add(this.buildAddRenameNodePanel());
+ controlPanel.add(this.buildMiscPanel());
+ return controlPanel;
+ }
+
+ private Component buildAddRenameNodePanel() {
+ JPanel addRenameNodePanel = new JPanel(new BorderLayout());
+ addRenameNodePanel.add(this.buildAddButton(), BorderLayout.WEST);
+ addRenameNodePanel.add(this.nameTextField, BorderLayout.CENTER);
+ addRenameNodePanel.add(this.buildRenameButton(), BorderLayout.EAST);
+ return addRenameNodePanel;
+ }
+
+ private Component buildMiscPanel() {
+ JPanel miscPanel = new JPanel(new GridLayout(1, 0));
+ miscPanel.add(this.buildClearChildrenButton());
+ miscPanel.add(this.buildRemoveButton());
+ miscPanel.add(this.buildResetButton());
+ return miscPanel;
+ }
+
+ private String getName() {
+ return this.nameTextField.getText();
+ }
+
+ // ********** queries **********
+ private TestNode selectedNode() {
+ if (this.treeSelectionModel.isSelectionEmpty()) {
+ return null;
+ }
+ return (TestNode) this.treeSelectionModel.getSelectionPath().getLastPathComponent();
+ }
+
+ private TestModel selectedTestModel() {
+ if (this.treeSelectionModel.isSelectionEmpty()) {
+ return null;
+ }
+ return (TestModel) this.selectedNode().getValue();
+ }
+
+ private TestNode rootNode() {
+ return (TestNode) this.treeModel.getRoot();
+ }
+
+ private TestModel root() {
+ return (TestModel) this.rootNode().getValue();
+ }
+
+ private Collection expandedPaths() {
+ Enumeration stream = this.tree.getExpandedDescendants(new TreePath(this.rootNode()));
+ if (stream == null) {
+ return Collections.EMPTY_LIST;
+ }
+ return CollectionTools.list(new EnumerationIterator(stream));
+ }
+
+ // ********** behavior **********
+ private void setSelectedNode(TestNode selectedNode) {
+ this.treeSelectionModel.setSelectionPath(new TreePath(selectedNode.path()));
+ }
+
+ private void expandPaths(Collection paths) {
+ for (Iterator stream = paths.iterator(); stream.hasNext(); ) {
+ this.tree.expandPath((TreePath) stream.next());
+ }
+ }
+
+ // ********** add **********
+ private JButton buildAddButton() {
+ return new JButton(this.buildAddAction());
+ }
+
+ private Action buildAddAction() {
+ Action action = new AbstractAction("add") {
+ public void actionPerformed(ActionEvent event) {
+ TreeModelAdapterUITest.this.addNode();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ /**
+ * adding causes the tree to be sorted and nodes to be
+ * removed and re-added; so we have to fiddle with the expansion state
+ */
+ void addNode() {
+ TestModel selectedTestModel = this.selectedTestModel();
+ if (selectedTestModel != null) {
+ String name = this.getName();
+ // save the expansion state and restore it after the add
+ Collection paths = this.expandedPaths();
+
+ selectedTestModel.addChild(name);
+
+ this.expandPaths(paths);
+ this.setSelectedNode(this.selectedNode().childNamed(name));
+ }
+ }
+
+ // ********** remove **********
+ private JButton buildRemoveButton() {
+ return new JButton(this.buildRemoveAction());
+ }
+
+ private Action buildRemoveAction() {
+ Action action = new AbstractAction("remove") {
+ public void actionPerformed(ActionEvent event) {
+ TreeModelAdapterUITest.this.removeNode();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ /**
+ * we need to figure out which node to select after
+ * the selected node is deleted
+ */
+ void removeNode() {
+ TestModel selectedTestModel = this.selectedTestModel();
+ // do not allow the root to be removed
+ if ((selectedTestModel != null) && (selectedTestModel != this.root())) {
+ // save the parent and index, so we can select another, nearby, node
+ // once the selected node is removed
+ TestNode parentNode = (TestNode) this.selectedNode().getParent();
+ int childIndex = parentNode.indexOfChild(this.selectedNode());
+
+ selectedTestModel.getParent().removeChild(selectedTestModel);
+
+ int childrenSize = parentNode.childrenSize();
+ if (childIndex < childrenSize) {
+ // select the child that moved up and replaced the just-deleted child
+ this.setSelectedNode((TestNode) parentNode.getChild(childIndex));
+ } else {
+ if (childrenSize == 0) {
+ // if there are no more children, select the parent
+ this.setSelectedNode(parentNode);
+ } else {
+ // if the child at the bottom of the list was deleted, select the next child up
+ this.setSelectedNode((TestNode) parentNode.getChild(childIndex - 1));
+ }
+ }
+ }
+ }
+
+ // ********** rename **********
+ private JButton buildRenameButton() {
+ return new JButton(this.buildRenameAction());
+ }
+
+ private Action buildRenameAction() {
+ Action action = new AbstractAction("rename") {
+ public void actionPerformed(ActionEvent event) {
+ TreeModelAdapterUITest.this.renameNode();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ /**
+ * renaming causes the tree to be sorted and nodes to be
+ * removed and re-added; so we have to fiddle with the expansion state
+ */
+ void renameNode() {
+ TestModel selectedTestModel = this.selectedTestModel();
+ if (selectedTestModel != null) {
+ // save the node and re-select it after the rename
+ TestNode selectedNode = this.selectedNode();
+ // save the expansion state and restore it after the rename
+ Collection paths = this.expandedPaths();
+
+ selectedTestModel.setName(this.getName());
+
+ this.expandPaths(paths);
+ this.setSelectedNode(selectedNode);
+ }
+ }
+
+ // ********** clear children **********
+ private JButton buildClearChildrenButton() {
+ return new JButton(this.buildClearChildrenAction());
+ }
+
+ private Action buildClearChildrenAction() {
+ Action action = new AbstractAction("clear children") {
+ public void actionPerformed(ActionEvent event) {
+ TreeModelAdapterUITest.this.clearChildren();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ /**
+ * nothing special, we just want to test #fireCollectionChanged(String)
+ */
+ void clearChildren() {
+ TestModel selectedTestModel = this.selectedTestModel();
+ if (selectedTestModel != null) {
+ selectedTestModel.clearChildren();
+ }
+ }
+
+ // ********** reset **********
+ private JButton buildResetButton() {
+ return new JButton(this.buildResetAction());
+ }
+
+ private Action buildResetAction() {
+ Action action = new AbstractAction("reset") {
+ public void actionPerformed(ActionEvent event) {
+ TreeModelAdapterUITest.this.reset();
+ }
+ };
+ action.setEnabled(true);
+ return action;
+ }
+
+ /**
+ * test the adapter's root node holder;
+ * toggle between sorted and unsorted lists
+ */
+ void reset() {
+ this.sorted = ! this.sorted;
+ if (this.sorted) {
+ this.rootNodeHolder.setValue(this.buildSortedRootNode());
+ } else {
+ this.rootNodeHolder.setValue(this.buildUnsortedRootNode());
+ }
+ this.tree.expandPath(new TreePath(this.rootNode()));
+ }
+
+}