/*******************************************************************************
* 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.utility;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.persistence.tools.workbench.utility.events.ChangeSupport;
import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeEvent;
import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeListener;
import org.eclipse.persistence.tools.workbench.utility.events.ListChangeEvent;
import org.eclipse.persistence.tools.workbench.utility.events.ListChangeListener;
import org.eclipse.persistence.tools.workbench.utility.events.StateChangeEvent;
import org.eclipse.persistence.tools.workbench.utility.events.StateChangeListener;
import org.eclipse.persistence.tools.workbench.utility.events.TreeChangeEvent;
import org.eclipse.persistence.tools.workbench.utility.events.TreeChangeListener;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
/**
* Convenience implementation of Model.
*/
public abstract class AbstractModel implements Model {
/**
* Delegate state/property/collection/list/tree change support to this
* helper object. The change support object is "lazy-initialized".
*/
private ChangeSupport changeSupport;
// ********** constructors/initialization **********
/**
* Default constructor.
* This will call #initialize() on the newly-created instance.
*/
protected AbstractModel() {
super();
this.initialize();
}
protected void initialize() {
// do nothing by default
}
/**
* This accessor will build the change support when required.
*/
private ChangeSupport changeSupport() {
if (this.changeSupport == null) {
this.changeSupport = this.buildDefaultChangeSupport();
}
return this.changeSupport;
}
protected ChangeSupport buildDefaultChangeSupport() {
return new ChangeSupport(this);
}
// ********** state change support **********
/**
* @see Model#addStateChangeListener(StateChangeListener)
*/
public synchronized void addStateChangeListener(StateChangeListener listener) {
this.changeSupport().addStateChangeListener(listener);
}
/**
* @see Model#removeStateChangeListener(StateChangeListener)
*/
public synchronized void removeStateChangeListener(StateChangeListener listener) {
this.changeSupport().removeStateChangeListener(listener);
}
protected final void fireStateChanged() {
this.changeSupport().fireStateChanged();
}
protected final void fireStateChanged(StateChangeEvent evt) {
this.changeSupport().fireStateChanged(evt);
}
// ********** property change support **********
/**
* @see Model#addPropertyChangeListener(PropertyChangeListener)
*/
public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
this.changeSupport().addPropertyChangeListener(listener);
}
/**
* @see Model#addPropertyChangeListener(String, PropertyChangeListener)
*/
public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.changeSupport().addPropertyChangeListener(propertyName, listener);
}
/**
* @see Model#removePropertyChangeListener(PropertyChangeListener)
*/
public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
this.changeSupport().removePropertyChangeListener(listener);
}
/**
* @see Model#removePropertyChangeListener(String, PropertyChangeListener)
*/
public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.changeSupport().removePropertyChangeListener(propertyName, listener);
}
protected final void firePropertyChanged(String propertyName, Object oldValue, Object newValue) {
this.changeSupport().firePropertyChanged(propertyName, oldValue, newValue);
}
protected final void firePropertyChanged(String propertyName, int oldValue, int newValue) {
this.changeSupport().firePropertyChanged(propertyName, oldValue, newValue);
}
protected final void firePropertyChanged(String propertyName, boolean oldValue, boolean newValue) {
this.changeSupport().firePropertyChanged(propertyName, oldValue, newValue);
}
protected final void firePropertyChanged(String propertyName, Object newValue) {
this.changeSupport().firePropertyChanged(propertyName, null, newValue);
}
protected final void firePropertyChanged(PropertyChangeEvent evt) {
this.changeSupport().firePropertyChanged(evt);
}
// ********** collection change support **********
/**
* @see Model#addCollectionChangeListener(CollectionChangeListener)
*/
public synchronized void addCollectionChangeListener(CollectionChangeListener listener) {
this.changeSupport().addCollectionChangeListener(listener);
}
/**
* @see Model#addCollectionChangeListener(String, CollectionChangeListener)
*/
public synchronized void addCollectionChangeListener(String collectionName, CollectionChangeListener listener) {
this.changeSupport().addCollectionChangeListener(collectionName, listener);
}
/**
* @see Model#removeCollectionChangeListener(CollectionChangeListener)
*/
public synchronized void removeCollectionChangeListener(CollectionChangeListener listener) {
this.changeSupport().removeCollectionChangeListener(listener);
}
/**
* @see Model#removeCollectionChangeListener(String, CollectionChangeListener)
*/
public synchronized void removeCollectionChangeListener(String collectionName, CollectionChangeListener listener) {
this.changeSupport().removeCollectionChangeListener(collectionName, listener);
}
protected final void fireItemAdded(String collectionName, Object addedItem) {
this.changeSupport().fireItemAdded(collectionName, addedItem);
}
protected final void fireItemsAdded(String collectionName, Collection addedItems) {
this.changeSupport().fireItemsAdded(collectionName, addedItems);
}
protected final void fireItemsAdded(CollectionChangeEvent evt) {
this.changeSupport().fireItemsAdded(evt);
}
protected final void fireItemRemoved(String collectionName, Object removedItem) {
this.changeSupport().fireItemRemoved(collectionName, removedItem);
}
protected final void fireItemsRemoved(String collectionName, Collection removedItems) {
this.changeSupport().fireItemsRemoved(collectionName, removedItems);
}
protected final void fireItemsRemoved(CollectionChangeEvent evt) {
this.changeSupport().fireItemsRemoved(evt);
}
protected final void fireCollectionChanged(String collectionName) {
this.changeSupport().fireCollectionChanged(collectionName);
}
protected final void fireCollectionChanged(CollectionChangeEvent evt) {
this.changeSupport().fireCollectionChanged(evt);
}
/**
* Convenience method.
* Add the specified item to the specified bound collection
* and fire the appropriate event if necessary.
* Return whether the collection changed.
*/
protected boolean addItemToCollection(Object item, Collection collection, String collectionName) {
if (collection.add(item)) {
this.fireItemAdded(collectionName, item);
return true;
}
return false;
}
/**
* Convenience method.
* Add the specified items to the specified bound collection
* and fire the appropriate event if necessary.
* Return whether collection changed.
*/
protected boolean addItemsToCollection(Collection items, Collection collection, String collectionName) {
return this.addItemsToCollection(items.iterator(), collection, collectionName);
}
/**
* Convenience method.
* Add the specified items to the specified bound collection
* and fire the appropriate event if necessary.
* Return whether collection changed.
*/
protected boolean addItemsToCollection(Iterator items, Collection collection, String collectionName) {
Collection addedItems = null;
while (items.hasNext()) {
Object item = items.next();
if (collection.add(item)) {
if (addedItems == null) {
addedItems = new ArrayList();
}
addedItems.add(item);
}
}
if (addedItems != null) {
this.fireItemsAdded(collectionName, addedItems);
return true;
}
return false;
}
/**
* Convenience method.
* Remove the specified item from the specified bound collection
* and fire the appropriate event if necessary.
* Return whether the collection changed.
* @see java.util.Collection#remove(Object)
*/
protected boolean removeItemFromCollection(Object item, Collection collection, String collectionName) {
if (collection.remove(item)) {
this.fireItemRemoved(collectionName, item);
return true;
}
return false;
}
/**
* Convenience method.
* Remove the specified items from the specified bound collection
* and fire the appropriate event if necessary.
* Return whether the collection changed.
* @see java.util.Collection#remove(Object)
*/
protected boolean removeItemsFromCollection(Collection items, Collection collection, String collectionName) {
return this.removeItemsFromCollection(items.iterator(), collection, collectionName);
}
/**
* Convenience method.
* Remove the specified items from the specified bound collection
* and fire the appropriate event if necessary.
* Return whether the collection changed.
* @see java.util.Collection#remove(Object)
*/
protected boolean removeItemsFromCollection(Iterator items, Collection collection, String collectionName) {
Collection removedItems = CollectionTools.collection(items);
removedItems.retainAll(collection);
boolean changed = collection.removeAll(removedItems);
if ( ! removedItems.isEmpty()) {
this.fireItemsRemoved(collectionName, removedItems);
}
return changed;
}
/**
* Convenience method.
* Clear the entire collection
* and fire the appropriate event if necessary.
* Return whether the list changed.
*/
protected boolean clearCollection(Collection collection, String collectionName) {
if (collection.isEmpty()) {
return false;
}
collection.clear();
this.fireCollectionChanged(collectionName);
return true;
}
/**
* Convenience method.
* Synchronize the collection with the specified new collection,
* making a minimum number of removes and adds.
*/
protected void synchronizeCollection(Collection newCollection, Collection collection, String collectionName) {
Collection removedItems = new HashBag(collection);
removedItems.removeAll(newCollection);
this.removeItemsFromCollection(removedItems, collection, collectionName);
Collection addedItems = new HashBag(newCollection);
addedItems.removeAll(collection);
this.addItemsToCollection(addedItems, collection, collectionName);
}
/**
* Convenience method.
* Synchronize the collection with the specified new collection,
* making a minimum number of removes and adds.
*/
protected void synchronizeCollection(Iterator newItems, Collection collection, String collectionName) {
this.synchronizeCollection(CollectionTools.collection(newItems), collection, collectionName);
}
// ********** list change support **********
/**
* @see Model#addListChangeListener(ListChangeListener)
*/
public synchronized void addListChangeListener(ListChangeListener listener) {
this.changeSupport().addListChangeListener(listener);
}
/**
* @see Model#addListChangeListener(String, ListChangeListener)
*/
public synchronized void addListChangeListener(String listName, ListChangeListener listener) {
this.changeSupport().addListChangeListener(listName, listener);
}
/**
* @see Model#removeListChangeListener(ListChangeListener)
*/
public synchronized void removeListChangeListener(ListChangeListener listener) {
this.changeSupport().removeListChangeListener(listener);
}
/**
* @see Model#removeListChangeListener(String, ListChangeListener)
*/
public synchronized void removeListChangeListener(String listName, ListChangeListener listener) {
this.changeSupport().removeListChangeListener(listName, listener);
}
protected final void fireItemAdded(String listName, int index, Object addedItem) {
this.changeSupport().fireItemAdded(listName, index, addedItem);
}
protected final void fireItemsAdded(String listName, int index, List addedItems) {
this.changeSupport().fireItemsAdded(listName, index, addedItems);
}
protected final void fireItemsAdded(ListChangeEvent evt) {
this.changeSupport().fireItemsAdded(evt);
}
protected final void fireItemRemoved(String listName, int index, Object removedItem) {
this.changeSupport().fireItemRemoved(listName, index, removedItem);
}
protected final void fireItemsRemoved(String listName, int index, List removedItems) {
this.changeSupport().fireItemsRemoved(listName, index, removedItems);
}
protected final void fireItemsRemoved(ListChangeEvent evt) {
this.changeSupport().fireItemsRemoved(evt);
}
protected final void fireItemReplaced(String listName, int index, Object newItem, Object replacedItem) {
this.changeSupport().fireItemReplaced(listName, index, newItem, replacedItem);
}
protected final void fireItemsReplaced(String listName, int index, List newItems, List replacedItems) {
this.changeSupport().fireItemsReplaced(listName, index, newItems, replacedItems);
}
protected final void fireItemsReplaced(ListChangeEvent evt) {
this.changeSupport().fireItemsReplaced(evt);
}
protected final void fireListChanged(String listName) {
this.changeSupport().fireListChanged(listName);
}
protected final void fireListChanged(ListChangeEvent evt) {
this.changeSupport().fireListChanged(evt);
}
/**
* Convenience method.
* Add the specified item to the specified bound list
* and fire the appropriate event if necessary.
*/
protected void addItemToList(int index, Object item, List list, String listName) {
list.add(index, item);
this.fireItemAdded(listName, index, item);
}
/**
* Convenience method.
* Add the specified item to the end of the specified bound list
* and fire the appropriate event if necessary.
*/
protected void addItemToList(Object item, List list, String listName) {
this.addItemToList(list.size(), item, list, listName);
}
/**
* Convenience method.
* Add the specified items to the specified bound list
* and fire the appropriate event if necessary.
*/
protected void addItemsToList(int index, List items, List list, String listName) {
list.addAll(index, items);
this.fireItemsAdded(listName, index, items);
}
/**
* Convenience method.
* Add the specified items to the end of to the specified bound list
* and fire the appropriate event if necessary.
*/
protected void addItemsToList(List items, List list, String listName) {
this.addItemsToList(list.size(), items, list, listName);
}
/**
* Convenience method.
* Remove the specified item from the specified bound list
* and fire the appropriate event if necessary.
* Return the removed item.
*/
protected Object removeItemFromList(int index, List list, String listName) {
Object item = list.remove(index);
this.fireItemRemoved(listName, index, item);
return item;
}
/**
* Convenience method.
* Remove the specified item from the specified bound list
* and fire the appropriate event if necessary.
* Return the removed item.
*/
protected Object removeItemFromList(Object item, List list, String listName) {
return this.removeItemFromList(list.indexOf(item), list, listName);
}
/**
* Convenience method.
* Remove the specified items from the specified bound list
* and fire the appropriate event if necessary.
* Return the removed items.
*/
protected List removeItemsFromList(int index, int length, List list, String listName) {
List subList = list.subList(index, index + length);
List removedItems = new ArrayList(subList);
subList.clear();
this.fireItemsRemoved(listName, index, removedItems);
return removedItems;
}
/**
* Convenience method.
* Set the specified item in the specified bound list
* and fire the appropriate event if necessary.
* Return the replaced item.
*/
protected Object setItemInList(int index, Object item, List list, String listName) {
Object replacedItem = list.set(index, item);
this.fireItemReplaced(listName, index, item, replacedItem);
return replacedItem;
}
/**
* Convenience method.
* Replace the specified item in the specified bound list
* and fire the appropriate event if necessary.
* Return the replaced item.
*/
protected Object replaceItemInList(Object oldItem, Object newItem, List list, String listName) {
return this.setItemInList(list.indexOf(oldItem), newItem, list, listName);
}
/**
* Convenience method.
* Set the specified items in the specified bound list
* and fire the appropriate event if necessary.
* Return the replaced items.
*/
protected List setItemsInList(int index, List items, List list, String listName) {
List subList = list.subList(index, index + items.size());
List replacedItems = new ArrayList(subList);
for (int i = 0; i < items.size(); i++) {
subList.set(i, items.get(i));
}
this.fireItemsReplaced(listName, index, items, replacedItems);
return replacedItems;
}
/**
* Convenience method.
* Clear the entire list
* and fire the appropriate event if necessary.
* Return whether the list changed.
*/
protected boolean clearList(List list, String listName) {
if (list.isEmpty()) {
return false;
}
list.clear();
this.fireListChanged(listName);
return true;
}
// ********** tree change support **********
/**
* @see Model#addTreeChangeListener(TreeChangeListener)
*/
public synchronized void addTreeChangeListener(TreeChangeListener listener) {
this.changeSupport().addTreeChangeListener(listener);
}
/**
* @see Model#addTreeChangeListener(String, TreeChangeListener)
*/
public synchronized void addTreeChangeListener(String treeName, TreeChangeListener listener) {
this.changeSupport().addTreeChangeListener(treeName, listener);
}
/**
* @see Model#removeTreeChangeListener(TreeChangeListener)
*/
public synchronized void removeTreeChangeListener(TreeChangeListener listener) {
this.changeSupport().removeTreeChangeListener(listener);
}
/**
* @see Model#removeTreeChangeListener(String, TreeChangeListener)
*/
public synchronized void removeTreeChangeListener(String treeName, TreeChangeListener listener) {
this.changeSupport().removeTreeChangeListener(treeName, listener);
}
protected final void fireNodeAdded(String treeName, Object[] path) {
this.changeSupport().fireNodeAdded(treeName, path);
}
protected final void fireNodeAdded(TreeChangeEvent evt) {
this.changeSupport().fireNodeAdded(evt);
}
protected final void fireNodeRemoved(String treeName, Object[] path) {
this.changeSupport().fireNodeRemoved(treeName, path);
}
protected final void fireNodeRemoved(TreeChangeEvent evt) {
this.changeSupport().fireNodeRemoved(evt);
}
protected final void fireTreeStructureChanged(String treeName) {
this.changeSupport().fireTreeChanged(treeName);
}
protected final void fireTreeStructureChanged(String treeName, Object[] path) {
this.changeSupport().fireTreeChanged(treeName, path);
}
protected final void fireTreeStructureChanged(TreeChangeEvent evt) {
this.changeSupport().fireTreeChanged(evt);
}
// ********** queries **********
/**
* Return whether there are any state change listeners.
*/
public boolean hasAnyStateChangeListeners() {
return this.changeSupport().hasAnyStateChangeListeners();
}
/**
* Return whether there are no state change listeners.
*/
public boolean hasNoStateChangeListeners() {
return ! this.hasAnyStateChangeListeners();
}
/**
* Return whether there are any property change listeners for a specific property.
*/
public boolean hasAnyPropertyChangeListeners(String propertyName) {
return this.changeSupport().hasAnyPropertyChangeListeners(propertyName);
}
/**
* Return whether there are any property change listeners for a specific property.
*/
public boolean hasNoPropertyChangeListeners(String propertyName) {
return ! this.hasAnyPropertyChangeListeners(propertyName);
}
/**
* Return whether there are any collection change listeners for a specific collection.
*/
public boolean hasAnyCollectionChangeListeners(String collectionName) {
return this.changeSupport().hasAnyCollectionChangeListeners(collectionName);
}
/**
* Return whether there are any collection change listeners for a specific collection.
*/
public boolean hasNoCollectionChangeListeners(String collectionName) {
return ! this.hasAnyCollectionChangeListeners(collectionName);
}
/**
* Return whether there are any list change listeners for a specific list.
*/
public boolean hasAnyListChangeListeners(String listName) {
return this.changeSupport().hasAnyListChangeListeners(listName);
}
/**
* Return whether there are any list change listeners for a specific list.
*/
public boolean hasNoListChangeListeners(String listName) {
return ! this.hasAnyListChangeListeners(listName);
}
/**
* Return whether there are any tree change listeners for a specific tree.
*/
public boolean hasAnyTreeChangeListeners(String treeName) {
return this.changeSupport().hasAnyTreeChangeListeners(treeName);
}
/**
* Return whether there are any tree change listeners for a specific tree.
*/
public boolean hasNoTreeChangeListeners(String treeName) {
return ! this.hasAnyTreeChangeListeners(treeName);
}
// ********** convenience methods **********
/**
* Return whether the values are equal, with the appropriate null checks.
* Convenience method for checking whether an attribute value has changed.
*
* DO NOT use this to determine whether to fire a change notification,
* ChangeSupport already does that.
*/
protected final boolean valuesAreEqual(Object value1, Object value2) {
return this.changeSupport().valuesAreEqual(value1, value2);
}
protected final boolean attributeValueHasNotChanged(Object oldValue, Object newValue) {
return this.valuesAreEqual(oldValue, newValue);
}
/**
* Return whether the values are different, with the appropriate null checks.
* Convenience method for checking whether an attribute value has changed.
*
* DO NOT use this to determine whether to fire a change notification,
* ChangeSupport already does that.
*
* For example, after firing the change notification, you can use this method
* to decide if some other, related, piece of state needs to be synchronized
* with the state that just changed.
*/
protected final boolean valuesAreDifferent(Object value1, Object value2) {
return this.changeSupport().valuesAreDifferent(value1, value2);
}
protected final boolean attributeValueHasChanged(Object oldValue, Object newValue) {
return this.valuesAreDifferent(oldValue, newValue);
}
// ********** standard methods **********
/**
* @see java.lang.Object#clone()
* Although cloning models is usually not a Good Idea,
* we should at least support it properly.
*/
protected Object clone() throws CloneNotSupportedException {
AbstractModel clone = (AbstractModel) super.clone();
clone.postClone();
return clone;
}
/**
* Perform any post-clone processing necessary to
* successfully disconnect the clone from the original.
* When this method is called on the clone, the clone
* is a "shallow" copy of the original (i.e. the clone
* shares all its instance variables with the original).
*/
protected void postClone() {
// clear out change support - models do not share listeners
this.changeSupport = null;
// when you override this method, don't forget to include:
// super.postClone();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer sb = new StringBuffer();
StringTools.buildSimpleToStringOn(this, sb);
sb.append(" (");
this.toString(sb);
sb.append(')');
return sb.toString();
}
/**
* make this public so one model can call a nested model's
* #toString(StringBuffer)
*/
public void toString(StringBuffer sb) {
// subclasses should override this to do something a bit more helpful
}
}