/*******************************************************************************
* Copyright (c) 2007, 2014 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.protocol;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.UUID;
import org.eclipse.tcf.internal.core.ServiceManager;
import org.eclipse.tcf.internal.core.TransportManager;
import org.eclipse.tcf.internal.services.local.LocatorService;
import org.eclipse.tcf.services.ILocator;
/**
* Class Protocol provides static methods to access Target Communication Framework root objects:
* 1. the framework event queue and dispatch thread;
* 2. local instance of Locator service, which maintains a list of available targets;
* 3. list of open communication channels.
*
* It also provides utility methods for posting asynchronous events,
* including delayed events (timers).
*
* Before TCF can be used, it should be given an object implementing IEventQueue interface:
* @see #setEventQueue
*
* @noinstantiate This class is not intended to be instantiated by clients.
*/
public final class Protocol {
/**
* Event queue instance that holds all the Runnables to be run in the dispatch thread
*/
private static IEventQueue event_queue;
/**
* Main Logger instance used by the TCF Framework
*/
private static ILogger logger;
/**
* Set that holds the Timer objects for dispatch by the timer_dispatcher thread
*/
private static final TreeSet<Timer> timer_queue = new TreeSet<Timer>();
/**
* Agent ID constant generated pseudorandomly by UUID
*/
private static final String agent_id = UUID.randomUUID().toString();
/**
* Static variable that is incremented for every new Timer object added to the timer_queue, and whose value is used as ID of the Timer object,
* for resolving a time in the timestamps of different Timer objects
*/
private static int timer_cnt;
/**
* Class used for by the timer_dispatcher thread, that binds a Timestamp and a Runnable, and that enables the ordering of objects, according the Time
*/
private static class Timer implements Comparable<Timer> {
final int id;
final long time;
final Runnable run;
Timer(long time, Runnable run) {
this.id = timer_cnt++;
this.time = time;
this.run = run;
}
@Override
public int compareTo(Timer x) {
if (x == this) return 0;
if (time < x.time) return -1;
if (time > x.time) return +1;
if (id < x.id) return -1;
if (id > x.id) return +1;
assert false;
return 0;
}
@Override
public boolean equals(Object y) {
return (y instanceof Timer) && compareTo((Timer)y) == 0;
}
@Override
public int hashCode() {
return id;
}
}
/**
* Thread used for dispatching Runnables which must wait for a certain delay to be dispatched in the dispatch thread.
* @see #invokeLater(long delay, Runnable runnable)
*/
private static final Thread timer_dispatcher = new Thread() {
public void run() {
try {
synchronized (timer_queue) {
while (true) {
if (timer_queue.isEmpty()) {
timer_queue.wait();
}
else {
long time = System.currentTimeMillis();
Timer t = timer_queue.first();
if (t.time > time) {
timer_queue.wait(t.time - time);
}
else {
timer_queue.remove(t);
invokeLater(t.run);
}
}
}
}
}
catch (IllegalStateException x) {
// Dispatch is shut down, exit this thread
}
catch (Throwable x) {
log("Exception in TCF dispatch loop", x);
}
}
};
private static final ArrayList<CongestionMonitor> congestion_monitors = new ArrayList<CongestionMonitor>();
/**
* Before TCF can be used, it should be given an object implementing IEventQueue interface.
* The implementation maintains a queue of objects implementing Runnable interface and
* executes <code>run</code> methods of that objects in a sequence by a single thread.
* The thread in referred as TCF event dispatch thread. Objects in the queue are called TCF events.
* Executing <code>run</code> method of an event is also called dispatching of the event.
*
* Only few methods in TCF APIs are thread safe - can be invoked from any thread.
* If a method description does not say "can be invoked from any thread" explicitly -
* the method must be invoked from TCF event dispatch thread. All TCF listeners are
* invoked from the dispatch thread.
*
* @param event_queue - IEventQueue implementation.
*/
public static void setEventQueue(IEventQueue event_queue) {
assert Protocol.event_queue == null;
Protocol.event_queue = event_queue;
event_queue.invokeLater(new Runnable() {
public void run() {
LocatorService.createLocalInstance();
}
});
timer_dispatcher.setName("TCF Timer Dispatcher");
timer_dispatcher.setDaemon(true);
timer_dispatcher.start();
}
/**
* @return instance of IEventQueue that is used for TCF events.
*/
public static IEventQueue getEventQueue() {
return event_queue;
}
/**
* Returns true if the calling thread is TCF event dispatch thread.
* Use this call the ensure that a given task is being executed (or not being)
* on dispatch thread.
*
* @return true if running on the dispatch thread.
*/
public static boolean isDispatchThread() {
return event_queue != null && event_queue.isDispatchThread();
}
/**
* Causes <code>runnable</code> event to have its <code>run</code>
* method called in the dispatch thread of the framework.
* Events are dispatched in same order as queued.
* If invokeLater is called from the dispatching thread
* the <i>runnable.run()</i> will still be deferred until
* all pending events have been processed.
*
* This method can be invoked from any thread.
*
* @param runnable the <code>Runnable</code> whose <code>run</code>
* method should be executed asynchronously.
*/
public static void invokeLater(Runnable runnable) {
event_queue.invokeLater(runnable);
}
/**
* Causes <code>runnable</code> event to have its <code>run</code>
* method called in the dispatch thread of the framework.
* The event is dispatched after the given delay.
*
* This method can be invoked from any thread.
*
* @param delay milliseconds to delay event dispatch.
* If delay <= 0 the event is posted into the
* "ready" queue without delay.
* @param runnable the <code>Runnable</code> whose <code>run</code>
* method should be executed asynchronously.
*/
public static void invokeLater(long delay, Runnable runnable) {
if (delay <= 0) {
event_queue.invokeLater(runnable);
}
else {
synchronized (timer_queue) {
timer_queue.add(new Timer(System.currentTimeMillis() + delay, runnable));
timer_queue.notify();
}
}
}
/**
* Causes <code>runnable</code> to have its <code>run</code>
* method called in the dispatch thread of the framework.
* Calling thread is suspended until the method is executed.
* If invokeAndWait is called from the dispatching thread
* the <i>runnable.run()</i> is executed immediately.
*
* This method can be invoked from any thread.
*
* @param runnable the <code>Runnable</code> whose <code>run</code>
* method should be executed on dispatch thread.
*/
public static void invokeAndWait(final Runnable runnable) {
if (event_queue.isDispatchThread()) {
runnable.run();
}
else {
Runnable r = new Runnable() {
public void run() {
try {
runnable.run();
}
finally {
synchronized (this) {
notify();
}
}
}
};
synchronized (r) {
event_queue.invokeLater(r);
try {
r.wait();
}
catch (InterruptedException x) {
throw new Error(x);
}
}
}
}
/**
* Set framework logger.
* By default Eclipse logger is used, or System.err if TCF is used stand-alone.
*
* @param logger - an object implementing ILogger interface.
*/
public static synchronized void setLogger(ILogger logger) {
Protocol.logger = logger;
}
/**
* Logs the given message.
* @see #setLogger
* This method can be invoked from any thread.
* @param msg - log entry text
* @param x - a Java exception associated with the log entry or null.
*/
public static synchronized void log(String msg, Throwable x) {
if (logger == null) {
System.err.println(msg);
if (x != null) x.printStackTrace();
}
else {
logger.log(msg, x);
}
}
/**
* Get TCF agent unique ID.
* The intent of the ID is to enable distributed systems to uniquely identify instances of TCF agents.
* @return the agent ID.
*/
public static String getAgentID() {
return agent_id;
}
/**
* Get instance of the framework locator service.
* The service can be used to discover available remote peers.
*
* @return instance of ILocator.
*/
public static ILocator getLocator() {
return LocatorService.getLocator();
}
/**
* Return an array of all open channels.
* @return an array of IChannel
*/
public static IChannel[] getOpenChannels() {
assert isDispatchThread();
return TransportManager.getOpenChannels();
}
/**
* Interface to be implemented by clients willing to be notified when
* new TCF communication channel is opened.
*
* The interface allows a client to get pointers to channel objects
* that were opened by somebody else. If a client open a channel itself, it already has
* the pointer and does not need Protocol.ChannelOpenListener. If a channel is created,
* for example, by remote peer connecting to the client, the only way to get the pointer
* is Protocol.ChannelOpenListener.
*/
public interface ChannelOpenListener {
public void onChannelOpen(IChannel channel);
}
/**
* Add a listener that will be notified when new channel is opened.
* @param listener
*/
public static void addChannelOpenListener(ChannelOpenListener listener) {
assert isDispatchThread();
TransportManager.addChanelOpenListener(listener);
}
/**
* Remove channel opening listener.
* @param listener
*/
public static void removeChannelOpenListener(ChannelOpenListener listener) {
assert isDispatchThread();
TransportManager.removeChanelOpenListener(listener);
}
/**
* Transmit TCF event message.
* The message is sent to all open communication channels - broadcasted.
*/
public static void sendEvent(String service_name, String event_name, byte[] data) {
assert isDispatchThread();
TransportManager.sendEvent(service_name, event_name, data);
}
/**
* Call back after all TCF messages sent by this host up to this moment are delivered
* to their intended target. This method is intended for synchronization of messages
* across multiple channels.
*
* Note: Cross channel synchronization can reduce performance and throughput.
* Most clients don't need cross channel synchronization and should not call this method.
*
* @param done will be executed by dispatch thread after pending communication
* messages are delivered to corresponding targets.
*/
public static void sync(Runnable done) {
assert isDispatchThread();
TransportManager.sync(done);
}
/**
* Clients implement CongestionMonitor interface to monitor usage of local resources,
* like, for example, display queue size - if the queue becomes too big, UI response time
* can become too high, or it can crash all together because of OutOfMemory errors.
* TCF flow control logic prevents such conditions by throttling traffic coming from remote peers.
* Note: Local (in-bound traffic) congestion is detected by framework and reported to
* remote peer without client needed to be involved. Only clients willing to provide
* additional data about local congestion should implement CongestionMonitor and
* register it using Protocol.addCongestionMonitor().
*/
public interface CongestionMonitor {
/**
* Get current level of client resource utilization.
* @return integer value in range -100..100, where -100 means all resources are free,
* 0 means optimal load, and positive numbers indicate level of congestion.
*/
int getCongestionLevel();
}
/**
* Register a congestion monitor.
* @param monitor - client implementation of CongestionMonitor interface
*/
public static void addCongestionMonitor(CongestionMonitor monitor) {
assert monitor != null;
assert isDispatchThread();
congestion_monitors.add(monitor);
}
/**
* Unregister a congestion monitor.
* @param monitor - client implementation of CongestionMonitor interface
*/
public static void removeCongestionMonitor(CongestionMonitor monitor) {
assert isDispatchThread();
congestion_monitors.remove(monitor);
}
/**
* Get current level of local traffic congestion.
*
* @return integer value in range -100..100, where -100 means no pending
* messages (no traffic), 0 means optimal load, and positive numbers
* indicate level of congestion.
*/
public static int getCongestionLevel() {
assert isDispatchThread();
int level = -100;
for (CongestionMonitor m : congestion_monitors) {
int n = m.getCongestionLevel();
if (n > level) level = n;
}
if (event_queue != null) {
int n = event_queue.getCongestion();
if (n > level) level = n;
}
if (level > 100) level = 100;
return level;
}
/**
* Register s service provider.
* This method can be invoked from any thread.
* @param provider - IServiceProvider implementation
*/
public static void addServiceProvider(IServiceProvider provider) {
ServiceManager.addServiceProvider(provider);
}
/**
* Unregister s service provider.
* This method can be invoked from any thread.
* @param provider - IServiceProvider implementation
*/
public static void removeServiceProvider(IServiceProvider provider) {
ServiceManager.removeServiceProvider(provider);
}
/**
* Register s transport provider.
* This method can be invoked from any thread.
* @param provider - ITransportProvider implementation
*/
public static void addTransportProvider(ITransportProvider provider) {
TransportManager.addTransportProvider(provider);
}
/**
* Unregister s transport provider.
* This method can be invoked from any thread.
* @param provider - ITransportProvider implementation
*/
public static void removeTransportProvider(ITransportProvider provider) {
TransportManager.removeTransportProvider(provider);
}
}