From c887a49f8c00430245c71a0c11e5b0efcf27b251 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Tue, 27 Nov 2012 08:40:17 -0600 Subject: [PATCH] Added LightOptionParser, NullOutputStream, bugfixes. LightOptionParser ----------------- Lightweight, fairly featureful option parsing mechanism idiomatic to Groovy. Takes in a map of parameter definitions and returns a map of option values and arguments. NullOutputStream ---------------- Ignores all data sent its way. ParameterizedSocket ------------------- Added trace and debug logging to the message handling code. SmartConfig ----------- Fixed a bug where a property lookup on a non-existent property with no default given caused a null pointer exception trying to resolve the class of the default value. --- project.properties | 6 +- .../jdbernard/net/ParameterizedSocket.java | 13 ++- .../jdbernard/util/LightOptionParser.groovy | 86 +++++++++++++++++++ .../com/jdbernard/util/NullOutputStream.java | 13 +++ .../com/jdbernard/util/SmartConfig.groovy | 2 +- 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 src/main/com/jdbernard/util/LightOptionParser.groovy create mode 100644 src/main/com/jdbernard/util/NullOutputStream.java diff --git a/project.properties b/project.properties index c6373ed..3e06a5c 100644 --- a/project.properties +++ b/project.properties @@ -1,6 +1,6 @@ -#Thu, 22 Nov 2012 14:39:26 -0600 +#Tue, 27 Nov 2012 08:38:33 -0600 name=jdb-util -version=1.7 +version=1.8 lib.local=true -build.number=16 +build.number=2 diff --git a/src/main/com/jdbernard/net/ParameterizedSocket.java b/src/main/com/jdbernard/net/ParameterizedSocket.java index 9e0d514..8946d00 100644 --- a/src/main/com/jdbernard/net/ParameterizedSocket.java +++ b/src/main/com/jdbernard/net/ParameterizedSocket.java @@ -12,6 +12,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; public class ParameterizedSocket { @@ -50,6 +52,7 @@ public class ParameterizedSocket { private Socket socket; private byte[] buffer; private Charset charset; + private Logger log = LoggerFactory.getLogger(getClass()); public ParameterizedSocket(Socket socket, int bufferSize) { this.socket = socket; @@ -58,7 +61,8 @@ public class ParameterizedSocket { public ParameterizedSocket(String ipAddress, int port, int bufferSize) throws UnknownHostException, IOException { - this(new Socket(ipAddress, port), bufferSize); } + this(new Socket(ipAddress, port), bufferSize); + log.trace("Opened a socket to {}:{}", ipAddress, port); } public ParameterizedSocket(Socket socket) throws UnknownHostException, IOException { @@ -76,6 +80,7 @@ public class ParameterizedSocket { public void writeMessage(Message message) throws IOException { if (message == null || message.parts.size() == 0) return; + log.debug("Writing a message: [{}].", message); byte[] messageBytes = formatMessage(message); socket.getOutputStream().write(messageBytes); } @@ -83,6 +88,8 @@ public class ParameterizedSocket { List messageList = new ArrayList(); Map namedParameters = new HashMap(); + log.trace("Reading a message."); + if (socket.getInputStream().read() != START_TOKEN_BYTE) { byte[] errMsg = formatMessage("ERROR", "Invalid command (expected START_TOKEN)."); socket.getOutputStream().write(errMsg); } @@ -135,7 +142,9 @@ public class ParameterizedSocket { else namedParameters.put(paramName, new String(buffer, 0, bufIdx, charset)); } - return new Message(messageList, namedParameters); } + Message message = new Message(messageList, namedParameters); + log.debug("Message read: [{}].", message); + return message; } protected byte[] formatMessage(String... parts) { return formatMessage(new Message(parts)); } diff --git a/src/main/com/jdbernard/util/LightOptionParser.groovy b/src/main/com/jdbernard/util/LightOptionParser.groovy new file mode 100644 index 0000000..abbd659 --- /dev/null +++ b/src/main/com/jdbernard/util/LightOptionParser.groovy @@ -0,0 +1,86 @@ +/** + * # Light Option Parser + * @author Jonathan Bernard (jdbernard@gmail.com) + * + * LightOptionParser parses command-line style options. + * TODO: complete docs. + */ +package com.jdbernard.util + +public class LightOptionParser { + + public static def parseOptions(def optionDefinitions, List args) { + + def returnOpts = [:] + def foundOpts = [:] + def optionArgIndices = [] + + /// Find all the options. + args.eachWithIndex { arg, idx -> + if (arg.startsWith('--')) foundOpts[arg.substring(2)] = [idx: idx] + else if (arg.startsWith('-')) foundOpts[arg.substring(1)] = [idx: idx] } + + /// Look for option arguments. + foundOpts.each { foundName, optInfo -> + + def retVal + + /// Find the definition for this option. + def optDef = optionDefinitions.find { + it.key == foundName || it.value.longName == foundName } + def optName = optDef.key + optDef = optDef.value + + if (!optDef) throw new IllegalArgumentException( + "Unrecognized option: '${args[optInfo.idx]}.") + + /// Remember the option index for later. + optionArgIndices << optInfo.idx + + /// If there are no arguments, this is a flag. + if ((optDef.arguments ?: 0) == 0) retVal = true + + /// Otherwise, read in the arguments + if (optDef.arguments && optDef.arguments > 0) { + + /// Not enough arguments left + if ((optInfo.idx + optDef.arguments) >= args.size()) { + throw new Exception("Option '${args[optInfo.idx]}' " + + "expects ${optDef.arguments} arguments.") } + + int firstArgIdx = optInfo.idx + 1 + + /// Case of only one argument + if (optDef.arguments == 1) + retVal = args[firstArgIdx] + /// Case of multiple arguments + else retVal = args[firstArgIdx..<(firstArgIdx + optDef.arguments)] + + /// Remember all the option argument indices for later. + (firstArgIdx..<(firstArgIdx + optDef.arguments)).each { + optionArgIndices << it }} + + /// Store the value in the returnOpts map + returnOpts[optName] = retVal + if (optDef.longName) returnOpts[optDef.longName] = retVal } + + /// Check that all required options have been found. + optionDefinitions.each { optName, optDef -> + /// If this is a required field + if (optDef.required && + /// and it has not been found, by either it's short or long name. + !(returnOpts[optName] || + (optDef.longName && returnOpts[longName]))) + + throw new Exception("Missing required option: '-${optName}'.") } + + /// Remove all the option arguments from the args list and return just + /// the non-option arguments. + optionArgIndices.sort().reverse().each { args.remove(it) } + + //optionArgIndices = optionArgIndices.collect { args[it] } + //args.removeAll(optionArgIndices) + + returnOpts.args = args + return returnOpts } +} diff --git a/src/main/com/jdbernard/util/NullOutputStream.java b/src/main/com/jdbernard/util/NullOutputStream.java new file mode 100644 index 0000000..59f708f --- /dev/null +++ b/src/main/com/jdbernard/util/NullOutputStream.java @@ -0,0 +1,13 @@ +package com.jdbernard.util; + +import java.io.OutputStream; + +public class NullOutputStream extends OutputStream { + + public NullOutputStream() {} + public void close() { } + public void flush() { } + public void write(int b) { } + public void write(byte[] b) { } + public void write(byte[] b, int offset, int length) { } +} diff --git a/src/main/com/jdbernard/util/SmartConfig.groovy b/src/main/com/jdbernard/util/SmartConfig.groovy index 9e8e61e..1f61c14 100644 --- a/src/main/com/jdbernard/util/SmartConfig.groovy +++ b/src/main/com/jdbernard/util/SmartConfig.groovy @@ -71,7 +71,7 @@ public class SmartConfig { log.trace("No known interpretation, casting to type of " + "default argument.") - val = val.asType(defVal.class) + val = val.asType(defVal?.class ?: String) } }