blob: 114748b8dd3cd6e1e75774493715b6a6f6795165 [file] [log] [blame]
/*
* Copyright (c) 2011, 2021 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// 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<>(list);
int index = list.size();
list.addAll(index, items);
if (hasListChangeListeners(listName)) {
IListChangeEvent<T> event = new ListChangeEvent<>(
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<>();
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<>(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<>();
this.propertyChangeListeners = new HashMap<>();
}
@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<>(items);
items.remove(oldIndex);
items.add(newIndex, item);
if (hasListChangeListeners(listName)) {
IListChangeEvent<T> event = new ListChangeEvent<>(
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<>(
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<>(items);
int index = items.indexOf(item);
items.remove(index);
if (hasListChangeListeners(listName)) {
IListChangeEvent<T> event = new ListChangeEvent<>(
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<>(list);
list.removeAll(items);
if (hasListChangeListeners(listName)) {
IListChangeEvent<T> event = new ListChangeEvent<>(
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<>(items);
items.set(index, item);
if (hasListChangeListeners(listName)) {
IListChangeEvent<T> event = new ListChangeEvent<>(
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<>(items);
items.clear();
items.addAll(newItems);
if (hasListChangeListeners(listName)) {
IListChangeEvent<T> event = new ListChangeEvent<>(
source,
original,
EventType.REPLACED,
listName,
items,
-1,
-1
);
fireListChangeEvent(event);
}
}
}