| /* |
| * 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); |
| } |
| } |
| } |