/******************************************************************************* * Copyright (c) 2011, 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 * ******************************************************************************/ package org.eclipse.persistence.jpa.jpql.tools.model.query; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.persistence.jpa.jpql.Assert; import org.eclipse.persistence.jpa.jpql.ExpressionTools; import org.eclipse.persistence.jpa.jpql.tools.model.IListChangeEvent; import org.eclipse.persistence.jpa.jpql.tools.model.IListChangeEvent.EventType; import org.eclipse.persistence.jpa.jpql.tools.model.IListChangeListener; import org.eclipse.persistence.jpa.jpql.tools.model.IPropertyChangeListener; import org.eclipse.persistence.jpa.jpql.tools.model.ListChangeEvent; import org.eclipse.persistence.jpa.jpql.tools.model.PropertyChangeEvent; import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable; import org.eclipse.persistence.jpa.jpql.utility.iterable.SnapshotCloneListIterable; /** * This <code>ChangeSupport</code> is responsible to notifies registered listeners upon changes made * to a {@link StateObject}, those changes are either a property has changed ({@link IPropertyChangeListener}) * or the content of a list has changed ({@link IListChangeListener}). * * @version 2.4 * @since 2.4 * @author Pascal Filion */ @SuppressWarnings("nls") public class ChangeSupport { /** * The list of registered {@link IListChangeListener listeners}. */ private Map<String, List<IListChangeListener<?>>> listChangeListeners; /** * The list of registered {@link IPropertyChangeListener listeners}. */ private Map<String, List<IPropertyChangeListener<?>>> propertyChangeListeners; /** * The object for which this object will take care of notifying the listeners upon changes made * to the object's internal state. */ private StateObject source; /** * Creates a new <code>ChangeSupport</code>. * * @param source The object for which this object will take care of notifying the listeners upon * changes made to the object's internal state * @exception NullPointerException The source {@link StateObject} cannot be <code>null</code> */ public ChangeSupport(StateObject source) { super(); initialize(source); } /** * Adds the given item as a child to the given list. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param items The list of to which the child is added * @param listName The name associated with the list * @param item The child to become a child of this one * @param <T> The type of the items */ public <T> void addItem(ListHolderStateObject<T> source, List<T> items, String listName, T item) { addItems(source, items, listName, Collections.singletonList(item)); } /** * Adds the given items as children to the given list. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param list The list of items to which the child is added * @param listName The name associated with the list * @param items The child to become children of this one * @param <T> The type of the items */ public <T> void addItems(ListHolderStateObject<T> source, List<T> list, String listName, List<? extends T> items) { List<T> original = new ArrayList<T>(list); int index = list.size(); list.addAll(index, items); if (hasListChangeListeners(listName)) { IListChangeEvent<T> event = new ListChangeEvent<T>( source, original, EventType.ADDED, listName, new ArrayList<T>(items), index, index ); fireListChangeEvent(event); } } /** * Registers the given {@link IListChangeListener} for the specified list. The listener will be * notified only when items are added, removed, moved from the list. * * @param listName The name of the list for which the listener will be notified when the content * of the list has changed * @param listener The listener to be notified upon changes * @exception NullPointerException {@link IListChangeListener} cannot be <code>null</code> * @exception IllegalArgumentException The listener is already registered with the list name */ public void addListChangeListener(String listName, IListChangeListener<?> listener) { addListener(listChangeListeners, IListChangeListener.class, listName, listener); } /** * Registers the given list for the specified name. The listener will be notified upon changes. * * @param listeners The list of listeners from which the given listener is added * @param listenerType The type of the listener, which is only used in the exception's message * @param name The name of the event for which the listener is registered * @param listener The listener to register * @exception NullPointerException {@link IPropertyChangeListener} cannot be <code>null</code> * @exception IllegalArgumentException The listener is already registered with the given name */ protected <T> void addListener(Map<String, List<T>> listeners, Class<?> listenerType, String name, T listener) { if (listener == null) { throw new NullPointerException(listenerType.getSimpleName() + " cannot be null"); } List<T> listenerList = listeners.get(name); if (listenerList == null) { listenerList = new ArrayList<T>(); listeners.put(name, listenerList); } if (listenerList.contains(listener)) { throw new IllegalArgumentException(listenerType.getSimpleName() + " is already registered"); } listenerList.add(listener); } /** * Registers the given {@link IPropertyChangeListener} for the specified property. The listener * will be notified only for changes to the specified property. * * @param propertyName The name of the property for which the listener was registered * @param listener The listener to be notified upon changes * @exception NullPointerException {@link IPropertyChangeListener} cannot be <code>null</code> * @exception IllegalArgumentException The listener is already registered with the property name */ public void addPropertyChangeListener(String propertyName, IPropertyChangeListener<?> listener) { addListener(propertyChangeListeners, IPropertyChangeListener.class, propertyName, listener); } /** * Determines whether the given item can be moved down by one position in the list owned by its * parent. * * @param list The list used to determine if the given item can be moved down in that list * @param stateObject The item that could potentially be moved down * @return <code>true</code> if the object can be moved down by one unit; <code>false</code> * otherwise */ public <T> boolean canMoveDown(List<T> list, T stateObject) { int index = list.indexOf(stateObject); return (index > -1) && (index + 1 < list.size()); } /** * Determines whether the given item can be moved up by one position in the list owned by its * parent. * * @param list The list used to determine if the given item can be moved up in that list * @param item The item that could potentially be moved up * @return <code>true</code> if the object can be moved up by one unit; <code>false</code> * otherwise */ public <T> boolean canMoveUp(List<T> list, T item) { int index = list.indexOf(item); return (index > 0); } protected <T> void fireListChangeEvent(IListChangeEvent<T> event) { for (IListChangeListener<T> listener : this.<T>listChangeListeners(event.getListName())) { try { listener.itemsRemoved(event); } catch (Exception e) { // TODO: Log event } } } /** * Notifies the {@link IPropertyChangeListener IPropertyChangeListeners} that have been registered * with the given property name that the property has changed. * * @param propertyName The name of the property associated with the property change * @param oldValue The old value of the property that changed * @param newValue The new value of the property that changed */ @SuppressWarnings("unchecked") public void firePropertyChanged(String propertyName, Object oldValue, Object newValue) { if (ExpressionTools.valuesAreDifferent(oldValue, newValue) && hasPropertyChangeListeners(propertyName)) { PropertyChangeEvent<Object> event = new PropertyChangeEvent<Object>(source, propertyName, oldValue, newValue); for (IPropertyChangeListener<?> listener : propertyChangeListeners(propertyName)) { try { ((IPropertyChangeListener<Object>) listener).propertyChanged(event); } catch (Exception e) { // TODO: Log event } } } } /** * Determines whether there are at least one {@link IListChangeListener} registered to listen for * changes made to the list with the given list name. * * @param listName The name of the list to check if it has registered listeners * @return <code>true</code> if listeners have been registered for the given list name; * <code>false</code> otherwise */ public boolean hasListChangeListeners(String listName) { return hasListeners(listChangeListeners, listName); } /** * Determines whether there are at least one listener registered. * * @param name The name of the property or list to check if it has registered listeners * @return <code>true</code> if listeners have been registered for the given name; * <code>false</code> otherwise */ protected boolean hasListeners(Map<String, ?> listeners, String name) { return listeners.containsKey(name); } /** * Determines whether there are at least one {@link IPropertyChangeListener} registered to listen * for changes made to the property with the given property name. * * @param propertyName The name of the property to check if it has registered listeners * @return <code>true</code> if listeners have been registered for the given property name; * <code>false</code> otherwise */ public boolean hasPropertyChangeListeners(String propertyName) { return hasListeners(propertyChangeListeners, propertyName); } /** * Initializes this <code>ChangeSupport</code>. * * @param source The object for which this object will take care of notifying the listeners upon * changes made to the object's internal state * @exception NullPointerException The source {@link StateObject} cannot be <code>null</code> */ protected void initialize(StateObject source) { Assert.isNotNull(source, "The source StateObject cannot be null"); this.source = source; this.listChangeListeners = new HashMap<String, List<IListChangeListener<?>>>(); this.propertyChangeListeners = new HashMap<String, List<IPropertyChangeListener<?>>>(); } @SuppressWarnings({"unchecked", "rawtypes"}) protected <T> ListIterable<IListChangeListener<T>> listChangeListeners(String listName) { return new SnapshotCloneListIterable(listChangeListeners.get(listName)); } /** * Moves the given {@link StateObject} down by one position in the list owned by its parent. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param items The list of items to which the child is moved down * @param listName The name associated with the list * @param item The child to move down within the list * @param <T> The type of the items */ public <T> void moveDown(ListHolderStateObject<T> source, List<T> items, String listName, T item) { int index = items.indexOf(item); moveItem(source, items, EventType.MOVED_DOWN, listName, item, index + 1, index); } /** * Moves the given item from its current position to a new position in the list owned by its parent. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param items The list of items to which the child is moved * @param eventType The type describing how the item was moved (up or down) * @param listName The name associated with the list * @param item The child to move within the list * @param oldIndex The current position of the item to move * @param newIndex The new position within the list * @param <T> The type of the items */ protected <T> void moveItem(ListHolderStateObject<T> source, List<T> items, EventType eventType, String listName, T item, int oldIndex, int newIndex) { List<T> original = new ArrayList<T>(items); items.remove(oldIndex); items.add(newIndex, item); if (hasListChangeListeners(listName)) { IListChangeEvent<T> event = new ListChangeEvent<T>( source, original, eventType, listName, Collections.singletonList(item), newIndex, oldIndex ); fireListChangeEvent(event); } } /** * Moves the given item up by one position in the list owned by its parent. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param items The list of items to which the child is moved up * @param listName The name associated with the list * @param item The child to move up within the list * @param <T> The type of the items */ public <T> void moveUp(ListHolderStateObject<T> source, List<T> items, String listName, T item) { int index = items.indexOf(item); moveItem(source, items, EventType.MOVED_DOWN, listName, item, index - 1, index); } protected ListIterable<IPropertyChangeListener<?>> propertyChangeListeners(String propertyName) { return new SnapshotCloneListIterable<IPropertyChangeListener<?>>( propertyChangeListeners.get(propertyName) ); } /** * Removes the given item from the list of children. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param items The list of item to which the child is removed * @param listName The name associated with the list * @param item The child to removed from the list * @param <T> The type of the items */ public <T> void removeItem(ListHolderStateObject<T> source, List<T> items, String listName, T item) { List<T> original = new ArrayList<T>(items); int index = items.indexOf(item); items.remove(index); if (hasListChangeListeners(listName)) { IListChangeEvent<T> event = new ListChangeEvent<T>( source, original, EventType.REMOVED, listName, Collections.singletonList(item), index, index ); fireListChangeEvent(event); } } /** * Removes the given items from the list of children. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param list The list of items to which the child is removed * @param listName The name associated with the list * @param items The items to removed from the list * @param <T> The type of the items */ public <T> void removeItems(ListHolderStateObject<T> source, List<? extends T> list, String listName, Collection<? extends T> items) { List<T> original = new ArrayList<T>(list); list.removeAll(items); if (hasListChangeListeners(listName)) { IListChangeEvent<T> event = new ListChangeEvent<T>( source, original, EventType.REMOVED, listName, new ArrayList<T>(items), -1, -1 ); fireListChangeEvent(event); } } /** * Unregisters the given {@link IListChangeListener} that was registered for the specified list. * The listener will no longer be notified only when items are added, removed, moved from the * list. * * @param listName The name of the list for which the listener was registered * @param listener The listener to unregister * @exception NullPointerException {@link IListChangeListener} cannot be <code>null</code> * @exception IllegalArgumentException The listener was never registered with the list name */ public void removeListChangeListener(String listName, IListChangeListener<?> listener) { removeListener(listChangeListeners, IListChangeListener.class, listName, listener); } /** * Unregisters the given listener that was registered for the specified name. The listener will * no longer be notified upon changes. * * @param listeners The list of listeners from which the given listener is removed * @param listenerType The type of the listener, which is only used in the exception's message * @param name The name of the event for which the listener was registered * @param listener The listener to unregister * @exception NullPointerException {@link IPropertyChangeListener} cannot be <code>null</code> * @exception IllegalArgumentException The listener was never registered with the given name */ protected <T> void removeListener(Map<String, List<T>> listeners, Class<?> listenerType, String name, T listener) { if (listener == null) { throw new NullPointerException(listenerType.getSimpleName() + " cannot be null"); } List<T> listenerList = listeners.get(name); if (listenerList == null) { throw new IllegalArgumentException("No listeners were registered for " + name); } if (!listenerList.remove(listener)) { throw new IllegalArgumentException(listenerType.getSimpleName() + " was never registered"); } if (listeners.isEmpty()) { listeners.remove(name); } } /** * Unregisters the given {@link IPropertyChangeListener} that was registered for the specified * property. The listener will no longer be notified when the property changes. * * @param propertyName The name of the property for which the listener was registered * @param listener The listener to unregister * @exception NullPointerException {@link IPropertyChangeListener} cannot be <code>null</code> * @exception IllegalArgumentException The listener was never registered with the property name */ public void removePropertyChangeListener(String propertyName, IPropertyChangeListener<?> listener) { removeListener(propertyChangeListeners, IPropertyChangeListener.class, propertyName, listener); } /** * Replaces the item at the given position by a new one. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param items The list of items to which a child is replaced * @param listName The name associated with the list * @param index The position of the item to replace * @param item The item to replace the one at the given position * @param <T> The type of the items */ public <T> void replaceItem(ListHolderStateObject<T> source, List<T> items, String listName, int index, T item) { List<T> original = new ArrayList<T>(items); items.set(index, item); if (hasListChangeListeners(listName)) { IListChangeEvent<T> event = new ListChangeEvent<T>( source, original, EventType.REPLACED, listName, items, index, index ); fireListChangeEvent(event); } } /** * Replaces the given list by removing any existing items and adding the items contained in the * second list. * * @param source The {@link ListHolderStateObject} from where the change is coming * @param items The list of items to which the child is removed * @param listName The name associated with the list * @param newItems The items to removed from the list * @param <T> The type of the items */ public <T> void replaceItems(ListHolderStateObject<T> source, List<T> items, String listName, List<T> newItems) { List<T> original = new ArrayList<T>(items); items.clear(); items.addAll(newItems); if (hasListChangeListeners(listName)) { IListChangeEvent<T> event = new ListChangeEvent<T>( source, original, EventType.REPLACED, listName, items, -1, -1 ); fireListChangeEvent(event); } } }