/**
* Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
* All rights reserved. Use is subject to license terms. See LICENSE.TXT
*/
package org.diirt.datasource;
import java.util.HashMap;
import java.util.Map;
/**
* A specialized collector to handle multiple channels that can be added/removed
* dynamically and which gets translated to a single connection flag for a
* reader or writer.
*
* @author carcassi
*/
public class ConnectionCollector implements ReadFunction<Boolean> {
private final Object lock = new Object();
private final Map<String, Boolean> channelConnected = new HashMap<>();
private final Map<String, ConnectionWriteFunction> writeFunctions = new HashMap<>();
private Boolean connected;
private Runnable notification;
public void setChangeNotification(Runnable notification) {
synchronized (lock) {
this.notification = notification;
}
}
private class ConnectionWriteFunction implements WriteFunction<Boolean> {
private final String name;
private int counter = 1;
public ConnectionWriteFunction(String name) {
this.name = name;
}
@Override
public void writeValue(Boolean newValue) {
Runnable task;
synchronized(lock) {
if (isClosed()) {
throw new IllegalStateException("ConnectionCollector for '" + name + "' was closed.");
}
channelConnected.put(name, newValue);
connected = null;
task = notification;
}
// Run task without holding the lock
if (task != null) {
task.run();
}
}
private void open() {
counter++;
}
private boolean isClosed() {
return counter == 0;
}
private void close() {
counter--;
}
}
/**
* Adds a new channel to the collector and returns the write function
* to use to change the connection status.
*
* @param name channel name
* @return the write function
*/
WriteFunction<Boolean> addChannel(final String name) {
synchronized (lock) {
if (channelConnected.containsKey(name)) {
ConnectionWriteFunction writeFunction = writeFunctions.get(name);
writeFunction.open();
return writeFunction;
} else {
channelConnected.put(name, false);
ConnectionWriteFunction writeFunction = new ConnectionWriteFunction(name);
writeFunctions.put(name, writeFunction);
connected = null;
return writeFunction;
}
}
}
@Override
public Boolean readValue() {
synchronized (lock) {
if (connected == null) {
connected = calculate(channelConnected);
}
return connected;
}
}
/**
* Calculates the overall connection status based on the status of each
* channel.
* <p>
* For future development, this is the method that one could
* override to implement a different connection logic.
*
* @param channelConnected the connection status of each channel
* @return the overall connection status
*/
boolean calculate(Map<String, Boolean> channelConnected) {
for (Boolean conn : channelConnected.values()) {
if (conn != Boolean.TRUE) {
return false;
}
}
return true;
}
/**
* Remove a channel from the collector.
*
* @param channelName the channel name
*/
void removeChannel(String channelName) {
synchronized(lock) {
ConnectionWriteFunction function = writeFunctions.get(channelName);
if (function == null) {
throw new IllegalArgumentException("Trying to remove channel '" + channelName + "' from ConnectionCollector, but it was already removed or never added.");
} else {
function.close();
if (function.isClosed()) {
channelConnected.remove(channelName);
writeFunctions.remove(channelName);
connected = null;
}
}
}
}
}