// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/graphicLoader/netmap/NetMapReader.java,v $ // $RCSfile: NetMapReader.java,v $ // $Revision: 1.5 $ // $Date: 2005/08/09 17:46:33 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.graphicLoader.netmap; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.InterruptedIOException; import java.io.PrintWriter; import java.io.StreamTokenizer; import java.io.StringReader; import java.net.Socket; import java.util.Properties; import java.util.Vector; import com.bbn.openmap.util.Debug; /** * The NetMapReader is the class that makes actual contact with the * NetMap server and maintains the connection to it. It is controlled * by the NetMapConnector, and is created by it as well. The * NetMapReader understands that the NetMap server maintains a notion * of views, and that to get information from the server, it has to * request information about a particular view. Luckily, a list of * views can be requested from the server as well. * <P> * * The NetMap server will provide a text output stream of lines, with * each line representing an event. The NetMapReader has an implicit * understanding of the format of different line types, and creates a * java Properties object for each line, categorizing each field, and * putting it in the Properties object as (Field Header key, Field * value). This Properties object is sent to the NetMapConnector, * where it is distributed to the NetMapEventListeners. Each * NetMapEventListener is free to peruse the Properties object in the * event, and get information it needs from each one by asking for * specific field values. Some fields may not be present in all * Properties objects, but the fields should be consistent for the * same event type. At some point, we'll include a description of the * events and the fields that can be expected. * * In general, there are two things you can do. First, you can create * a NetMapReader without a view, and then call getViewList() on it to * get a list of possible views. The NetMapReader will disconnect * itself after that request. Second, you can create a NetMapReader * with a specific view (or set the view later), and then call start() * on it to begin receiving events about that view. Call disconnect() * when you want it to stop. * <P> */ public class NetMapReader extends Thread implements NetMapConstants { private static Object EOF = new Object(); private static Object LP = new Object(); private static Object RP = new Object(); StreamTokenizer st = null; boolean shutdown = false; String viewName = null; Socket s = null; /** * This is the component that is organizing the dispersal of the * events as a result of reading stuff off the NetMap connection. */ NetMapConnector netmapConn; boolean DEBUG = false; boolean DEBUG_VERBOSE = false; /** * Create a NetMapReader to listen to a host on a port, with a * NetMapConnector to get back in touch with. This method just * connects, finds out the views, and closes the connection to the * server. */ public NetMapReader(String host, String port, NetMapConnector connector) throws IOException { this(host, port, connector, null); } /** * Create a NetMapReader to listen to a NetMapServer running on a * port, and parse the stream relating information about the given * view. If the view is null, then the NetMapReader will get a * view list and disconnect. */ public NetMapReader(String host, String port, NetMapConnector connector, String view) throws IOException { netmapConn = connector; if (view != null) { setView(view); } DEBUG = Debug.debugging("netmap"); DEBUG_VERBOSE = Debug.debugging("netmap_verbose"); try { s = connect(host, port); } catch (IOException e) { throw e; } } /** * Set the view that will be requested when the reader is started, * via reader.start(). */ public void setView(String view) { this.viewName = view; } /** * A general connection method that returns a socket for a host * and port. */ private Socket connect(String host, String portString) throws IOException { int port = 0; Socket sock = null; boolean DEBUG = Debug.debugging("netmap"); try { port = Integer.parseInt(portString, 10); } catch (NumberFormatException e) { if (DEBUG) Debug.output("Illegal name " + host + ":" + portString); throw new IOException("Illegal port: " + portString); } if (DEBUG) Debug.output("Connecting to server " + host + ":" + port); try { sock = new Socket(host, port); } catch (IOException e) { if (sock != null) sock.close(); if (DEBUG) { Debug.output("Can't connect to " + host + ":" + port + "\n " + e); } throw e; } return sock; } /** * For an established NetMapReader, get the list of views that the * NetMapReader knows about. The NetMapReader will disconnect * after this query. */ public ChoiceList getViewList(String host, String port) { BufferedReader in = null; PrintWriter out = null; ChoiceList viewList = null; Socket sock = null; boolean DEBUG = Debug.debugging("netmap"); try { sock = connect(host, port); in = new BufferedReader(new InputStreamReader(sock.getInputStream())); out = new PrintWriter(sock.getOutputStream(), true); // Now load the views from the server out.println(JMAP_VIEW_CMD); } catch (IOException e) { Debug.error("NetMapReader: " + e); return null; } viewList = new ChoiceList(); try { String line = null; while ((line = in.readLine()) != null) { line = line.trim(); int labelStart = line.indexOf("\'"); int labelEnd = line.lastIndexOf("\'"); String viewLabel = null; String viewLine = null; if (DEBUG) Debug.output("View definition: " + line); if (labelStart < 0) { viewLine = line; viewLabel = line; } else { viewLine = line.substring(0, labelStart) + " " + line.substring(labelEnd + 1); viewLabel = line.substring((labelStart + 1), labelEnd); } viewList.add(viewLabel.trim(), viewLine.trim()); } } catch (IOException e) { Debug.error("NetMapReader: Input error: " + e); viewList.removeAllElements(); viewList = null; } try { sock.close(); netmapConn.connectionDown(); } catch (IOException e) { } return viewList; } /** * A queue command that lets the NetMapReader know to disconnect * when it has the opportunity to. */ public void shutdown() { this.shutdown = true; } /** * Called when NetMapReader.start() is called. This makes the * NetMapReader call the NetMap server to get information about * the view that is set. This assumes that the view has been set. * The NetMapConnector will be called back with Properties objects * describing events received. */ public void run() { BufferedReader tdin = null; PrintWriter tdout = null; if (viewName == null) { Debug.error("NetMapReader not given a view name to request from the NETMAP server."); return; } while (!this.shutdown) { if (DEBUG) Debug.output("NetMapReader attempting connection"); try { tdin = new BufferedReader(new InputStreamReader(s.getInputStream())); tdout = new PrintWriter(s.getOutputStream(), true); if (DEBUG) Debug.output("Loading view: " + viewName); tdout.println("jmap '" + viewName + "' 768"); s.setSoTimeout(500); } catch (InterruptedIOException eConnectInterrupted) { continue; } catch (IOException eConnect) { Debug.error("NetMapReader: " + eConnect.getMessage() + "; NetMapReader sleeping"); try { Thread.sleep(40000); } catch (Exception eSleep) { } continue; } if (netmapConn == null) { continue; } netmapConn.connectionUp(); while (!this.shutdown) { try { String line = null; if ((line = tdin.readLine()) == null) break; if (DEBUG_VERBOSE) Debug.output(" read: " + line); Properties eventProps = procline(line); if (DEBUG_VERBOSE) Debug.output(" processed..."); if (!eventProps.isEmpty()) { netmapConn.distributeEvent(eventProps); if (DEBUG_VERBOSE) Debug.output(" distributed..."); } else { if (DEBUG_VERBOSE) Debug.output(" ignored..."); } } catch (InterruptedIOException eReadInterrupted) { continue; } catch (Exception e) { Debug.error("NetMapReader exception: " + e.getMessage() + "; in NetMapReader run. "); // Which is better? // break; continue; } } try { s.close(); netmapConn.connectionDown(); } catch (Exception eShutdown) { } } } /** * Used to represent Double values read off the stream, converted * from the tokenized vector. */ int[] nargs = new int[60]; /** Process a line from NetMap input stream. */ protected Properties procline(String cmdline) { Vector v = tokenize(cmdline); // Right now, nargs[] gets set in tokenize() with values // gleamed off Doubles in v. if (DEBUG_VERBOSE) Debug.output(" parsed: " + v.toString()); Properties eventProps = new Properties(); String cmd = v.firstElement().toString(); int shape = nargs[4]; if (cmd.equals(NODE_OBJECT)) { eventProps.put(COMMAND_FIELD, NODE_OBJECT); eventProps.put(INDEX_FIELD, Integer.toString(nargs[1])); eventProps.put(SHAPE_FIELD, Integer.toString(nargs[4])); if (v.elementAt(4) instanceof String) { String icon = (String) v.elementAt(4); eventProps.put(SHAPE_FIELD, "11"); eventProps.put(ICON_FIELD, icon); if (DEBUG) Debug.output("NetMapReader: jimage " + icon); } if (shape == NODE_DELETE) { // Delete eventProps.put(SHAPE_FIELD, NODE_DELETE_STRING); } else { if (shape == NODE_MOVE) { // move // nobj 13 342 432 0 lat=42.3876343 // lon=-71.1457977 elev=0.00 1038000904 // cmd, index, posx, posy, node_move, lat, lon, // elevation, time eventProps.put(SHAPE_FIELD, NODE_MOVE_STRING); eventProps.put(TIME_FIELD, Integer.toString(nargs[8])); } else { // nobj 1 13 12 50 10 10 1 5 '11' 0 'NODE_11_' // lat=40.0295830 lon=-74.3184204 ip=10.0.0.11 // cmd, index, posx, posy, icon, width, height, // status (color), menu, label, joffset, name, // lat, lon // Define a new entry if "shape" is anything // else... eventProps.put(WIDTH_FIELD, Integer.toString(nargs[5])); eventProps.put(HEIGHT_FIELD, Integer.toString(nargs[6])); eventProps.put(STATUS_FIELD, Integer.toString(nargs[7])); eventProps.put(MENU_FIELD, Integer.toString(nargs[8])); eventProps.put(LABEL_FIELD, (String) v.elementAt(9)); eventProps.put(NAME_FIELD, (String) v.elementAt(11)); eventProps.put(JOFFSET_FIELD, Integer.toString(nargs[10])); } eventProps.put(POSX_FIELD, Integer.toString(nargs[2])); eventProps.put(POSY_FIELD, Integer.toString(nargs[3])); String elev = null; if ((elev = getVal("elev=", cmdline)) != null) { eventProps.put(ELEVATION_FIELD, elev); } String geo = null; if ((geo = getVal("lat=", cmdline)) != null) { eventProps.put(LAT_FIELD, geo); } if ((geo = getVal("lon=", cmdline)) != null) { eventProps.put(LON_FIELD, geo); } if ((geo = getVal("ip=", cmdline)) != null) { eventProps.put(IP_FIELD, geo); } } } else if (cmd.equals(NODE_OBJECT_STATUS)) { eventProps.put(COMMAND_FIELD, NODE_OBJECT_STATUS); eventProps.put(INDEX_FIELD, Integer.toString(nargs[1])); eventProps.put(STATUS_FIELD, Integer.toString(nargs[2])); } else if (cmd.equals(LINK_OBJECT_STATUS)) { eventProps.put(COMMAND_FIELD, LINK_OBJECT_STATUS); eventProps.put(INDEX_FIELD, Integer.toString(nargs[1])); eventProps.put(STATUS_FIELD, Integer.toString(nargs[2])); } else if (cmd.equals(LINK_OBJECT)) { eventProps.put(COMMAND_FIELD, LINK_OBJECT); eventProps.put(INDEX_FIELD, Integer.toString(nargs[1])); eventProps.put(SHAPE_FIELD, Integer.toString(nargs[2])); if (shape != -1) { eventProps.put(STATUS_FIELD, Integer.toString(nargs[5])); eventProps.put(LINK_NODE1_FIELD, Integer.toString(nargs[3])); eventProps.put(LINK_NODE2_FIELD, Integer.toString(nargs[4])); } } else if (cmd.equals(REFRESH)) { eventProps.put(COMMAND_FIELD, REFRESH); } else if (cmd.equals(UPDATE)) { eventProps.put(COMMAND_FIELD, UPDATE); } return eventProps; } /** * Given a line, break it up into a Vector representing the String * parts, and the int[] containing the number parts. The Vector * will contain String representations of the numbers. Should be * called before procline() is called, and in fact is called from * within procline(). */ protected Vector tokenize(String line) { Object ob; Vector v = new Vector(12, 10); unitInit(new StringReader(line)); int cnt = 0; while ((ob = unit()) != EOF) { v.addElement(ob); if (ob instanceof Double) nargs[cnt] = ((Number) ob).intValue(); else nargs[cnt] = 0; cnt++; } return v; } /** * Initialize the StringTokenizer. */ protected void unitInit(StringReader rdr) { st = new StreamTokenizer(rdr); st.commentChar('%'); st.slashSlashComments(true); st.slashStarComments(true); st.wordChars('/', '/'); // disable default special handling st.wordChars('=', '='); // disable default special handling st.wordChars(':', ':'); // disable default special handling } /** * Break the next token into an Object, with some addition * semantic functionality to interpret EOF and parenthesis. */ protected Object unit() { Object p = next(); if (p == EOF) return EOF; if (p == LP) { Object r; Vector l = new Vector(2, 4); while (true) { r = unit(); if (r == RP) return l; if (r == EOF) return EOF; l.addElement(r); } } return p; } /** * Break the next token into an Object. */ protected Object next() { int i = 0; char[] c; try { i = st.nextToken(); } catch (IOException e) { Debug.error("NetMapReader: " + e.toString() + " in toktest\n"); } if ((i == StreamTokenizer.TT_EOF) || (i == 0)) return EOF; if (i == StreamTokenizer.TT_WORD) return new Symbol(st.sval, 1); if ((i == '\'') || (i == '\"')) return st.sval; if (i == StreamTokenizer.TT_NUMBER) return new Double(st.nval); if ((i == '(') || (i == '[') || (i == '{')) return LP; if ((i == ')') || (i == ']') || (i == '}')) return RP; c = new char[1]; c[0] = (char) i; return new Symbol(new String(c), 2); } protected String getVal(String marker, String line) { int sTok = 0; int eTok = 0; if ((sTok = line.toLowerCase().indexOf(marker)) < 0) return null; if (((eTok = line.indexOf(" ", sTok)) < 0) && ((eTok = line.indexOf("\t", sTok)) < 0)) { eTok = line.length(); } return (line.substring(sTok + marker.length(), eTok)); } // All this stuff below may be used later for more JMAP // integration... // jicon jget(String name) { // jicon x = (jicon)jicons.get(name); // if (x == null) { // x = new jicon(name); // jicons.put(name, x); // x.icon = can.loadImage("images/"+name); // if (jmap.on && jmap.dbgmode) // System.err.println("new jicon " + name + " " + x.icon); // } // return x; // } /** * Got a response from our cexec request */ // private void cexec(String a) { // f.status("exec " + a); // show message in status line // try { // Runtime.getRuntime().exec(a); // } catch(IOException err) { // if (jmap.on) // System.err.println(err.toString()); // } // } /** * Got a response from our cshow request */ // public void cshow(String url) { // // show message in status line // f.status("show " + url); // try { // AppletContext apcon = jmap.getAppletContext(); // if (url.substring(0, 2).equals("r ")) { // relative // apcon.showDocument(new URL(jmap.getDocumentBase(), // url.substring(2))); // } // else // apcon.showDocument(new URL(url)); // } catch (java.net.MalformedURLException err) { // if (jmap.on) // System.err.println(err.toString()); // } // } /** * We got a popup menu from NetMap that we had requested a bit * ago. jmenu 2 SNMP (('show name' MCMD 'echo $NAME') ('ping' MCMD * 'pingf $NAME')) */ // private void jmenu(String line) { // PopupMenu tmenuh; // int type = nargs[ 1 ]; // if (jmap.on && jmap.dbgmode) // System.err.println("Got jmenu type " + type); // Vector l; // Vector r = new Vector(4, 4); // if (f.pmenu[ type ] == null) { // if not already saved // // create menu; store it in our global array // l = (Vector)v.elementAt(3); // tmenuh = new PopupMenu(v.elementAt(2).toString()); // popupButtons(tmenuh, l, r); // f.pmenu[ type ] = tmenuh; // f.pmenuV[ type ] = r; // can.add(tmenuh); // } // if (f.tmenutypeDesired == type) { // if still desired // // install menu in ourjmap window // f.tmenu = f.pmenu[ type ]; // f.tmenutype = type; // f.tmenutypeDesired = 0; // f.tmenu.show(can, f.tmenux, f.tmenuy); // } // } // private void jpulldowns(String line) { // if (pulldowns) // return; // already have pulldowns; skip this // pulldowns = true; // Menu tmenuh; // int type = nargs[1 ]; // Vector l; // Vector r= new Vector(4, 4); // Vector g = (Vector)v.elementAt(1); // Enumeration e = g.elements(); // while (e.hasMoreElements()) { // tmenuh = new Menu(e.nextElement().toString()); // l = (Vector)e.nextElement(); // popupButtons(tmenuh, l, r); // f.mb.add(tmenuh); // } // f.pulldownV = r; // save action data for menu operations // } /** * Recursively create menu from list specification Vector r is * result lookup list to correlate MenuItem with details */ // private void popupButtons(Menu menu, Vector l, Vector r) { // Object ob, ob2; // int i; // Vector m; // Menu submenu; // MenuItem mi; // for (i = 0; i < l.size(); i++) { // m = (Vector)l.elementAt(i); // ob = m.elementAt(0); // ob2 = m.elementAt(1); // if (ob2 instanceof Symbol) { // mi = new MenuItem((String)ob); // m.setElementAt(mi, 0); // // replace first element with MenuItem // menu.add(mi); // // save for use by jmapFrame event handler // r.addElement(m); // } // else if (ob2 instanceof Vector) { // submenu = new Menu((String)ob); // menu.add(submenu); // m.removeElementAt(0); // popupButtons(submenu, m, r); // } // else if (jmap.on && jmap.dbgmode) // System.err.println("Invalid menu item " + m); // } // menu.addActionListener(f); // } // // got a response from our mcmd request // private void mcmd(String a) { // // f.status(a); // show message in status line // if ((v.size() > 4) && // (v.elementAt(1).toString().equals("host"))) { // String b = v.elementAt(5).toString(); // /* // if (b.equals("Up")) // new jmapSoundPlayer(f.jmap, "up.au"); // else if (b.equals("not")) // new jmapSoundPlayer(f.jmap, "noanswer.au"); // */ // } // } }