/* * Strongback * Copyright 2015, Strongback and individual contributors by the @authors tag. * See the COPYRIGHT.txt in the distribution for a full listing of individual * contributors. * * Licensed under the MIT License; you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://opensource.org/licenses/MIT * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.strongback; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; import org.strongback.annotation.Immutable; import org.strongback.annotation.ThreadSafe; import org.strongback.components.Switch; /** * A threadsafe {@link SwitchReactor} implementation that relies upon being periodically {@link Executable#execute(long) * executed}. This class is carefully written to ensure that all functions are registered atomically even while * {@link #execute(long)} is being called. * * @author Randall Hauch */ @ThreadSafe final class AsyncSwitchReactor implements Executable, SwitchReactor { private final ConcurrentMap<Switch, Container> listeners = new ConcurrentHashMap<>(); @Override public void execute(long time) { listeners.forEach((swtch, container) -> container.notifyListeners(swtch.isTriggered())); } @Override public void onTriggered(Switch swtch, Runnable function) { listeners.computeIfAbsent(swtch,(s)->new Container()).addWhenTriggered(function); } @Override public void onUntriggered(Switch swtch, Runnable function) { listeners.computeIfAbsent(swtch,(s)->new Container()).addWhenUntriggered(function); } @Override public void whileTriggered(Switch swtch, Runnable function) { listeners.computeIfAbsent(swtch,(s)->new Container()).addWhileTriggered(function); } @Override public void whileUntriggered(Switch swtch, Runnable function) { listeners.computeIfAbsent(swtch,(s)->new Container()).addWhileUntriggered(function); } /** * A container class for all listener functions associated with a specific {@link Switch}. The class is threadsafe to allow * for new listener functions to be added while the existing functions are called based upon the switch's current state. * <p> * To achieve efficient and lock-free concurrent operations, each of the functions for a specific Switch state or transition * are maintained in a simple linked-list structure (see {@link Listener}). Each immutable Listener is created to hold one * function and an optional "next" listener. To add a new function, a new Listener object is created with the function and * the current Listener object for that state, and the Container's reference to that state's listeners is updated with the * new Listener object. In essence, new functions are added to the front of the linked list without using any locking. * <p> * It is not currently possible to remove functions that have been registered. * * @author Randall Hauch */ @ThreadSafe private static final class Container { private boolean previouslyTriggered; private final AtomicReference<Listener> whenTriggered = new AtomicReference<>(); private final AtomicReference<Listener> whenUntriggered = new AtomicReference<>(); private final AtomicReference<Listener> whileTriggered = new AtomicReference<>(); private final AtomicReference<Listener> whileUntriggered = new AtomicReference<>(); public void notifyListeners(boolean nowTriggered) { notifyAtomicallyWhen(()->!previouslyTriggered && nowTriggered, whenTriggered); notifyAtomicallyWhen(()->previouslyTriggered && !nowTriggered, whenUntriggered); notifyAtomicallyWhen(()->previouslyTriggered && nowTriggered, whileTriggered); notifyAtomicallyWhen(()->!previouslyTriggered && !nowTriggered, whileUntriggered); previouslyTriggered = nowTriggered; } private void notifyAtomicallyWhen(BooleanSupplier criteria, AtomicReference<Listener> listenerRef ) { Listener listener = listenerRef.get(); if ( listener != null && criteria.getAsBoolean() ) listener.fire(); } public void addWhenTriggered(Runnable function) { whenTriggered.updateAndGet((existing)->new Listener(function,existing)); } public void addWhenUntriggered(Runnable function) { whenUntriggered.updateAndGet((existing)->new Listener(function,existing)); } public void addWhileTriggered(Runnable function) { whileTriggered.updateAndGet((existing)->new Listener(function,existing)); } public void addWhileUntriggered(Runnable function) { whileUntriggered.updateAndGet((existing)->new Listener(function,existing)); } } /** * One node in a linked list of listener functions. * * @author Randall Hauch */ @Immutable private static final class Listener { private final Runnable function; private final Listener next; public Listener(Runnable function, Listener next) { this.function = function; this.next = next; } public void fire() { function.run(); if (next != null) next.fire(); } } }