package lcm.lcm;
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.nio.*;
import lcm.util.*;
/** Lightweight Communications and Marshalling Java implementation **/
public class LCM
{
static class SubscriptionRecord
{
String regex;
Pattern pat;
LCMSubscriber lcsub;
}
ArrayList<SubscriptionRecord> subscriptions = new ArrayList<SubscriptionRecord>();
ArrayList<Provider> providers = new ArrayList<Provider>();
HashMap<String,ArrayList<SubscriptionRecord>> subscriptionsMap = new HashMap<String,ArrayList<SubscriptionRecord>>();
boolean closed = false;
static LCM singleton;
LCMDataOutputStream encodeBuffer = new LCMDataOutputStream(new byte[1024]);
/** Create a new LCM object, connecting to one or more URLs. If
* no URL is specified, the environment variable LCM_DEFAULT_URL is
* used. If that environment variable is not defined, then the
* default URL is used.
**/
public LCM(String... urls) throws IOException
{
if (urls.length==0) {
String env = System.getenv("LCM_DEFAULT_URL");
if (env == null)
urls = new String[] {"udpm://239.255.76.67:7667"};
else
urls = new String[] { env };
}
for (String url : urls) {
// Allow passing in NULL or the empty string to explicitly indicate
// the default LCM URL.
if(null == url || url.equals("")) {
url = System.getenv("LCM_DEFAULT_URL");
if (url == null)
url = "udpm://239.255.76.67:7667";
}
URLParser up = new URLParser(url);
String protocol = up.get("protocol");
if (protocol.equals("udpm"))
providers.add(new UDPMulticastProvider(this, up));
else if (protocol.equals("tcpq"))
providers.add(new TCPProvider(this, up));
else if (protocol.equals("file"))
providers.add(new LogFileProvider(this, up));
else if (protocol.equals("memq"))
providers.add(new MemqProvider(this, up));
else
System.err.println("LCM: Unknown URL protocol: "+protocol);
}
}
/** Retrieve a default instance of LCM using either the environment
* variable LCM_DEFAULT_URL or the default. If an exception
* occurs, System.exit(-1) is called.
**/
public static LCM getSingleton()
{
if (singleton == null) {
try {
singleton = new LCM();
} catch (Exception ex) {
System.err.println("LC singleton fail: "+ex);
System.exit(-1);
return null;
}
}
return singleton;
}
/** Return the number of subscriptions. **/
public int getNumSubscriptions()
{
if (this.closed) throw new IllegalStateException();
return subscriptions.size();
}
/** Publish a string on a channel. This method does not use the
* LCM type definitions and thus is not type safe. This method is
* primarily provided for testing purposes and may be removed in
* the future.
**/
public void publish(String channel, String s) throws IOException
{
if (this.closed) throw new IllegalStateException();
s=s+"\0";
byte[] b = s.getBytes();
publish(channel, b, 0, b.length);
}
/** Publish an LCM-defined type on a channel. If more than one URL was
* specified, the message will be sent on each.
**/
public synchronized void publish(String channel, LCMEncodable e)
{
if (this.closed) throw new IllegalStateException();
try {
encodeBuffer.reset();
e.encode(encodeBuffer);
publish(channel, encodeBuffer.getBuffer(), 0, encodeBuffer.size());
} catch (IOException ex) {
System.err.println("LC publish fail: "+ex);
}
}
/** Publish raw data on a channel, bypassing the LCM type
* specification. If more than one URL was specified when the LCM
* object was created, the message will be sent on each.
**/
public synchronized void publish(String channel, byte[] data, int offset, int length)
throws IOException
{
if (this.closed) throw new IllegalStateException();
for (Provider p : providers)
p.publish(channel, data, offset, length);
}
/** Subscribe to all channels whose name matches the regular
* expression. Note that to subscribe to all channels, you must
* specify ".*", not "*".
**/
public void subscribe(String regex, LCMSubscriber sub)
{
if (this.closed) throw new IllegalStateException();
SubscriptionRecord srec = new SubscriptionRecord();
srec.regex = regex;
srec.pat = Pattern.compile(regex);
srec.lcsub = sub;
synchronized(this) {
for (Provider p : providers)
p.subscribe (regex);
}
synchronized(subscriptions) {
subscriptions.add(srec);
for (String channel : subscriptionsMap.keySet()) {
if (srec.pat.matcher(channel).matches()) {
ArrayList<SubscriptionRecord> subs = subscriptionsMap.get(channel);
subs.add(srec);
}
}
}
}
/** Remove this particular regex/subscriber pair (UNTESTED AND API
* MAY CHANGE). If regex is null, all subscriptions for 'sub' are
* cancelled. If subscriber is null, any previous subscriptions
* matching the regular expression will be cancelled. If both
* 'sub' and 'regex' are null, all subscriptions will be
* cancelled.
**/
public void unsubscribe(String regex, LCMSubscriber sub) {
if (this.closed) throw new IllegalStateException();
synchronized(this) {
for (Provider p : providers)
p.unsubscribe (regex);
}
// TODO: providers don't seem to use anything beyond first channel
synchronized(subscriptions) {
// Find and remove subscriber from list
for (Iterator<SubscriptionRecord> it = subscriptions.iterator(); it.hasNext(); ) {
SubscriptionRecord sr = it.next();
if ((sub == null || sr.lcsub == sub) &&
(regex == null || sr.regex.equals(regex))) {
it.remove();
}
}
// Find and remove subscriber from map
for (String channel : subscriptionsMap.keySet()) {
for (Iterator<SubscriptionRecord> it = subscriptionsMap.get(channel).iterator(); it.hasNext(); ) {
SubscriptionRecord sr = it.next();
if ((sub == null || sr.lcsub == sub) &&
(regex == null || sr.regex.equals(regex))) {
it.remove();
}
}
}
}
}
/** Not for use by end users. Provider back ends call this method
* when they receive a message. The subscribers that match the
* channel name are synchronously notified.
**/
public void receiveMessage(String channel, byte data[], int offset, int length)
{
if (this.closed) throw new IllegalStateException();
synchronized (subscriptions) {
ArrayList<SubscriptionRecord> srecs = subscriptionsMap.get(channel);
if (srecs == null) {
// must build this list!
srecs = new ArrayList<SubscriptionRecord>();
subscriptionsMap.put(channel, srecs);
for (SubscriptionRecord srec : subscriptions) {
if (srec.pat.matcher(channel).matches())
srecs.add(srec);
}
}
for (SubscriptionRecord srec : srecs) {
srec.lcsub.messageReceived(this,
channel,
new LCMDataInputStream(data, offset, length));
}
}
}
/** A convenience function that subscribes to all LCM channels. **/
public synchronized void subscribeAll(LCMSubscriber sub)
{
subscribe(".*", sub);
}
/** Call this function to release all resources used by the LCM instance. After calling this
* function, the LCM instance should consume no resources, and cannot be used to
* receive or transmit messages.
*/
public synchronized void close()
{
if (this.closed) throw new IllegalStateException();
for (Provider p : providers) {
p.close();
}
providers = null;
this.closed = true;
}
////////////////////////////////////////////////////////////////
/** Minimalist test code.
**/
public static void main(String args[])
{
LCM lcm;
try {
lcm = new LCM();
} catch (IOException ex) {
System.err.println("ex: "+ex);
return;
}
lcm.subscribeAll(new SimpleSubscriber());
while (true) {
try {
Thread.sleep(1000);
lcm.publish("TEST", "foobar");
} catch (Exception ex) {
System.err.println("ex: "+ex);
}
}
}
static class SimpleSubscriber implements LCMSubscriber
{
public void messageReceived(LCM lcm, String channel, LCMDataInputStream dins)
{
System.err.println("RECV: "+channel);
}
}
}