Dépôt officiel du code source de l'ERP OpenConcerto
Rev 180 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils;
import org.openconcerto.utils.cc.BiConsumerExn;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Objects;
/**
* Allow to maintain the dispatching of events in order when a listener itself fires an event.
* <p>
* If each notification simply goes through a list of listeners, the events are delivered out of
* order : <img src="doc-files/reentrantEventsNaive.png" />
* <p>
* This class adds new notifications at the end of a list, but resume notifying from the start of
* the list. <img src="doc-files/reentrantEventsIn-order.png" />
*
* @author sylvain
*
* @param <L> listener type.
* @param <E> event type.
* @param <X> exception type.
*/
public final class ReentrantEventDispatcher<L, E, X extends Exception> {
public final class DispatchingState {
private final Iterator<L> iter;
private final BiConsumerExn<L, E, X> callback;
private final E evt;
private DispatchingState(final Iterator<L> iter, BiConsumerExn<L, E, X> callback, final E evt) {
this.iter = Objects.requireNonNull(iter, "Missing iterator");
this.callback = Objects.requireNonNull(callback, "Missing callback");
this.evt = evt;
}
private final Iterator<L> getIterator() {
return this.iter;
}
private final BiConsumerExn<L, E, X> getCallback() {
return this.callback;
}
public final E getEvt() {
return this.evt;
}
}
private final ThreadLocal<LinkedList<DispatchingState>> events = new ThreadLocal<LinkedList<DispatchingState>>() {
@Override
protected LinkedList<DispatchingState> initialValue() {
return new LinkedList<>();
}
};
private final BiConsumerExn<L, E, X> callback;
public ReentrantEventDispatcher() {
this(null);
}
public ReentrantEventDispatcher(final BiConsumerExn<L, E, X> callback) {
super();
this.callback = callback;
}
public final void fire(final Iterator<L> iter, final E evt) throws X {
this.fire(this.createDispatchingState(iter, evt));
}
public final void fire(final Iterator<L> iter, final BiConsumerExn<L, E, X> callback, final E evt) throws X {
this.fire(this.createDispatchingState(iter, callback, evt));
}
public final DispatchingState createDispatchingState(final Iterator<L> iter, final E evt) {
return this.createDispatchingState(iter, this.callback, evt);
}
public final DispatchingState createDispatchingState(final Iterator<L> iter, final BiConsumerExn<L, E, X> callback, final E evt) {
return new DispatchingState(iter, callback, evt);
}
public final void fire(final DispatchingState newTuple) throws X {
final LinkedList<DispatchingState> linkedList = this.events.get();
// add new event
linkedList.addLast(newTuple);
// process all pending events
DispatchingState currentTuple;
while ((currentTuple = linkedList.peekFirst()) != null) {
final Iterator<L> currentIter = currentTuple.getIterator();
final BiConsumerExn<L, E, X> currentCallback = currentTuple.getCallback();
final E currentEvt = currentTuple.getEvt();
while (currentIter.hasNext()) {
final L l = currentIter.next();
currentCallback.accept(l, currentEvt);
}
/*
* It isn't because one callback failed that the event itself didn't happen or should be
* reverted : we should still notify the other callbacks. So don't use a finally block
* to remove currentTuple. But if the event should indeed be reverted (e.g. the callback
* was a check), #remove(DispatchingState) should be called.
*/
// not removeFirst() since the item might have been already removed
linkedList.pollFirst();
}
}
/**
* Remove a dispatching state. Useful because if there's an exception while notifying the
* listeners, then the next {@link #fire(DispatchingState)} in the same thread will by default
* resume notifying the listeners. That behaviour is not valid for a reverted DB transaction.
*
* @param dispatchingState what to remove.
* @return {@code true} if the item was actually removed.
* @see #createDispatchingState(Iterator, Object)
*/
public final boolean remove(DispatchingState dispatchingState) {
return this.events.get().remove(dispatchingState);
}
}