// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.util;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A node in a network that broadcasts some signal among all its peers one time. The signal may get
* converted when goes from node to node. The first time a node receives a signal, it calls
* a user callback. It ignores all further signals. A signal is accompanied by exception
* called 'cause' that can be null.
* <p>The class is thread-safe.
* <p>This class is useful for a shutdown strategy, when several resources should be taken down
* together, but there are no single authority to manage it. E.g. in a client-medium-server
* threesome each part can initiate shutdown.
* <p>Nodes of different signal system can be bound using {@link SignalConverter}.
*
* @param <SIGNAL> type of signal that this node works with
*/
public class SignalRelay<SIGNAL> {
public static <T> SignalRelay<T> create(Callback<T> callback) {
return new SignalRelay<T>(callback);
}
private static final Logger LOGGER = Logger.getLogger(SignalRelay.class.getName());
private final List<PeerWrapper<SIGNAL, ?>> peers = new ArrayList<PeerWrapper<SIGNAL, ?>>(2);
private final Callback<SIGNAL> callback;
private boolean isSignalled = false;
private SIGNAL savedSignal;
public SignalRelay(Callback<SIGNAL> callback) {
this.callback = callback;
}
public void sendSignal(SIGNAL signal, Exception cause) {
sendSignalImpl(this, signal, cause);
}
public synchronized boolean isSignalled() {
return isSignalled;
}
/**
* @return a signal received or null if called before a signal came in.
*/
public synchronized SIGNAL getReceivedSignal() {
return savedSignal;
}
/**
* Binds this node to a peer node. If the peer already received a signal, accepts it and
* throws an exception.
* @param <OPPOSITE> type of signal the peer node works with
* @param toPeerConverter a signal converter that is used when sending signal or null
* @param fromPeerConverter a signal converter that is used when receiving signal or null
* @throws AlreadySignalledException when binding to node that already has a signal
*/
public <OPPOSITE> void bind(SignalRelay<OPPOSITE> peer,
SignalConverter<SIGNAL, OPPOSITE> toPeerConverter,
SignalConverter<OPPOSITE, SIGNAL> fromPeerConverter) throws AlreadySignalledException {
this.addPeer(peer, toPeerConverter);
try {
peer.addPeer(this, fromPeerConverter);
} catch (AlreadySignalledException e) {
PeerWrapper.send(this, peer.getReceivedSignal(), this, fromPeerConverter, e);
throw e;
}
}
/**
* An interface to notify a user about a received signal.
*/
public interface Callback<S> {
/**
* Called from the thread that initiated a broadcast (i.e. called
* {@link #onSignal(Object, Exception)}).
* @throws RuntimeException thrown exception gets caught and logged
* @throws Error not caught
*/
void onSignal(S signal, Exception cause) throws RuntimeException, Error;
}
/**
* A converter that translates signals from one type to another so that unrelated resources
* could co-work.
*/
public interface SignalConverter<FROM, TO> {
TO convert(FROM source);
}
public static class AlreadySignalledException extends Exception {
AlreadySignalledException() {
super();
}
AlreadySignalledException(String message, Throwable cause) {
super(message, cause);
}
AlreadySignalledException(String message) {
super(message);
}
AlreadySignalledException(Throwable cause) {
super(cause);
}
}
private void sendSignalImpl(SignalRelay<?> source, SIGNAL signal, Exception cause) {
synchronized (this) {
if (isSignalled) {
return;
}
isSignalled = true;
savedSignal = signal;
}
try {
callback.onSignal(signal, cause);
} catch (RuntimeException e) {
LOGGER.log(Level.SEVERE, "Exception in relay callback", e);
}
for (PeerWrapper<SIGNAL, ?> peer : peers) {
if (peer.getPeer() == source) {
continue;
}
peer.send(this, signal, cause);
}
}
private synchronized <OPPOSITE> void addPeer(SignalRelay<OPPOSITE> peer,
SignalConverter<SIGNAL, OPPOSITE> converter) throws AlreadySignalledException {
if (isSignalled) {
throw new AlreadySignalledException();
}
peers.add(new PeerWrapper<SIGNAL, OPPOSITE>(peer, converter));
}
private static class PeerWrapper<S, P> {
private final SignalRelay<P> peer;
private final SignalConverter<S, P> converter;
PeerWrapper(SignalRelay<P> peer, SignalConverter<S, P> converter) {
this.peer = peer;
this.converter = converter;
}
SignalRelay<?> getPeer() {
return peer;
}
void send(SignalRelay<?> source, S signal, Exception cause) {
send(source, signal, peer, converter, cause);
}
static <T, D> void send(SignalRelay<?> source, T signal, SignalRelay<D> destination,
SignalConverter<T, D> converter, Exception cause) {
D convertedSignal;
if (converter == null) {
convertedSignal = null;
} else {
convertedSignal = converter.convert(signal);
}
destination.sendSignalImpl(source, convertedSignal, cause);
}
}
}