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); } } }