/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.dlect.events.collections;
import com.google.common.collect.Maps;
import java.util.ConcurrentModificationException;
import java.util.Map;
import javax.annotation.Nonnull;
import org.dlect.events.Event;
import org.dlect.events.EventAdapter;
import org.dlect.events.EventID;
import org.dlect.events.ListEvent;
/**
* A helper object that handles the actual event firing for collections. This object also prevents event handlers from
* modifying the same collection. Thus hopefully mitigating the risk of concurrent modification exceptions at a later
* time.
*
* @param <T> The type of object that this class accepts.
*/
public class CollectionEventHelper<T> {
private final EventAdapter adapter;
private final EventID eventID;
private final Object source;
private final transient Map<Thread, Event> firingEvents = Maps.newHashMap();
/**
* Creates a new helper using the given eventID, source and adapter. All events fired through this helper will be
* fired to the given event adapter and will be initialised with the given event ID and source.
*
* @param eventID The event ID of the events that this object will fire.
* @param source The source of the events the this object will fire
* @param adapter The adapter to fire the events through.
*
* @throws IllegalArgumentException If any argument is null.
*/
public CollectionEventHelper(@Nonnull Object source, @Nonnull EventID eventID, @Nonnull EventAdapter adapter) {
if (eventID == null) {
throw new IllegalArgumentException("EventID is null");
}
if (source == null) {
throw new IllegalArgumentException("Source is null");
}
if (adapter == null) {
throw new IllegalArgumentException("Event Adapter is null");
}
this.eventID = eventID;
this.source = source;
this.adapter = adapter;
}
protected void beginChange(Event toFire) {
Thread t = Thread.currentThread();
synchronized (firingEvents) {
if (isLocked(t)) {
throw new ConcurrentModificationException("Attempting to modify a collection from within an event handler is prohibited. Old Event: " + firingEvents.get(t) + "; New Event: " + toFire);
} else {
firingEvents.put(t, toFire);
}
}
}
protected boolean isLocked(Thread t) {
synchronized (firingEvents) {
return firingEvents.containsKey(t);
}
}
protected boolean isLocked() {
return isLocked(Thread.currentThread());
}
protected void endChange() {
Thread t = Thread.currentThread();
synchronized (firingEvents) {
if (firingEvents.containsKey(t)) {
firingEvents.remove(t);
} else {
throw new IllegalStateException("Change ended but thread not recorded as started.");
}
}
}
/**
* Fires an addition event to this object's adapter. This method must be called after the change has occurred in
* the list. This method enforces that not EventListener may modify the collection from inside the event handler
* handling an event from the associated collection.
*
* @param addedElement The object added to the associated collection
*
* @see ListEvent#getAddEvent(java.lang.Object, org.dlect.events.EventID, java.lang.Object)
* @see EventAdapter#fireEvent(org.dlect.events.Event)
*/
public void fireAdd(T addedElement) {
final ListEvent event = ListEvent.getAddEvent(source, eventID, addedElement);
beginChange(event);
try {
adapter.fireEvent(event);
} finally {
endChange();
}
}
/**
* Fires a replacement event to this object's adapter. This method must be called after the change has occurred in
* the list. This method enforces that not EventListener may modify the collection from inside the event handler
* handling an event from the associated collection.
*
* @param removedElement The object removed from the associated collection
*
* @see ListEvent#getRemoveEvent(java.lang.Object, org.dlect.events.EventID, java.lang.Object)
* @see EventAdapter#fireEvent(org.dlect.events.Event)
*/
public void fireRemove(T removedElement) {
final ListEvent event = ListEvent.getRemoveEvent(source, eventID, removedElement);
beginChange(event);
try {
adapter.fireEvent(event);
} finally {
endChange();
}
}
/**
* Fires a replacement event to this object's adapter. This method must be called after the change has occurred in
* the list. This method enforces that not EventListener may modify the collection from inside the event handler
* handling an event from the associated collection.
*
* @param original The original object before it was replaced.
* @param replacement The object replacing the original object.
*
* @see ListEvent#getReplaceEvent(java.lang.Object, org.dlect.events.EventID, java.lang.Object, java.lang.Object)
* @see EventAdapter#fireEvent(org.dlect.events.Event)
*/
public void fireReplace(T original, T replacement) {
final ListEvent event = ListEvent.getReplaceEvent(source, eventID, original, replacement);
beginChange(event);
try {
adapter.fireEvent(event);
} finally {
endChange();
}
}
protected EventAdapter getAdapter() {
return adapter;
}
}