package org.filemq; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; public class FmqConfig { private String name; // Property name if any private String value; // Property value, if any @SuppressWarnings ("unused") private FmqConfig child, // First child if any next, // Next sibling if any parent; // Parent if any // -------------------------------------------------------------------------- // Constructor // // Optionally attach new config to parent config, as first or next child. public FmqConfig (final String name, FmqConfig parent) { setName (name); if (parent != null) { if (parent.child != null) { // Attach as last child of parent FmqConfig last = parent.child; while (last.next != null) last = last.next; last.next = this; } else // Attach as first child of parent parent.child = this; } this.parent = parent; } // -------------------------------------------------------------------------- // Destructor public void destroy () { // Destroy all children and siblings recursively if (child != null) child.destroy (); if (next != null) next.destroy (); } // -------------------------------------------------------------------------- // Return name of config item public String name () { return name; } // -------------------------------------------------------------------------- // Set config item name, name may be NULL public void setName (final String name) { this.name = name; } // -------------------------------------------------------------------------- // Return value of config item public String value () { return value; } // -------------------------------------------------------------------------- // Set value of config item public void setValue (final String value) { this.value = value; } // -------------------------------------------------------------------------- // Set value of config item via printf format public void formatValue (final String format, Object ... args) { this.value = String.format (format, args); } // -------------------------------------------------------------------------- // Find the first child of a config item, if any public FmqConfig child () { return child; } // -------------------------------------------------------------------------- // Find the next sibling of a config item, if any public FmqConfig next () { return next; } // -------------------------------------------------------------------------- // Find a config item along a path public FmqConfig locate (final String path) { // Check length of next path segment int slash = path.indexOf ('/'); int length = path.length (); if (slash >= 0) length = slash; String base = path.substring (0, length); // Find matching name starting at first child of root FmqConfig child = this.child; while (child != null) { if (child.name.equals (base)) { if (slash > 0) // Look deeper return child.locate (path.substring (slash + 1)); else return child; } child = child.next; } return null; } // -------------------------------------------------------------------------- // Resolve a configuration path into a string value public String resolve (final String path, final String defaultValue) { FmqConfig item = locate (path); if (item != null) return item.value (); else return defaultValue; } public void setPath (final String path, final String value) { // Check length of next path segment int slash = path.indexOf ('/'); int length = path.length (); if (slash >= 0) length = slash; // Find or create items starting at first child of root FmqConfig child_ = this.child; while (child_ != null) { if (child_.name.startsWith (path.substring (0, length))) { // This segment exists if (slash >= 0) // Recurse to next level child_.setPath (path.substring (slash + 1), value); else child_.setValue (value); return; } child_ = child_.next; } // This segment doesn't exist, create it child_ = new FmqConfig (path.substring (0, length), this); if (slash >= 0) // Recurse down further child_.setPath (path.substring (slash + 1), value); else child_.setValue (value); } // -------------------------------------------------------------------------- // Finds the latest node at the specified depth, where 0 is the root. If no // such node exists, returns NULL. public FmqConfig depthAt (int level) { FmqConfig self = this; while (level > 0) { if (self.child != null) { self = self.child; while (self.next != null) self = self.next; level--; } else return null; } return self; } // -------------------------------------------------------------------------- // Load a config item tree from a ZPL file (http://rfc.zeromq.org/spec:4/) // // Here is an example ZPL stream and corresponding config structure: // // context // iothreads = 1 // verbose = 1 # Ask for a trace // main // type = zqueue # ZMQ_DEVICE type // frontend // option // hwm = 1000 // swap = 25000000 # 25MB // bind = 'inproc://addr1' // bind = 'ipc://addr2' // backend // bind = inproc://addr3 // // root Down = child // | Across = next // v // context-->main // | | // | v // | type=queue-->frontend-->backend // | | | // | | v // | | bind=inproc://addr3 // | v // | option-->bind=inproc://addr1-->bind=ipc://addr2 // | | // | v // | hwm=1000-->swap=25000000 // v // iothreads=1-->verbose=false /* ========================================================================= ZPL parser functions =========================================================================*/ private static char charAt (String value, int at) { if (at >= 0 && at < value.length ()) return value.charAt (at); return 0; } // Count and verify indentation level, -1 means a syntax error // private static int collectLevel (String [] pstart, int lineno) { int pos = 0; String start = pstart [0]; char readptr = charAt (start, pos); while (readptr == ' ') readptr = charAt (start, ++pos); int level = pos / 4; if (level * 4 != pos) { System.err.printf ("E: (%d) indent 4 spaces at once\n", lineno); level = -1; } pstart [0] = start.substring (pos); return level; } // Collect property name // private static String collectName (String [] pstart, int lineno) { int pos = 0; String start = pstart [0]; char readptr = charAt (start, pos); while (Character.isDigit (readptr) || Character.isLetter (readptr) || readptr == '/') readptr = charAt (start, ++pos); String name = start.substring (0, pos); pstart [0] = start.substring (pos); if (pos > 0 && (name.startsWith ("/") || name.endsWith ("/"))) { System.err.printf ("E: (%d) '/' not valid at name start or end\n", lineno); } return name; } // Checks there's no junk after value on line, returns 0 if OK else -1. // private static int verifyEoln (String start, int lineno) { int pos = 0; char readptr = charAt (start, pos); while (readptr > 0) { if (Character.isWhitespace (readptr)) readptr = charAt (start, ++pos); else if (readptr == '#') break; else { System.err.printf ("E: (%d) invalid syntax '%s'\n", lineno, start.substring (pos)); return -1; } } return 0; } // Returns value for name, or "" - if syntax error, returns NULL. // private static String collectValue (String [] pstart, int lineno) { int pos = 0; String value = null; String start = pstart [0]; char readptr = charAt (start, pos); int rc = 0; while (Character.isWhitespace (readptr)) readptr = charAt (start, ++pos); if (readptr == '=') { readptr = charAt (start, ++pos); while (Character.isWhitespace (readptr)) readptr = charAt (start, ++pos);; // If value starts with quote or apost, collect it if (readptr == '"' || readptr == '\'') { int endquote = start.indexOf (readptr, pos+1); if (endquote >= 0) { int valueLength = endquote - pos - 1; value = start.substring (pos + 1, valueLength); rc = verifyEoln (start.substring (endquote + 1), lineno); } else { System.err.printf ("E: (%d) missing %c\n", lineno, readptr); rc = -1; } } else { // Collect unquoted value up to comment int comment = start.indexOf ('#', pos); if (comment >= 0) { while (Character.isWhitespace (start.charAt (comment -1))) comment--; value = start.substring (pos, comment); } else value = start.substring (pos); } } else { value = ""; rc = verifyEoln (start.substring (pos), lineno); } // If we had an error, drop value and return NULL if (rc < 0) value = null; return value; } public static FmqConfig load (String configFile) { File file = new File (configFile); if (!file.exists ()) return null; // File not found, or unreadable // Prepare new fmq_config_t structure FmqConfig self = new FmqConfig ("root", null); // Parse the file line by line String curLine; boolean valid = true; int lineno = 0; BufferedReader in = null; try { in = new BufferedReader (new FileReader (file)); while ((curLine = in.readLine ()) != null) { // Trim line int length = curLine.length (); while (Character.isWhitespace (charAt (curLine, length - 1))) --length; curLine = curLine.substring (0, length); // Collect indentation level and name, if any lineno++; String [] scanner = { curLine }; int level = collectLevel (scanner, lineno); if (level == -1) { valid = false; break; } String name = collectName (scanner, lineno); if (name == null) { valid = false; break; } // If name is not empty, collect property value if (!name.isEmpty ()) { String value = collectValue (scanner, lineno); if (value == null) valid = false; else { // Navigate to parent for this element FmqConfig parent = self.depthAt (level); if (parent != null) { FmqConfig item = new FmqConfig (name, parent); item.value = value; } else { System.err.printf ("E: (%d) indentation error\n", lineno); valid = false; } } } else if (verifyEoln (scanner [0], lineno) < 0) valid = false; if (!valid) break; } } catch (IOException e) { valid = false; } finally { try { in.close (); } catch (IOException e) { e.printStackTrace(); } } // Either the whole ZPL file is valid or none of it is if (!valid) { self.destroy (); self = null; } return self; } }