/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.uitools.swing; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.ListIterator; import javax.swing.AbstractListModel; import javax.swing.event.ListDataListener; import org.eclipse.persistence.tools.workbench.uitools.app.NullPropertyValueModel; import org.eclipse.persistence.tools.workbench.uitools.app.PropertyValueModel; import org.eclipse.persistence.tools.workbench.uitools.app.ValueModel; import org.eclipse.persistence.tools.workbench.utility.CollectionTools; import org.eclipse.persistence.tools.workbench.utility.iterators.NullListIterator; /** * A basic implementation of ComboBoxModel that does not maintain * its list of selectable elements. Instead it retrieves them * each time they are asked for. * * To use * - override both #getSize() and #getElementAt(int) * or * - override #listValue() * (#getSize() or #getElementAt(int) may then also be overridden) * or * - override #listValueFromSubject(Object) * If this is used, a listSubjectHolder must also be used * (#getSize() or #getElementAt(int) may then also be overridden) */ public abstract class IndirectComboBoxModel extends AbstractListModel implements CachingComboBoxModel { /** * This holds the selected item. */ private PropertyValueModel selectedItemHolder; /** * This listens for when the selected item changes and * notifies all list data listeners, such as JComboBoxes. */ private PropertyChangeListener selectedItemListener; /** * This holds the list "subject". The "subject" supplies * us with the list of items for the combo box. */ private ValueModel listSubjectHolder; /** * This listens for when the list "subject" changes and * updates all list data listeners, such as JComboBoxes. */ private PropertyChangeListener listSubjectListener; /** * Cache the list size so that we may determine if * the list has changed. */ private int cachedSize; /** * Cache the list so it is not built unecessarily. Users * of IndirectComboBoxModel determine when to cache and * uncache using the methods cacheList() and uncacheList() */ private List cachedList; // ********** constructors/initialization ********** /** * Construct an indirect combo box model for the specified * selected item. The list will be empty. */ public IndirectComboBoxModel(PropertyValueModel selectedItemHolder) { this(selectedItemHolder, NullPropertyValueModel.instance()); } /** * Construct an indirect combo box model for the specified * selected item and list. */ public IndirectComboBoxModel(PropertyValueModel selectedItemHolder, ValueModel listSubjectHolder) { super(); if (selectedItemHolder == null || listSubjectHolder == null) { throw new NullPointerException(); } this.selectedItemHolder = selectedItemHolder; this.selectedItemListener = this.buildSelectedItemListener(); this.listSubjectHolder = listSubjectHolder; this.listSubjectListener = this.buildListSubjectListener(); } protected PropertyChangeListener buildSelectedItemListener() { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { IndirectComboBoxModel.this.fireSelectedItemChanged(); } public String toString() { return "selected item listener"; } }; } protected PropertyChangeListener buildListSubjectListener() { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { IndirectComboBoxModel.this.fireContentsChanged(); } public String toString() { return "list subject listener"; } }; } // ********** ComboBoxModel implementation ********** /** * @see javax.swing.ComboBoxModel#setSelectedItem(Object) */ public void setSelectedItem(Object newSelectedItem) { this.selectedItemHolder.setValue(newSelectedItem); } /** * @see javax.swing.ComboBoxModel#getSelectedItem() */ public Object getSelectedItem() { return this.selectedItemHolder.getValue(); } // ********** ListModel implementation ********** /** * @see javax.swing.ListModel#getSize() */ public int getSize() { int size = this.listSubjectHolder.getValue() == null ? 0 : listSizeFromSubject(this.listSubjectHolder.getValue()); // if the size has changed, notify listeners if (this.cachedSize != size) { this.cachedSize = size; this.fireContentsChanged(); } return size; } /** * Return the size of the subject's collection aspect. * At this point we can be sure that the subject is not null. * @see #size() */ protected int listSizeFromSubject(Object listSubject) { return CollectionTools.size(this.listValue()); } /** * @see javax.swing.ListModel#getElementAt(int) */ public Object getElementAt(int index) { return CollectionTools.get(this.listValue(), index); } /** * Extend to engage the underlying models. * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener) */ public void addListDataListener(ListDataListener l) { if (this.hasNoListDataListeners()) { this.engageModel(); } super.addListDataListener(l); } /** * Extend to dis-engage the underlying models. * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener) */ public void removeListDataListener(ListDataListener l) { super.removeListDataListener(l); if (this.hasNoListDataListeners()) { this.disengageModel(); } } // ********** internal behavior ********** /** * Notify listeners that the selection has changed. */ protected void fireSelectedItemChanged() { // this is what DefaultComboBoxModel does, so we'll do it, too this.fireContentsChanged(this, -1, -1); } /** * Notify listeners that the entire list contents have changed. */ protected void fireContentsChanged() { // Using the cached size so that we don't necessarily calculate the new size just yet this.fireContentsChanged(this, 0, this.cachedSize - 1); } /** * Return the items in the list. The "subject" may be null. */ protected ListIterator listValue() { if (this.cachedList != null) { return this.cachedList.listIterator(); } Object listSubject = this.listSubjectHolder.getValue(); if (listSubject == null) { return NullListIterator.instance(); } return this.listValueFromSubject(listSubject); } /** * Return the items in the list. The specified "subject" will not be null. * Typically this method will be overridden by subclasses to * return a list derived from the specified "subject". */ protected ListIterator listValueFromSubject(Object subject) { return NullListIterator.instance(); } /** * Return whether the model has no listeners. */ protected boolean hasNoListDataListeners() { return this.getListDataListeners().length == 0; } /** * Return whether the model has any listeners. */ protected boolean hasListDataListeners() { return ! this.hasNoListDataListeners(); } /** * Start listening to the selected item and "subject" holders. */ protected void engageModel() { this.selectedItemHolder.addPropertyChangeListener(ValueModel.VALUE, this.selectedItemListener); this.listSubjectHolder.addPropertyChangeListener(ValueModel.VALUE, this.listSubjectListener); } /** * Stop listening to the selected item and "subject" holders. */ protected void disengageModel() { this.listSubjectHolder.removePropertyChangeListener(ValueModel.VALUE, this.listSubjectListener); this.selectedItemHolder.removePropertyChangeListener(ValueModel.VALUE, this.selectedItemListener); } /** * @see CachingComboBoxModel#cacheList() */ public void cacheList() { if (isCached()) { throw new IllegalArgumentException("List is already Cached, see isCached()"); } this.cachedList = CollectionTools.list(listValue()); } /** * @see CachingComboBoxModel#uncacheList() */ public void uncacheList() { if (isCached()) { this.cachedList = null; } else { throw new IllegalArgumentException("List is not cached"); } } /** * @see CachingComboBoxModel#isCached() */ public boolean isCached() { return this.cachedList != null; } }