http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ProcBuilder.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ProcBuilder.java 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ProcBuilder.java
new file mode 100755
index 0000000..3050209
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ProcBuilder.java
@@ -0,0 +1,382 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.logging.*;
+
+import com.ibm.juno.core.utils.IOPipe.LineProcessor;
+
+/**
+ * Utility class for running operating system processes.
+ * <p>
+ * Similar to {@link java.lang.ProcessBuilder} but with additional features.
+ *
+ * @author James Bognar ([email protected])
+ */
+@SuppressWarnings("hiding")
+public class ProcBuilder {
+
+       private java.lang.ProcessBuilder pb = new java.lang.ProcessBuilder();
+       private TeeWriter outWriters = new TeeWriter(), logWriters = new 
TeeWriter();
+       private LineProcessor lp;
+       private Process p;
+       private int maxExitStatus = 0;
+       private boolean byLines;
+       private String divider = 
"--------------------------------------------------------------------------------";
+
+       /**
+        * Creates a process builder with the specified arguments.
+        * Equivalent to calling 
<code>ProcessBuilder.create().command(args);</code>
+        *
+        * @param args The command-line arguments.
+        * @return A new process builder.
+        */
+       public static ProcBuilder create(Object...args) {
+               return new ProcBuilder().command(args);
+       }
+
+       /**
+        * Creates an empty process builder.
+        *
+        * @return A new process builder.
+        */
+       public static ProcBuilder create() {
+               return new ProcBuilder().command();
+       }
+
+       /**
+        * Command arguments.
+        * Arguments can be collections or arrays and will be automatically 
expanded.
+        *
+        * @param args The command-line arguments.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder command(Object...args) {
+               return commandIf(ANY, args);
+       }
+
+       /**
+        * Command arguments if the specified matcher matches.
+        * Can be used for specifying os-specific commands.
+        * Example:
+        * <p class='bcode'>
+        *      ProcessBuilder pb = ProcessBuilder
+        *              .create()
+        *              .commandIf(<jsf>WINDOWS</jsf>, <js>"cmd /c dir"</js>)
+        *              .commandIf(<jsf>UNIX</jsf>, <js>"bash -c ls"</js>)
+        *              .merge()
+        *              .execute();
+        * </p>
+        *
+        * @param m The matcher.
+        * @param args The command line arguments if matcher matches.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder commandIf(Matcher m, Object...args) {
+               if (m.matches())
+                       pb.command(toList(args));
+               return this;
+       }
+
+       /**
+        * Append to the command arguments.
+        * Arguments can be collections or arrays and will be automatically 
expanded.
+        *
+        * @param args The command-line arguments.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder append(Object...args) {
+               return appendIf(ANY, args);
+       }
+
+       /**
+        * Append to the command arguments if the specified matcher matches.
+        * Arguments can be collections or arrays and will be automatically 
expanded.
+        *
+        * @param m The matcher.
+        * @param args The command line arguments if matcher matches.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder appendIf(Matcher m, Object...args) {
+               if (m.matches())
+                       pb.command().addAll(toList(args));
+               return this;
+       }
+
+       /**
+        * Merge STDOUT and STDERR into a single stream.
+        *
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder merge() {
+               pb.redirectErrorStream(true);
+               return this;
+       }
+
+       /**
+        * Use by-lines mode.
+        * Flushes output after every line of input.
+        *
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder byLines() {
+               this.byLines = true;
+               return this;
+       }
+
+       /**
+        * Pipe output to the specified writer.
+        * The method can be called multiple times to write to multiple writers.
+        *
+        * @param w The writer to pipe to.
+        * @param close Close the writer afterwards.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder pipeTo(Writer w, boolean close) {
+               this.outWriters.add(w, close);
+               return this;
+       }
+
+       /**
+        * Pipe output to the specified writer, but don't close the writer.
+        *
+        * @param w The writer to pipe to.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder pipeTo(Writer w) {
+               return pipeTo(w, false);
+       }
+
+       /**
+        * Pipe output to the specified writer, including the command and 
return code.
+        * The method can be called multiple times to write to multiple writers.
+        *
+        * @param w The writer to pipe to.
+        * @param close Close the writer afterwards.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder logTo(Writer w, boolean close) {
+               this.logWriters.add(w, close);
+               this.outWriters.add(w, close);
+               return this;
+       }
+
+       /**
+        * Pipe output to the specified writer, including the command and 
return code.
+        * The method can be called multiple times to write to multiple writers.
+        * Don't close the writer afterwards.
+        *
+        * @param w The writer to pipe to.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder logTo(Writer w) {
+               return logTo(w, false);
+       }
+
+       /**
+        * Pipe output to the specified writer, including the command and 
return code.
+        * The method can be called multiple times to write to multiple writers.
+        *
+        * @param level The log level.
+        * @param logger The logger to log to.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder logTo(final Level level, final Logger logger) {
+               if (logger.isLoggable(level)) {
+                       logTo(new StringWriter() {
+                               private boolean isClosed;  // Prevents messages 
from being written twice.
+                               @Override /* Writer */
+                               public void close() {
+                                       if (! isClosed)
+                                               logger.log(level, 
this.toString());
+                                       isClosed = true;
+                               }
+                       }, true);
+               }
+               return this;
+       }
+
+       /**
+        * Line processor to use to process/convert lines of output returned by 
the process.
+        *
+        * @param lp The new line processor.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder lp(LineProcessor lp) {
+               this.lp = lp;
+               return this;
+       }
+
+       /**
+        * Append the specified environment variables to the process.
+        *
+        * @param env The new set of environment variables.
+        * @return This object (for method chaining).
+        */
+       @SuppressWarnings({"rawtypes", "unchecked"})
+       public ProcBuilder env(Map env) {
+               if (env != null)
+               for (Map.Entry e : (Set<Map.Entry>)env.entrySet())
+                       environment(e.getKey().toString(), e.getValue() == null 
? null : e.getValue().toString());
+               return this;
+       }
+
+       /**
+        * Append the specified environment variable.
+        *
+        * @param key The environment variable name.
+        * @param val The environment variable value.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder environment(String key, String val) {
+               pb.environment().put(key, val);
+               return this;
+       }
+
+       /**
+        * Sets the directory where the command will be executed.
+        *
+        * @param directory The directory.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder directory(File directory) {
+               pb.directory(directory);
+               return this;
+       }
+
+       /**
+        * Sets the maximum allowed return code on the process call.
+        * If the return code exceeds this value, an IOException is returned on 
the {@link #run()} command.
+        * The default value is '0'.
+        *
+        * @param maxExitStatus The maximum exit status.
+        * @return This object (for method chaining).
+        */
+       public ProcBuilder maxExitStatus(int maxExitStatus) {
+               this.maxExitStatus = maxExitStatus;
+               return this;
+       }
+
+       /**
+        * Run this command and pipes the output to the specified writer or 
output stream.
+        *
+        * @return The exit code from the process.
+        * @throws IOException
+        * @throws InterruptedException
+        */
+       public int run() throws IOException, InterruptedException {
+               if (pb.command().size() == 0)
+                       throw new IOException("No command specified in 
ProcBuilder.");
+               try {
+                       logWriters.append(divider).append("\n").flush();
+                       logWriters.append(StringUtils.join(pb.command(), " 
")).append("\n").flush();
+                               p = pb.start();
+                       IOPipe.create(p.getInputStream(), 
outWriters).lineProcessor(lp).byLines(byLines).run();
+                       int rc = p.waitFor();
+                       logWriters.append("Exit: 
").append(String.valueOf(p.exitValue())).append("\n").flush();
+                       if (rc > maxExitStatus)
+                               throw new IOException("Return code "+rc+" from 
command " + StringUtils.join(pb.command(), " "));
+                       return rc;
+               } finally {
+                       close();
+               }
+       }
+
+       /**
+        * Run this command and returns the output as a simple string.
+        *
+        * @return The output from the command.
+        * @throws IOException
+        * @throws InterruptedException
+        */
+       public String getOutput() throws IOException, InterruptedException {
+               StringWriter sw = new StringWriter();
+               pipeTo(sw).run();
+               return sw.toString();
+       }
+
+       /**
+        * Returns the output from this process as a {@link Scanner}.
+        *
+        * @return The output from the process as a Scanner object.
+        * @throws IOException
+        * @throws InterruptedException
+        */
+       public Scanner getScanner() throws IOException, InterruptedException {
+               StringWriter sw = new StringWriter();
+               pipeTo(sw, true);
+               run();
+               return new Scanner(sw.toString());
+       }
+
+       /**
+        * Destroys the underlying process.
+        * This method is only needed if the {@link #getScanner()} method was 
used.
+        */
+       private void close() {
+               IOUtils.closeQuietly(logWriters, outWriters);
+               if (p != null)
+                       p.destroy();
+       }
+
+       /**
+        * Specifies interface for defining OS-specific commands.
+        */
+       public abstract static class Matcher {
+               abstract boolean matches();
+       }
+
+       private static String OS = System.getProperty("os.name").toLowerCase();
+
+       /** Operating system matcher: Any operating system. */
+       public final static Matcher ANY = new Matcher() {
+                       @Override boolean matches() {
+                               return true;
+                       }
+       };
+
+       /** Operating system matcher: Any Windows system. */
+       public final static Matcher WINDOWS = new Matcher() {
+                       @Override boolean matches() {
+                               return OS.indexOf("win") >= 0;
+                       }
+       };
+
+       /** Operating system matcher: Any Mac system. */
+       public final static Matcher MAC = new Matcher() {
+                       @Override boolean matches() {
+                               return OS.indexOf("mac") >= 0;
+                       }
+       };
+
+       /** Operating system matcher: Any Unix or Linux system. */
+       public final static Matcher UNIX = new Matcher() {
+                       @Override boolean matches() {
+                               return OS.indexOf("nix") >= 0 || 
OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0;
+                       }
+       };
+
+       private static List<String> toList(Object...args) {
+               List<String> l = new LinkedList<String>();
+               for (Object o : args) {
+                       if (o.getClass().isArray())
+                               for (int i = 0; i < Array.getLength(o); i++)
+                                       l.add(Array.get(o, i).toString());
+                       else if (o instanceof Collection)
+                               for (Object o2 : (Collection<?>)o)
+                                       l.add(o2.toString());
+                       else
+                               l.add(o.toString());
+               }
+               return l;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.class
new file mode 100755
index 0000000..2088152
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.java
new file mode 100755
index 0000000..b8ddc56
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/ReflectionUtils.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import static com.ibm.juno.core.utils.CollectionUtils.*;
+
+import java.io.*;
+import java.lang.annotation.*;
+import java.util.*;
+
+/**
+ * Reflection utilities.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class ReflectionUtils {
+
+       /**
+        * Similar to {@link Class#getAnnotation(Class)} except also searches 
annotations on interfaces.
+        *
+        * @param <T> The annotation class type.
+        * @param a The annotation class.
+        * @param c The annotated class.
+        * @return The annotation, or <jk>null</jk> if not found.
+        */
+       public static <T extends Annotation> T getAnnotation(Class<T> a, 
Class<?> c) {
+               if (c == null)
+                       return null;
+
+               T t = getDeclaredAnnotation(a, c);
+               if (t != null)
+                       return t;
+
+               t = getAnnotation(a, c.getSuperclass());
+               if (t != null)
+                       return t;
+
+               for (Class<?> c2 : c.getInterfaces()) {
+                       t = getAnnotation(a, c2);
+                       if (t != null)
+                               return t;
+               }
+               return null;
+       }
+
+       /**
+        * Returns the specified annotation only if it's been declared on the 
specified class.
+        * <p>
+        *      More efficient than calling {@link Class#getAnnotation(Class)} 
since it doesn't
+        *      recursively look for the class up the parent chain.
+        *
+        * @param <T> The annotation class type.
+        * @param a The annotation class.
+        * @param c The annotated class.
+        * @return The annotation, or <jk>null</jk> if not found.
+        */
+       @SuppressWarnings("unchecked")
+       public static <T extends Annotation> T getDeclaredAnnotation(Class<T> 
a, Class<?> c) {
+               for (Annotation a2 : c.getDeclaredAnnotations())
+                       if (a2.annotationType() == a)
+                               return (T)a2;
+               return null;
+       }
+
+       /**
+        * Returns all instances of the specified annotation on the specified 
class.
+        * <p>
+        * Searches all superclasses and superinterfaces.
+        * <p>
+        * Results are ordered child-to-parent.
+        *
+        * @param <T> The annotation class type.
+        * @param a The annotation class type.
+        * @param c The class being searched.
+        * @return The found matches, or an empty array if annotation was not 
found.
+        */
+       public static <T extends Annotation> List<T> findAnnotations(Class<T> 
a, Class<?> c) {
+               List<T> l = new LinkedList<T>();
+               appendAnnotations(a, c, l);
+               return l;
+       }
+
+       /**
+        * Sames as {@link #findAnnotations(Class, Class)} except returns the 
annotations as a map
+        * with the keys being the class on which the annotation was found.
+        * <p>
+        * Results are ordered child-to-parent.
+        *
+        * @param <T> The annotation class type.
+        * @param a The annotation class type.
+        * @param c The class being searched.
+        * @return The found matches, or an empty array if annotation was not 
found.
+        */
+       public static <T extends Annotation> LinkedHashMap<Class<?>,T> 
findAnnotationsMap(Class<T> a, Class<?> c) {
+               LinkedHashMap<Class<?>,T> m = new LinkedHashMap<Class<?>,T>();
+               findAnnotationsMap(a, c, m);
+               return m;
+       }
+
+       private static <T extends Annotation> void findAnnotationsMap(Class<T> 
a, Class<?> c, Map<Class<?>,T> m) {
+               if (c == null)
+                       return;
+
+               T t = getDeclaredAnnotation(a, c);
+               if (t != null)
+                       m.put(c, t);
+
+               findAnnotationsMap(a, c.getSuperclass(), m);
+
+               for (Class<?> c2 : c.getInterfaces())
+                       findAnnotationsMap(a, c2, m);
+       }
+
+       /**
+        * Finds and appends the specified annotation on the specified class 
and superclasses/interfaces to the specified list.
+        *
+        * @param a The annotation.
+        * @param c The class.
+        * @param l The list of annotations.
+        */
+       public static <T extends Annotation> void appendAnnotations(Class<T> a, 
Class<?> c, List<T> l) {
+               if (c == null)
+                       return;
+
+               addIfNotNull(l, getDeclaredAnnotation(a, c));
+
+               if (c.getPackage() != null)
+                       addIfNotNull(l, c.getPackage().getAnnotation(a));
+
+               appendAnnotations(a, c.getSuperclass(), l);
+
+               for (Class<?> c2 : c.getInterfaces())
+                       appendAnnotations(a, c2, l);
+       }
+
+       /**
+        * Similar to {@link Class#getResourceAsStream(String)} except looks up 
the
+        * parent hierarchy for the existence of the specified resource.
+        *
+        * @param c The class to return the resource on.
+        * @param name The resource name.
+        * @return An input stream on the specified resource, or <jk>null</jk> 
if the resource could not be found.
+        */
+       public static InputStream getResource(Class<?> c, String name) {
+               while (c != null) {
+                       InputStream is = c.getResourceAsStream(name);
+                       if (is != null)
+                               return is;
+                       c = c.getSuperclass();
+               }
+               return null;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.class
new file mode 100755
index 0000000..5509806
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.java
new file mode 100755
index 0000000..a19a76d
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceBundle.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import java.text.*;
+import java.util.*;
+
+/**
+ * Wraps a {@link ResourceBundle} to gracefully handle missing bundles and 
entries.
+ * <p>
+ * If the bundle isn't found, <code>getString(key)</code> returns 
<js>"{!!key}"</js>.
+ * <p>
+ * If the key isn't found, <code>getString(key)</code> returns 
<js>"{!key}"</js>.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class SafeResourceBundle extends ResourceBundle {
+
+       private ResourceBundle rb;
+       private String className = null;
+       Map<String,String> classPrefixedKeys = new HashMap<String,String>();
+
+       SafeResourceBundle() {}
+
+       /**
+        * Constructor.
+        *
+        * @param rb The resource bundle to wrap.  Can be <jk>null</jk> to 
indicate a missing bundle.
+        * @param forClass The class using this resource bundle.
+        */
+       public SafeResourceBundle(ResourceBundle rb, Class<?> forClass) {
+               this.rb = rb;
+               if (forClass != null)
+                       className = forClass.getSimpleName();
+               if (rb != null) {
+                       String c = className + '.';
+                       for (Enumeration<String> e = getKeys(); 
e.hasMoreElements();) {
+                               String key = e.nextElement();
+                               if (key.startsWith(c))
+                                       
classPrefixedKeys.put(key.substring(className.length() + 1), key);
+                       }
+               }
+       }
+
+       @Override /* ResourceBundle */
+       public boolean containsKey(String key) {
+               return rb != null && (rb.containsKey(key) || 
classPrefixedKeys.containsKey(key));
+       }
+
+       /**
+        * Similar to {@link ResourceBundle#getString(String)} except allows 
you to pass in {@link MessageFormat} objects.
+        *
+        * @param key The resource bundle key.
+        * @param args Optional variable replacement arguments.
+        * @return The resolved value.  Never <jk>null</jk>.  <js>"{!!key}"</j> 
if the bundle is missing.  <js>"{!key}"</j> if the key is missing.
+        */
+       public String getString(String key, Object...args) {
+               if (rb == null)
+                       return "{!!"+key+"}";
+               if (! containsKey(key))
+                       return "{!" + key + "}";
+               String val = getString(key);
+               if (args.length > 0)
+                       return MessageFormat.format(val, args);
+               return val;
+       }
+
+       /**
+        * Looks for all the specified keys in the resource bundle and returns 
the first value that exists.
+        *
+        * @param keys
+        * @return The resolved value, or <jk>null</jk> if no value is found or 
the resource bundle is missing.
+        */
+       public String findFirstString(String...keys) {
+               if (rb == null)
+                       return null;
+               for (String k : keys) {
+                       if (containsKey(k))
+                               return getString(k);
+               }
+               return null;
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override /* ResourceBundle */
+       public Set<String> keySet() {
+               if (rb == null)
+                       return Collections.emptySet();
+               return new MultiSet<String>(rb.keySet(), 
classPrefixedKeys.keySet()) ;
+       }
+
+       /**
+        * Returns all keys in this resource bundle with the specified prefix.
+        *
+        * @param prefix The prefix.
+        * @return The set of all keys in the resource bundle with the prefix.
+        */
+       public Set<String> keySet(String prefix) {
+               Set<String> set = new HashSet<String>();
+               for (String s : keySet()) {
+                       if (s.equals(prefix) || (s.startsWith(prefix) && 
s.charAt(prefix.length()) == '.'))
+                               set.add(s);
+               }
+               return set;
+       }
+
+       @Override /* ResourceBundle */
+       public Enumeration<String> getKeys() {
+               if (rb == null)
+                       return new Vector<String>(0).elements();
+               return rb.getKeys();
+       }
+
+       @Override /* ResourceBundle */
+       protected Object handleGetObject(String key) {
+               if (rb == null)
+                       return "{!!"+key+"}";
+               try {
+                       if (classPrefixedKeys.containsKey(key))
+                               key = classPrefixedKeys.get(key);
+                       return rb.getObject(key);
+               } catch (Exception e) {
+                       return "{!"+key+"}";
+               }
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.class
new file mode 100755
index 0000000..5e239a4
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.java
new file mode 100755
index 0000000..fcf4ccc
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SafeResourceMultiBundle.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import java.util.*;
+
+/**
+ * A collection of {@link SafeResourceBundle} objects.
+ * Allows servlets to define resource bundles for different classes in the 
class hierarchy.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class SafeResourceMultiBundle extends SafeResourceBundle {
+
+       private List<SafeResourceBundle> bundles = new 
ArrayList<SafeResourceBundle>();
+
+       /**
+        * Constructor.
+        *
+        * @param bundles The resource bundles to wrap.  Can be <jk>null</jk> 
to indicate a missing bundle.
+        */
+       public SafeResourceMultiBundle(Collection<SafeResourceBundle> bundles) {
+               this.bundles.addAll(bundles);
+       }
+
+       @Override /* SafeResourceBundle */
+       public String findFirstString(String...keys) {
+               for (String key : keys)
+                       for (SafeResourceBundle srb : bundles)
+                               if (srb.containsKey(key))
+                                       return srb.getString(key);
+               return null;
+       }
+
+       @Override /* ResourceBundle */
+       public boolean containsKey(String key) {
+               for (SafeResourceBundle srb : bundles)
+                       if (srb.containsKey(key))
+                               return true;
+               return false;
+       }
+
+       @Override /* SafeResourceBundle */
+       public String getString(String key, Object...args) {
+               for (SafeResourceBundle srb : bundles)
+                       if (srb.containsKey(key))
+                               return srb.getString(key, args);
+               return "{!" + key + "}";
+       }
+
+       @Override /* ResourceBundle */
+       @SuppressWarnings("unchecked")
+       public Set<String> keySet() {
+               MultiSet<String> s = new MultiSet<String>();
+               for (SafeResourceBundle rb : bundles)
+                       s.append(rb.keySet());
+               return s;
+       }
+
+       @Override /* ResourceBundle */
+       @SuppressWarnings("unchecked")
+       public Enumeration<String> getKeys() {
+               MultiSet<String> s = new MultiSet<String>();
+               for (SafeResourceBundle rb : bundles)
+                       s.append(rb.keySet());
+               return s.enumerator();
+       }
+
+       @Override /* ResourceBundle */
+       protected Object handleGetObject(String key) {
+               for (SafeResourceBundle srb : bundles)
+                       if (srb.containsKey(key))
+                               return srb.handleGetObject(key);
+               return "{!"+key+"}";
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$1.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$1.class 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$1.class
new file mode 100755
index 0000000..e935d78
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$1.class 
differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$SimpleMapEntry.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$SimpleMapEntry.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$SimpleMapEntry.class
new file mode 100755
index 0000000..c515bc1
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap$SimpleMapEntry.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.class 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.class
new file mode 100755
index 0000000..03c32ce
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.class 
differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.java 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.java
new file mode 100755
index 0000000..21bd047
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/SimpleMap.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import static com.ibm.juno.core.utils.ArrayUtils.*;
+import static com.ibm.juno.core.utils.ThrowableUtils.*;
+
+import java.text.*;
+import java.util.*;
+
+/**
+ * An instance of a <code>Map</code> where the keys and values
+ *     are simple <code>String[]</code> and <code>Object[]</code> arrays.
+ * <p>
+ *     Typically more efficient than <code>HashMaps</code> for small maps 
(e.g. &lt;10 entries).
+ * <p>
+ *     Does not support adding or removing entries.
+ * <p>
+ *     Setting values overwrites the value on the underlying value array.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class SimpleMap extends AbstractMap<String,Object> {
+
+       private final String[] keys;
+       private final Object[] values;
+       private final Map.Entry<String,Object>[] entries;
+
+       /**
+        * Constructor.
+        *
+        * @param keys The map keys.  Must not be <jk>null</jk>.
+        * @param values The map values.  Must not be <jk>null</jk>.
+        */
+       public SimpleMap(String[] keys, Object[] values) {
+               assertFieldNotNull(keys, "keys");
+               assertFieldNotNull(values, "values");
+               if (keys.length != values.length)
+                       illegalArg("keys ''{0}'' and values ''{1}'' array 
lengths differ", keys.length, values.length);
+
+               this.keys = keys;
+               this.values = values;
+               entries = new SimpleMapEntry[keys.length];
+               for (int i = 0; i < keys.length; i++) {
+                       if (keys[i] == null)
+                               illegalArg("Keys array cannot contain a null 
value.");
+                       entries[i] = new SimpleMapEntry(i);
+       }
+       }
+
+       @Override /* Map */
+       public Set<Map.Entry<String,Object>> entrySet() {
+               return asSet(entries);
+       }
+
+       @Override /* Map */
+       public Object get(Object key) {
+               for (int i = 0; i < keys.length; i++)
+                       if (keys[i].equals(key))
+                               return values[i];
+               return null;
+       }
+
+       @Override /* Map */
+       public Set<String> keySet() {
+               return asSet(keys);
+       }
+
+       @Override /* Map */
+       public Object put(String key, Object value) {
+               for (int i = 0; i < keys.length; i++) {
+                       if (keys[i].equals(key)) {
+                               Object v = values[i];
+                               values[i] = value;
+                               return v;
+                       }
+               }
+               throw new IllegalArgumentException(MessageFormat.format("No key 
''{0}'' defined in map", key));
+       }
+
+       private class SimpleMapEntry implements Map.Entry<String,Object> {
+
+               private int index;
+
+               private SimpleMapEntry(int index) {
+                       this.index = index;
+               }
+
+               @Override /* Map.Entry */
+               public String getKey() {
+                       return keys[index];
+               }
+
+               @Override /* Map.Entry */
+               public Object getValue() {
+                       return values[index];
+               }
+
+               @Override /* Map.Entry */
+               public Object setValue(Object val) {
+                       Object v = values[index];
+                       values[index] = val;
+                       return v;
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.class
new file mode 100755
index 0000000..c1f9118
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.java
new file mode 100755
index 0000000..8fffbdb
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringBuilderWriter.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import java.io.*;
+
+/**
+ * Similar to {@link StringWriter}, but uses a {@link StringBuilder} instead 
to avoid synchronization overhead.
+ * <p>
+ * Note that this class is NOT thread safe.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class StringBuilderWriter extends Writer {
+
+       private StringBuilder sb;
+
+       /**
+        * Create a new string writer using the default initial string-builder 
size.
+        */
+       public StringBuilderWriter() {
+               sb = new StringBuilder();
+               lock = null;
+       }
+
+       /**
+        * Create a new string writer using the specified initial 
string-builder size.
+        *
+        * @param initialSize The number of <tt>char</tt> values that will fit 
into this buffer before it is automatically expanded
+        * @throws IllegalArgumentException If <tt>initialSize</tt> is negative
+        */
+       public StringBuilderWriter(int initialSize) {
+               sb = new StringBuilder(initialSize);
+               lock = null;
+       }
+
+       @Override /* Writer */
+       public void write(int c) {
+               sb.append((char) c);
+       }
+
+       @Override /* Writer */
+       public void write(char cbuf[], int start, int length) {
+               sb.append(cbuf, start, length);
+       }
+
+       @Override /* Writer */
+       public void write(String str) {
+               sb.append(str);
+       }
+
+       @Override /* Writer */
+       public void write(String str, int off, int len) {
+               sb.append(str.substring(off, off + len));
+       }
+
+       @Override /* Writer */
+       public StringBuilderWriter append(CharSequence csq) {
+               if (csq == null)
+                       write("null");
+               else
+                       write(csq.toString());
+               return this;
+       }
+
+       @Override /* Writer */
+       public StringBuilderWriter append(CharSequence csq, int start, int end) 
{
+               CharSequence cs = (csq == null ? "null" : csq);
+               write(cs.subSequence(start, end).toString());
+               return this;
+       }
+
+       @Override /* Writer */
+       public StringBuilderWriter append(char c) {
+               write(c);
+               return this;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return sb.toString();
+       }
+
+       @Override /* Writer */
+       public void flush() {}
+
+       @Override /* Writer */
+       public void close() throws IOException {}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.class 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.class
new file mode 100755
index 0000000..82ed33f
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.class 
differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.java 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.java
new file mode 100755
index 0000000..4621af4
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringUtils.java
@@ -0,0 +1,873 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import static com.ibm.juno.core.utils.ThrowableUtils.*;
+
+import java.io.*;
+import java.math.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.util.regex.*;
+
+import javax.xml.bind.*;
+
+import com.ibm.juno.core.parser.*;
+
+/**
+ * Reusable string utility methods.
+ */
+public final class StringUtils {
+
+       private static final AsciiSet numberChars = new 
AsciiSet("-xX.+-#pP0123456789abcdefABCDEF");
+       private static final AsciiSet firstNumberChars = new 
AsciiSet("+-.#0123456789");
+       private static final AsciiSet octChars = new AsciiSet("01234567");
+       private static final AsciiSet decChars = new AsciiSet("0123456789");
+       private static final AsciiSet hexChars = new 
AsciiSet("0123456789abcdefABCDEF");
+
+       // Maps 6-bit nibbles to BASE64 characters.
+       private static final char[] base64m1 = 
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+
+       // Maps BASE64 characters to 6-bit nibbles.
+       private static final byte[] base64m2 = new byte[128];
+       static {
+               for (int i = 0; i < 64; i++)
+                       base64m2[base64m1[i]] = (byte)i;
+       }
+
+       /**
+        * Parses a number from the specified reader stream.
+        *
+        * @param r The reader to parse the string from.
+        * @param type The number type to created. <br>
+        *      Can be any of the following:
+        *      <ul>
+        *              <li> Integer
+        *              <li> Double
+        *              <li> Float
+        *              <li> Long
+        *              <li> Short
+        *              <li> Byte
+        *              <li> BigInteger
+        *              <li> BigDecimal
+        *      </ul>
+        *      If <jk>null</jk>, uses the best guess.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        * @return The parsed number.
+        * @throws ParseException
+        */
+       public static Number parseNumber(ParserReader r, Class<? extends 
Number> type) throws IOException, ParseException {
+               return parseNumber(parseNumberString(r), type);
+       }
+
+       /**
+        * Reads a numeric string from the specified reader.
+        *
+        * @param r The reader to read form.
+        * @return The parsed number string.
+        * @throws IOException
+        */
+       public static String parseNumberString(ParserReader r) throws 
IOException {
+               r.mark();
+               int c = 0;
+               while (true) {
+                       c = r.read();
+                       if (c == -1)
+                               break;
+                       if (! numberChars.contains((char)c)) {
+                               r.unread();
+                               break;
+                       }
+               }
+               return r.getMarked();
+       }
+
+       /**
+        * Parses a number from the specified string.
+        *
+        * @param s The string to parse the number from.
+        * @param type The number type to created. <br>
+        *      Can be any of the following:
+        *      <ul>
+        *              <li> Integer
+        *              <li> Double
+        *              <li> Float
+        *              <li> Long
+        *              <li> Short
+        *              <li> Byte
+        *              <li> BigInteger
+        *              <li> BigDecimal
+        *      </ul>
+        *      If <jk>null</jk>, uses the best guess.
+        * @return The parsed number.
+        * @throws ParseException
+        */
+       public static Number parseNumber(String s, Class<? extends Number> 
type) throws ParseException {
+
+               if (s.isEmpty())
+                       s = "0";
+               if (type == null)
+                       type = Number.class;
+
+               try {
+                       // Determine the data type if it wasn't specified.
+                       boolean isAutoDetect = (type == Number.class);
+                       boolean isDecimal = false;
+                       if (isAutoDetect) {
+                               // If we're auto-detecting, then we use either 
an Integer, Long, or Double depending on how
+                               // long the string is.
+                               // An integer range is -2,147,483,648 to 
2,147,483,647
+                               // An long range is -9,223,372,036,854,775,808 
to +9,223,372,036,854,775,807
+                               isDecimal = isDecimal(s);
+                               if (isDecimal) {
+                                       if (s.length() > 20)
+                                               type = Double.class;
+                                       else if (s.length() >= 10)
+                                       type = Long.class;
+                                       else
+                                               type = Integer.class;
+                               }
+                               else if (isFloat(s))
+                                       type = Double.class;
+                               else
+                                       throw new NumberFormatException(s);
+                       }
+
+                       if (type == Double.class || type == Double.TYPE) {
+                               Double d = Double.valueOf(s);
+                               if (isAutoDetect && (! isDecimal) && d >= 
-Float.MAX_VALUE && d <= Float.MAX_VALUE)
+                                       return d.floatValue();
+                               return d;
+                       }
+                       if (type == Float.class || type == Float.TYPE)
+                               return Float.valueOf(s);
+                       if (type == BigDecimal.class)
+                               return new BigDecimal(s);
+                       if (type == Long.class || type == Long.TYPE || type == 
AtomicLong.class) {
+                               try {
+                               Long l = Long.decode(s);
+                                       if (type == AtomicLong.class)
+                                               return new AtomicLong(l);
+                                       if (isAutoDetect && l >= 
Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
+                                               // This occurs if the string is 
10 characters long but is still a valid integer value.
+                                       return l.intValue();
+                                       }
+                               return l;
+                               } catch (NumberFormatException e) {
+                                       if (isAutoDetect) {
+                                               // This occurs if the string is 
20 characters long but still falls outside the range of a valid long.
+                                               return Double.valueOf(s);
+                                       }
+                                       throw e;
+                               }
+                       }
+                       if (type == Integer.class || type == Integer.TYPE)
+                               return Integer.decode(s);
+                       if (type == Short.class || type == Short.TYPE)
+                               return Short.decode(s);
+                       if (type == Byte.class || type == Byte.TYPE)
+                               return Byte.decode(s);
+                       if (type == BigInteger.class)
+                               return new BigInteger(s);
+                       if (type == AtomicInteger.class)
+                               return new AtomicInteger(Integer.decode(s));
+                       throw new ParseException("Unsupported Number type: 
{0}", type.getName());
+               } catch (NumberFormatException e) {
+                       throw new ParseException("Could not convert string 
''{0}'' to class ''{1}''", s, type.getName()).initCause(e);
+               }
+       }
+
+   private final static Pattern fpRegex = Pattern.compile(
+      
"[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*"
+   );
+
+       /**
+        * Returns <jk>true</jk> if this string can be parsed by {@link 
#parseNumber(String, Class)}.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if this string can be parsed without causing 
an exception.
+        */
+       public static boolean isNumeric(String s) {
+               if (s == null || s.isEmpty())
+                       return false;
+               if (! firstNumberChars.contains(s.charAt(0)))
+                       return false;
+               return isDecimal(s) || isFloat(s);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified string is a floating point 
number.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if the specified string is a floating point 
number.
+        */
+       public static boolean isFloat(String s) {
+               if (s == null || s.isEmpty())
+                       return false;
+               if (! firstNumberChars.contains(s.charAt(0)))
+                       return (s.equals("NaN") || s.equals("Infinity"));
+               int i = 0;
+               int length = s.length();
+               char c = s.charAt(0);
+               if (c == '+' || c == '-')
+                       i++;
+               if (i == length)
+                       return false;
+               c = s.charAt(i++);
+               if (c == '.' || decChars.contains(c)) {
+                       return fpRegex.matcher(s).matches();
+               }
+               return false;
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified string is numeric.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if the specified string is numeric.
+        */
+       public static boolean isDecimal(String s) {
+               if (s == null || s.isEmpty())
+                       return false;
+               if (! firstNumberChars.contains(s.charAt(0)))
+                       return false;
+               int i = 0;
+               int length = s.length();
+               char c = s.charAt(0);
+               boolean isPrefixed = false;
+               if (c == '+' || c == '-') {
+                       isPrefixed = true;
+                       i++;
+               }
+               if (i == length)
+                       return false;
+               c = s.charAt(i++);
+               if (c == '0' && length > (isPrefixed ? 2 : 1)) {
+                       c = s.charAt(i++);
+                       if (c == 'x' || c == 'X') {
+                               for (int j = i; j < length; j++) {
+                                       if (! hexChars.contains(s.charAt(j)))
+                                               return false;
+                               }
+                       } else if (octChars.contains(c)) {
+                               for (int j = i; j < length; j++)
+                                       if (! octChars.contains(s.charAt(j)))
+                                               return false;
+                       } else {
+                               return false;
+                       }
+               } else if (c == '#') {
+                       for (int j = i; j < length; j++) {
+                               if (! hexChars.contains(s.charAt(j)))
+                                       return false;
+                       }
+               } else if (decChars.contains(c)) {
+                       for (int j = i; j < length; j++)
+                               if (! decChars.contains(s.charAt(j)))
+                                       return false;
+               } else {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Convenience method for getting a stack trace as a string.
+        *
+        * @param t The throwable to get the stack trace from.
+        * @return The same content that would normally be rendered via 
<code>t.printStackTrace()</code>
+        */
+       public static String getStackTrace(Throwable t) {
+               StringWriter sw = new StringWriter();
+               PrintWriter pw = new PrintWriter(sw);
+               t.printStackTrace(pw);
+               pw.flush();
+               pw.close();
+               return sw.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param separator The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Object[] tokens, String separator) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(separator);
+                       sb.append(tokens[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(int[] tokens, String d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(d);
+                       sb.append(tokens[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Collection<?> tokens, String d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) {
+                       sb.append(iter.next());
+                       if (iter.hasNext())
+                               sb.append(d);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Object[] tokens, char d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(d);
+                       sb.append(tokens[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(int[] tokens, char d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < tokens.length; i++) {
+                       if (i > 0)
+                               sb.append(d);
+                       sb.append(tokens[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Join the specified tokens into a delimited string.
+        *
+        * @param tokens The tokens to join.
+        * @param d The delimiter.
+        * @return The delimited string.  If <code>tokens</code> is 
<jk>null</jk>, returns <jk>null</jk>.
+        */
+       public static String join(Collection<?> tokens, char d) {
+               if (tokens == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) {
+                       sb.append(iter.next());
+                       if (iter.hasNext())
+                               sb.append(d);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Splits a character-delimited string into a string array.
+        * Does not split on escaped-delimiters (e.g. "\,");
+        * Resulting tokens are trimmed of whitespace.
+        * NOTE:  This behavior is different than the Jakarta equivalent.
+        * split("a,b,c",',') -> {"a","b","c"}
+        * split("a, b ,c ",',') -> {"a","b","c"}
+        * split("a,,c",',') -> {"a","","c"}
+        * split(",,",',') -> {"","",""}
+        * split("",',') -> {}
+        * split(null,',') -> null
+        * split("a,b\,c,d", ',', false) -> {"a","b\,c","d"}
+        * split("a,b\\,c,d", ',', false) -> {"a","b\","c","d"}
+        * split("a,b\,c,d", ',', true) -> {"a","b,c","d"}
+        *
+        * @param s The string to split.  Can be <jk>null</jk>.
+        * @param c The character to split on.
+        * @return The tokens.
+        */
+       public static String[] split(String s, char c) {
+
+               char[] unEscapeChars = new char[]{'\\', c};
+
+               if (s == null)
+                       return null;
+               if (isEmpty(s))
+                       return new String[0];
+
+               List<String> l = new LinkedList<String>();
+               char[] sArray = s.toCharArray();
+               int x1 = 0, escapeCount = 0;
+               for (int i = 0; i < sArray.length; i++) {
+                       if (sArray[i] == '\\') escapeCount++;
+                       else if (sArray[i]==c && escapeCount % 2 == 0) {
+                               String s2 = new String(sArray, x1, i-x1);
+                               String s3 = unEscapeChars(s2, unEscapeChars);
+                               l.add(s3.trim());
+                               x1 = i+1;
+                       }
+                       if (sArray[i] != '\\') escapeCount = 0;
+               }
+               String s2 = new String(sArray, x1, sArray.length-x1);
+               String s3 = unEscapeChars(s2, unEscapeChars);
+               l.add(s3.trim());
+
+               return l.toArray(new String[l.size()]);
+       }
+
+       /**
+        * Returns <jk>true</jk> if specified string is <jk>null</jk> or empty.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if specified string is <jk>null</jk> or empty.
+        */
+       public static boolean isEmpty(String s) {
+               return s == null || s.isEmpty();
+       }
+
+       /**
+        * Returns <jk>true</jk> if specified string is <jk>null</jk> or it's 
{@link #toString()} method returns an empty string.
+        *
+        * @param s The string to check.
+        * @return <jk>true</jk> if specified string is <jk>null</jk> or it's 
{@link #toString()} method returns an empty string.
+        */
+       public static boolean isEmpty(Object s) {
+               return s == null || s.toString().isEmpty();
+       }
+
+       /**
+        * Returns <jk>null</jk> if the specified string is <jk>null</jk> or 
empty.
+        *
+        * @param s The string to check.
+        * @return <jk>null</jk> if the specified string is <jk>null</jk> or 
empty, or the same string if not.
+        */
+       public static String nullIfEmpty(String s) {
+               if (s == null || s.isEmpty())
+                       return null;
+               return s;
+       }
+
+       /**
+        * Removes escape characters (\) from the specified characters.
+        *
+        * @param s The string to remove escape characters from.
+        * @param toEscape The characters escaped.
+        * @return A new string if characters were removed, or the same string 
if not or if the input was <jk>null</jk>.
+        */
+       public static String unEscapeChars(String s, char[] toEscape) {
+               return unEscapeChars(s, toEscape, '\\');
+       }
+
+       /**
+        * Removes escape characters (specified by escapeChar) from the 
specified characters.
+        *
+        * @param s The string to remove escape characters from.
+        * @param toEscape The characters escaped.
+        * @param escapeChar The escape character.
+        * @return A new string if characters were removed, or the same string 
if not or if the input was <jk>null</jk>.
+        */
+       public static String unEscapeChars(String s, char[] toEscape, char 
escapeChar) {
+               if (s == null) return null;
+               if (s.length() == 0 || toEscape == null || toEscape.length == 0 
|| escapeChar == 0) return s;
+               StringBuffer sb = new StringBuffer(s.length());
+               char[] sArray = s.toCharArray();
+               for (int i = 0; i < sArray.length; i++) {
+                       char c = sArray[i];
+
+                       if (c == escapeChar) {
+                               if (i+1 != sArray.length) {
+                                       char c2 = sArray[i+1];
+                                       boolean isOneOf = false;
+                                       for (int j = 0; j < toEscape.length && 
! isOneOf; j++)
+                                               isOneOf = (c2 == toEscape[j]);
+                                       if (isOneOf) {
+                                               i++;
+                                       } else if (c2 == escapeChar) {
+                                               sb.append(escapeChar);
+                                               i++;
+                                       }
+                               }
+                       }
+                       sb.append(sArray[i]);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Debug method for rendering non-ASCII character sequences.
+        *
+        * @param s The string to decode.
+        * @return A string with non-ASCII characters converted to 
<js>"[hex]"</js> sequences.
+        */
+       public static String decodeHex(String s) {
+               if (s == null)
+                       return null;
+               StringBuilder sb = new StringBuilder();
+               for (char c : s.toCharArray()) {
+                       if (c < ' ' || c > '~')
+                               sb.append("["+Integer.toHexString(c)+"]");
+                       else
+                               sb.append(c);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * An efficient method for checking if a string starts with a character.
+        *
+        * @param s The string to check.  Can be <jk>null</jk>.
+        * @param c The character to check for.
+        * @return <jk>true</jk> if the specified string is not <jk>null</jk> 
and starts with the specified character.
+        */
+       public static boolean startsWith(String s, char c) {
+               if (s != null) {
+                       int i = s.length();
+                       if (i > 0)
+                               return s.charAt(0) == c;
+               }
+               return false;
+       }
+
+       /**
+        * An efficient method for checking if a string ends with a character.
+        *
+        * @param s The string to check.  Can be <jk>null</jk>.
+        * @param c The character to check for.
+        * @return <jk>true</jk> if the specified string is not <jk>null</jk> 
and ends with the specified character.
+        */
+       public static boolean endsWith(String s, char c) {
+               if (s != null) {
+                       int i = s.length();
+                       if (i > 0)
+                               return s.charAt(i-1) == c;
+               }
+               return false;
+       }
+
+       /**
+        * Tests two strings for equality, but gracefully handles nulls.
+        *
+        * @param s1 String 1.
+        * @param s2 String 2.
+        * @return <jk>true</jk> if the strings are equal.
+        */
+       public static boolean isEquals(String s1, String s2) {
+               if (s1 == null)
+                       return s2 == null;
+               if (s2 == null)
+                       return false;
+               return s1.equals(s2);
+       }
+
+       /**
+        * Shortcut for calling 
<code>base64Encode(in.getBytes(<js>"UTF-8"</js>))</code>
+        *
+        * @param in The input string to convert.
+        * @return The string converted to BASE-64 encoding.
+        */
+       public static String base64EncodeToString(String in) {
+               if (in == null)
+                       return null;
+               return base64Encode(in.getBytes(IOUtils.UTF8));
+       }
+
+       /**
+        * BASE64-encodes the specified byte array.
+        *
+        * @param in The input byte array to convert.
+        * @return The byte array converted to a BASE-64 encoded string.
+        */
+       public static String base64Encode(byte[] in) {
+               int outLength = (in.length * 4 + 2) / 3;   // Output length 
without padding
+               char[] out = new char[((in.length + 2) / 3) * 4];  // Length 
includes padding.
+               int iIn = 0;
+               int iOut = 0;
+               while (iIn < in.length) {
+                       int i0 = in[iIn++] & 0xff;
+                       int i1 = iIn < in.length ? in[iIn++] & 0xff : 0;
+                       int i2 = iIn < in.length ? in[iIn++] & 0xff : 0;
+                       int o0 = i0 >>> 2;
+                       int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
+                       int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
+                       int o3 = i2 & 0x3F;
+                       out[iOut++] = base64m1[o0];
+                       out[iOut++] = base64m1[o1];
+                       out[iOut] = iOut < outLength ? base64m1[o2] : '=';
+                       iOut++;
+                       out[iOut] = iOut < outLength ? base64m1[o3] : '=';
+                       iOut++;
+               }
+               return new String(out);
+       }
+
+       /**
+        * Shortcut for calling <code>base64Decode(String)</code> and 
converting the
+        *      result to a UTF-8 encoded string.
+        *
+        * @param in The BASE-64 encoded string to decode.
+        * @return The decoded string.
+        */
+       public static String base64DecodeToString(String in) {
+               byte[] b = base64Decode(in);
+               if (b == null)
+                       return null;
+               return new String(b, IOUtils.UTF8);
+       }
+
+       /**
+        * BASE64-decodes the specified string.
+        *
+        * @param in The BASE-64 encoded string.
+        * @return The decoded byte array.
+        */
+       public static byte[] base64Decode(String in) {
+               if (in == null)
+                       return null;
+
+               byte bIn[] = in.getBytes(IOUtils.UTF8);
+
+               if (bIn.length % 4 != 0)
+                       illegalArg("Invalid BASE64 string length.  Must be 
multiple of 4.");
+
+               // Strip out any trailing '=' filler characters.
+               int inLength = bIn.length;
+               while (inLength > 0 && bIn[inLength - 1] == '=')
+                       inLength--;
+
+               int outLength = (inLength * 3) / 4;
+               byte[] out = new byte[outLength];
+               int iIn = 0;
+               int iOut = 0;
+               while (iIn < inLength) {
+                       int i0 = bIn[iIn++];
+                       int i1 = bIn[iIn++];
+                       int i2 = iIn < inLength ? bIn[iIn++] : 'A';
+                       int i3 = iIn < inLength ? bIn[iIn++] : 'A';
+                       int b0 = base64m2[i0];
+                       int b1 = base64m2[i1];
+                       int b2 = base64m2[i2];
+                       int b3 = base64m2[i3];
+                       int o0 = (b0 << 2) | (b1 >>> 4);
+                       int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
+                       int o2 = ((b2 & 3) << 6) | b3;
+                       out[iOut++] = (byte)o0;
+                       if (iOut < outLength)
+                               out[iOut++] = (byte)o1;
+                       if (iOut < outLength)
+                               out[iOut++] = (byte)o2;
+               }
+               return out;
+       }
+
+       /**
+        * Generated a random UUID with the specified number of characters.
+        * Characters are composed of lower-case ASCII letters and numbers only.
+        * This method conforms to the restrictions for hostnames as specified 
in <a href='https://tools.ietf.org/html/rfc952'>RFC 952</a>
+        * Since each character has 36 possible values, the square 
approximation formula for
+        *      the number of generated IDs that would produce a 50% chance of 
collision is:
+        * <code>sqrt(36^N)</code>.
+        * Dividing this number by 10 gives you an approximation of the number 
of generated IDs
+        *      needed to produce a <1% chance of collision.
+        * For example, given 5 characters, the number of generated IDs need to 
produce a <1% chance of
+        *      collision would be:
+        * <code>sqrt(36^5)/10=777</code>
+        *
+        * @param numchars The number of characters in the generated UUID.
+        * @return A new random UUID.
+        */
+       public static String generateUUID(int numchars) {
+               Random r = new Random();
+               StringBuilder sb = new StringBuilder(numchars);
+               for (int i = 0; i < numchars; i++) {
+                       int c = r.nextInt(36) + 97;
+                       if (c > 'z')
+                               c -= ('z'-'0'+1);
+                       sb.append((char)c);
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Same as {@link String#trim()} but prevents 
<code>NullPointerExceptions</code>.
+        *
+        * @param s The string to trim.
+        * @return The trimmed string, or <jk>null</jk> if the string was 
<jk>null</jk>.
+        */
+       public static String trim(String s) {
+               if (s == null)
+                       return null;
+               return s.trim();
+       }
+
+       /**
+        * Parses an ISO8601 string into a date.
+        *
+        * @param date The date string.
+        * @return The parsed date.
+        * @throws IllegalArgumentException
+        */
+       @SuppressWarnings("nls")
+       public static Date parseISO8601Date(String date) throws 
IllegalArgumentException {
+               if (isEmpty(date))
+                       return null;
+               date = date.trim().replace(' ', 'T');  // Convert to 'standard' 
ISO8601
+               if (date.indexOf(',') != -1)  // Trim milliseconds
+                       date = date.substring(0, date.indexOf(','));
+               if (date.matches("\\d{4}"))
+                       date += "-01-01T00:00:00";
+               else if (date.matches("\\d{4}\\-\\d{2}"))
+                       date += "-01T00:00:00";
+               else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}"))
+                       date += "T00:00:00";
+               else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}"))
+                       date += ":00:00";
+               else if 
(date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}\\:\\d{2}"))
+                       date += ":00";
+               return DatatypeConverter.parseDateTime(date).getTime();
+       }
+
+       /**
+        * Simple utility for replacing variables of the form <js>"{key}"</js> 
with values
+        *      in the specified map.
+        * <p>
+        * Nested variables are supported in both the input string and map 
values.
+        * <p>
+        * If the map does not contain the specified value, the variable is not 
replaced.
+        *      <p>
+        *      <jk>null</jk> values in the map are treated as blank strings.
+        *
+        * @param s The string containing variables to replace.
+        * @param m The map containing the variable values.
+        * @return The new string with variables replaced, or the original 
string if it didn't have variables in it.
+        */
+       public static String replaceVars(String s, Map<String,Object> m) {
+
+               if (s.indexOf('{') == -1)
+                       return s;
+
+               int S1 = 1;        // Not in variable, looking for {
+               int S2 = 2;    // Found {, Looking for }
+
+               int state = S1;
+               boolean hasInternalVar = false;
+               int x = 0;
+               int depth = 0;
+               int length = s.length();
+               StringBuilder out = new StringBuilder();
+               for (int i = 0; i < length; i++) {
+                       char c = s.charAt(i);
+                       if (state == S1) {
+                               if (c == '{') {
+                                       state = S2;
+                                       x = i;
+                               } else {
+                                       out.append(c);
+                               }
+                       } else /* state == S2 */ {
+                               if (c == '{') {
+                                       depth++;
+                                       hasInternalVar = true;
+                               } else if (c == '}') {
+                                       if (depth > 0) {
+                                               depth--;
+                                       } else {
+                                               String key = s.substring(x+1, 
i);
+                                               key = (hasInternalVar ? 
replaceVars(key, m) : key);
+                                               hasInternalVar = false;
+                                               if (! m.containsKey(key))
+                                                       
out.append('{').append(key).append('}');
+                                               else {
+                                                       Object val = m.get(key);
+                                                       if (val == null)
+                                                               val = "";
+                                                       String v = 
val.toString();
+                                                       // If the replacement 
also contains variables, replace them now.
+                                                       if (v.indexOf('{') != 
-1)
+                                                               v = 
replaceVars(v, m);
+                                                       out.append(v);
+                                               }
+                                               state = 1;
+                                       }
+                               }
+                       }
+               }
+               return out.toString();
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified path string is prefixed with 
the specified prefix.
+        * <p>
+        * Examples:
+        * <p class='bcode'>
+        *      pathStartsWith(<js>"foo"</js>, <js>"foo"</js>);  <jc>// 
true</jc>
+        *      pathStartsWith(<js>"foo/bar"</js>, <js>"foo"</js>);  <jc>// 
true</jc>
+        *      pathStartsWith(<js>"foo2"</js>, <js>"foo"</js>);  <jc>// 
false</jc>
+        *      pathStartsWith(<js>"foo2"</js>, <js>""</js>);  <jc>// false</jc>
+        * </p>
+        *
+        * @param path The path to check.
+        * @param pathPrefix The prefix.
+        * @return <jk>true</jk> if the specified path string is prefixed with 
the specified prefix.
+        */
+       public static boolean pathStartsWith(String path, String pathPrefix) {
+               if (path == null || pathPrefix == null)
+                       return false;
+               if (path.startsWith(pathPrefix))
+                       return path.length() == pathPrefix.length() || 
path.charAt(pathPrefix.length()) == '/';
+               return false;
+       }
+
+       /**
+        * Same as {@link #pathStartsWith(String, String)} but returns 
<jk>true</jk> if at least one prefix matches.
+        * <p>
+        *
+        * @param path The path to check.
+        * @param pathPrefixes The prefixes.
+        * @return <jk>true</jk> if the specified path string is prefixed with 
any of the specified prefixes.
+        */
+       public static boolean pathStartsWith(String path, String[] 
pathPrefixes) {
+               for (String p : pathPrefixes)
+                       if (pathStartsWith(path, p))
+                               return true;
+               return false;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.class 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.class
new file mode 100755
index 0000000..3aa5049
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.class 
differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.java 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.java
new file mode 100755
index 0000000..c09e88b
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVar.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+/**
+ * Interface for the resolution of string variables using the {@link 
StringVarResolver} API.
+ *
+ * @author jbognar
+ */
+public abstract class StringVar {
+
+       /**
+        * The method called from {@link StringVarResolver}.
+        * Can be overridden to intercept the request and do special handling.
+        * Default implementation simply calls resolve(String).
+        *
+        * @param arg The inside argument of the variable.
+        * @return The resolved value.
+        */
+       protected String doResolve(String arg) {
+               return resolve(arg);
+       }
+
+       /**
+        * The interface that needs to be implemented for string vars.
+        *
+        * @param arg The inside argument of the variable.
+        * @return The resolved value.
+        */
+       public abstract String resolve(String arg);
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.class
new file mode 100755
index 0000000..3d6cef6
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.java
new file mode 100755
index 0000000..5f9b8be
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarMultipart.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+
+/**
+ * Interface for the resolution of string vars that consist of a 
comma-delimited list.
+ * <p>
+ * (e.g. <js>"$X{foo, bar, baz}"</js>)
+ */
+public abstract class StringVarMultipart extends StringVar {
+
+       /**
+        * The interface that needs to be implemented for this interface.
+        *
+        * @param args The arguments inside the variable.
+        * @return The resolved variable.
+        */
+       public abstract String resolve(String[] args);
+
+       @Override /* StringVar*/
+       public String resolve(String s) {
+               String[] s2 = s.indexOf(',') == -1 ? new String[]{s} : 
StringUtils.split(s, ',');
+               return resolve(s2);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$1.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$1.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$1.class
new file mode 100755
index 0000000..91f4947
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$1.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$2.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$2.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$2.class
new file mode 100755
index 0000000..18f9577
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver$2.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.class
new file mode 100755
index 0000000..f65f3b7
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.java
new file mode 100755
index 0000000..5abffd7
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarResolver.java
@@ -0,0 +1,357 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+import static com.ibm.juno.core.utils.StringUtils.*;
+import static com.ibm.juno.core.utils.ThrowableUtils.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Utility class for resolving  variables of the form <code>$X{key}</code> in 
strings.
+ * <p>
+ * This class implements the following two methods for resolving variables in 
strings:
+ * <ul>
+ *     <li>{@link #resolve(String)} - Returns a new string with variables 
replaced.
+ *     <li>{@link #writeTo(String,Writer)} - Resolves variables in the string 
and sends the result to the writer.
+ * </ul>
+ * <p>
+ * Variables are of the form <code>$X{key}</code>, where <code>X</code> can 
consist of zero or more ASCII characters.<br>
+ * The variable key can contain anything, even nested variables that get 
recursively resolved.
+ * <p>
+ * Variable types are defined through the {@link #addVar(String, StringVar)} 
method.
+ * <p>
+ * The {@link StringVar} interface defines a simple method for replacing a 
variable key with a value.
+ * <p>
+ * <h6 class='topic'>Example:</h6>
+ * <p class='bcode'>
+ *     <jc>// Create a variable resolver that resolves system properties (e.g. 
"$S{java.home}")</jc>
+ *     StringVarResolver r = <jk>new</jk> StringVarResolver()
+ *             .addVar(<js>"S"</js>, <jk>new</jk> StringVar() {
+ *                     <ja>@Override</ja>
+ *                     <jk>public</jk> String resolve(String varVal) {
+ *                             <jk>return</jk> 
System.<jsm>getProperty</jsm>(varVal);
+ *                     }
+ *             });
+ *
+ *     System.<jsf>out</jsf>.println(r.resolve(<js>"java.home is set to 
$S{java.home}"</js>));
+ * </p>
+ * <p>
+ * Subclasses of {@link StringVar} are provided for special purposes:
+ * <ul>
+ *     <li>{@link StringVarMultipart} - Interface for the resolution of vars 
that consist of a comma-delimited list (e.g. <js>"$X{foo, bar, baz}"</js>)
+ *     <li>{@link StringVarWithDefault} - Interface for the resolution of vars 
with a default value if the <code>resolve(String)</code> method returns 
<jk>null</jk> (e.g. <js>"$S{myProperty,not found}"</js>).
+ * </ul>
+ *     <p>
+ *     The {@link #DEFAULT} instance is a reusable variable resolver that 
includes support for system properties and environment variables.
+ * <p>
+ * <code>StringVarResolvers</code> can be extended by using the {@link 
#StringVarResolver(StringVarResolver)} constructor.
+ * <p>
+ * <h6 class='topic'>Example:</h6>
+ * <p class='bcode'>
+ *     <jc>// Create a var resolver that extends the default resolver and 
appends our own "$URLEncode{...}" variable</jc>
+ *     StringVarResolver r = <jk>new</jk> 
StringVarResolver(StringVarResolver.<jsf>DEFAULT</jsf>)
+ *             .addVar(<js>"URLEncode"</js>, <jk>new</jk> StringVar() {
+ *                     <ja>@Override</ja>
+ *                     <jk>public</jk> String resolve(String varVal) {
+ *                             <jk>return</jk> 
URLEncoder.<jsm>encode</jsm>(varVal, <js>"UTF-8"</js>);
+ *                     }
+ *             });
+ *
+ *     <jc>// Retrieve a system property and URL-encode it if necessary.</jc>
+ *     String myProperty = r.resolve(<js>"$URLEncode{$S{my.property}}"</js>);
+ * <p>
+ * Variables can be nested arbitrarily deep.
+ * <p>
+ * <h6 class='topic'>Example:</h6>
+ * <p class='bcode'>
+ *     <jc>// Look up a property in the following order:
+ *     // 1) MYPROPERTY environment variable.
+ *     // 2) 'my.property' system property if environment variable not found.
+ *     // 3) 'not found' string if system property not found.</jc>
+ *     String myproperty = 
StringVarResolver.<jsf>DEFAULT</jsf>.resolve(<js>"$E{MYPROPERTY,$S{my.property,not
 found}}"</js>);
+ * </p>
+ * <p>
+ * Resolved variables can also contain variables.
+ * <p class='bcode'>
+ *     <jc>// If MYPROPERTY is "$S{my.property}", and the system property 
"my.property" is "foo",
+ *     // then the following resolves to "foo".</jc>
+ *     String myproperty = 
StringVarResolver.<jsf>DEFAULT</jsf>.resolve(<js>"$E{MYPROPERTY}"</js>);
+ * </p>
+ * <p>
+ * <h6 class='topic'>Other notes:</h6>
+ * <ul class='spaced-list'>
+ *     <li>The escape character <js>'\'</js> can be used when necessary to 
escape the following characters: <code>$ , { }</code>
+ *     <li><b>WARNING:</b>  It is possible to cause {@link StackOverflowError 
StackOverflowErrors} if your nested variables result in
+ *             a recursive loop (e.g. the environment variable 
<code>'MYPROPERTY'</code> has the value <code>'$E{MYPROPERTY}'</code>).
+ *             So don't do that!
+ *     <li>As a general rule, this class tries to be as efficient as possible 
by not creating new strings when not needed.<br>
+ *             For example, calling the resolve method on a string that 
doesn't contain variables (e.g. 
<code>resolver.resolve(<js>"foobar"</js>)</code>)
+ *             will simply be a no-op and return the same string.
+ *
+ *
+ * @author James Bognar ([email protected])
+ */
+public class StringVarResolver {
+
+       /**
+        * Default string variable resolver with support for system properties 
and environment variables:
+        * <p>
+        * <ul>
+        *      <li><code>$S{key}</code>,<code>$S{key,default}</code> - System 
properties.
+        *      <li><code>$E{key}</code>,<code>$E{key,default}</code> - 
Environment variables.
+        * </ul>
+        */
+       public static final StringVarResolver DEFAULT = new StringVarResolver()
+               // System properties.
+               .addVar("S", new StringVarWithDefault() {
+                       @Override /* StringVar */
+                       public String resolve(String varVal) {
+                               return System.getProperty(varVal);
+                       }
+               })
+               // Environment variables.
+               .addVar("E", new StringVarWithDefault() {
+                       @Override /* StringVar */
+                       public String resolve(String varVal) {
+                               return System.getenv(varVal);
+                       }
+               })
+       ;
+
+       // Map of Vars added through addVar() method.
+       private Map<String,StringVar> varMap = new TreeMap<String,StringVar>();
+
+       private StringVarResolver parent;
+
+       /**
+        * Construct an empty var resolver.
+        */
+       public StringVarResolver() {}
+
+       /**
+        * Construct an empty var resolver with the specified parent.
+        *
+        * @param parent The parent string variable resolver.  Can be 
<jk>null</jk>.
+        */
+       public StringVarResolver(StringVarResolver parent) {
+               this.parent = parent;
+       }
+
+       /**
+        * Register a new variable with this resolver.
+        *
+        * @param varName The variable name (e.g. <js>"X"</js> resolves 
<js>"$X{...}"</js> variables).
+        * @param v The variable resolver.
+        * @return This object (for method chaining).
+        */
+       public StringVarResolver addVar(String varName, StringVar v) {
+               assertFieldNotNull(v, "v");
+
+               // Need to make sure only ASCII characters are used.
+               for (int i = 0; i < varName.length(); i++) {
+                       char c = varName.charAt(i);
+                       if (c < 'A' || c > 'z' || (c > 'Z' && c < 'a'))
+                               illegalArg("Invalid var name.  Must consist of 
only uppercase and lowercase ASCII letters.");
+               }
+               varMap.put(varName, v);
+               return this;
+       }
+
+       /**
+        * Resolve all variables in the specified string.
+        *
+        * @param s The string to resolve variables in.
+        * @return The new string with all variables resolved, or the same 
string if no variables were found.
+        *      Null input results in a blank string.
+        */
+       public String resolve(String s) {
+
+               if (s == null)
+                       return "";
+               if (s.indexOf('$') == -1 && s.indexOf('\\') == -1)
+                       return s;
+
+               // Special case where value consists of a single variable with 
no embedded variables (e.g. "$X{...}").
+               // This is a common case, so we want an optimized solution that 
doesn't involve string builders.
+               if (isSimpleVar(s)) {
+                       String var = s.substring(1, s.indexOf('{'));
+                       String val = s.substring(s.indexOf('{')+1, 
s.length()-1);
+                       StringVar v = getVar(var);
+                       if (v != null) {
+                               s = v.doResolve(val);
+                               return resolve(s);
+                       }
+                       return s;
+               }
+
+               try {
+                       return writeTo(s, new StringWriter()).toString();
+               } catch (IOException e) {
+                       throw new RuntimeException(e); // Never happens.
+               }
+       }
+
+       /**
+        * Checks to see if string is of the simple form "$X{...}" with no 
embedded variables.
+        * This is a common case, and we can avoid using StringWriters.
+        */
+       private boolean isSimpleVar(String s) {
+               int S1 = 1;        // Not in variable, looking for $
+               int S2 = 2;    // Found $, Looking for {
+               int S3 = 3;    // Found {, Looking for }
+               int S4 = 4;    // Found }
+
+               int length = s.length();
+               int state = S1;
+               for (int i = 0; i < length; i++) {
+                       char c = s.charAt(i);
+                       if (state == S1) {
+                               if (c == '$') {
+                                       state = S2;
+                               } else {
+                                       return false;
+                               }
+                       } else if (state == S2) {
+                               if (c == '{') {
+                                       state = S3;
+                               } else if (c < 'A' || c > 'z' || (c > 'Z' && c 
< 'a')) {   // False trigger "$X "
+                                       return false;
+                               }
+                       } else if (state == S3) {
+                               if (c == '}')
+                                       state = S4;
+                               else if (c == '{' || c == '$')
+                                       return false;
+                       } else if (state == S4) {
+                               return false;
+                       }
+               }
+               return state == S4;
+       }
+
+       /**
+        * Resolves variables in the specified string and sends the output to 
the specified writer.
+        * More efficient than first parsing to a string and then serializing 
to the writer since this
+        * method doesn't need to construct a large string.
+        *
+        * @param s The string to resolve variables in.
+        * @param out The writer to write to.
+        * @return The same writer.
+        * @throws IOException
+        */
+       public Writer writeTo(String s, Writer out) throws IOException {
+
+               int S1 = 1;        // Not in variable, looking for $
+               int S2 = 2;    // Found $, Looking for {
+               int S3 = 3;    // Found {, Looking for }
+
+               int state = S1;
+               boolean isInEscape = false;
+               boolean hasInternalVar = false;
+               boolean hasInnerEscapes = false;
+               String varType = null;
+               String varVal = null;
+               int x = 0, x2 = 0;
+               int depth = 0;
+               int length = s.length();
+               for (int i = 0; i < length; i++) {
+                       char c = s.charAt(i);
+                       if (state == S1) {
+                               if (isInEscape) {
+                                       if (c == '\\' || c == '$') {
+                                               out.append(c);
+                                       } else {
+                                               out.append('\\').append(c);
+                                       }
+                                       isInEscape = false;
+                               } else if (c == '\\') {
+                                       isInEscape = true;
+                               } else if (c == '$') {
+                                       x = i;
+                                       x2 = i;
+                                       state = S2;
+                               } else {
+                                       out.append(c);
+                               }
+                       } else if (state == S2) {
+                               if (isInEscape) {
+                                       isInEscape = false;
+                               } else if (c == '\\') {
+                                       hasInnerEscapes = true;
+                                       isInEscape = true;
+                               } else if (c == '{') {
+                                       varType = s.substring(x+1, i);
+                                       x = i;
+                                       state = S3;
+                               } else if (c < 'A' || c > 'z' || (c > 'Z' && c 
< 'a')) {  // False trigger "$X "
+                                       if (hasInnerEscapes)
+                                               
out.append(unEscapeChars(s.substring(x, i+1), new char[]{'\\','{'}));
+                                       else
+                                               out.append(s, x, i+1);
+                                       x = i + 1;
+                                       state = S1;
+                                       hasInnerEscapes = false;
+                               }
+                       } else if (state == S3) {
+                               if (isInEscape) {
+                                       isInEscape = false;
+                               } else if (c == '\\') {
+                                       isInEscape = true;
+                                       hasInnerEscapes = true;
+                               } else if (c == '{') {
+                                       depth++;
+                                       hasInternalVar = true;
+                               } else if (c == '}') {
+                                       if (depth > 0) {
+                                               depth--;
+                                       } else {
+                                               varVal = s.substring(x+1, i);
+                                               varVal = (hasInternalVar ? 
resolve(varVal) : varVal);
+                                               StringVar r = getVar(varType);
+                                               if (r == null) {
+                                                       if (hasInnerEscapes)
+                                                               
out.append(unEscapeChars(s.substring(x2, i+1), new char[]{'\\','$','{','}'}));
+                                                       else
+                                                               out.append(s, 
x2, i+1);
+                                                       x = i+1;
+                                               } else {
+                                                       String replacement = 
r.doResolve(varVal);
+                                                       if (replacement == null)
+                                                               replacement = 
"";
+                                                       // If the replacement 
also contains variables, replace them now.
+                                                       if 
(replacement.indexOf('$') != -1)
+                                                               replacement = 
resolve(replacement);
+                                                       out.append(replacement);
+                                                       x = i+1;
+                                               }
+                                               state = 1;
+                                               hasInnerEscapes = false;
+                                       }
+                               }
+                       }
+               }
+               if (isInEscape)
+                       out.append("\\");
+               else if (state == S2)
+                       out.append("$").append(unEscapeChars(s.substring(x+1), 
new char[]{'{', '\\'}));
+               else if (state == S3)
+                       
out.append("$").append(varType).append('{').append(unEscapeChars(s.substring(x+1),
 new char[]{'\\','$','{','}'}));
+               return out;
+       }
+
+       private StringVar getVar(String varType) {
+               StringVar v = varMap.get(varType);
+               if (v == null && parent != null)
+                       v = parent.getVar(varType);
+               return v;
+       }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.class
new file mode 100755
index 0000000..901efeb
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.java
new file mode 100755
index 0000000..e1f63eb
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/StringVarWithDefault.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.utils;
+
+
+/**
+ * Interface for the resolution of string vars with a default value if the 
<code>resolve(String)</code> method returns <jk>null</jk>.
+ * <p>
+ * For example, to resolve the system property <js>"myProperty"</js> but 
resolve to <js>"not found"</js> if the property doesn't exist:
+ * <js>"$S{myProperty,not found}"</js>
+ */
+public abstract class StringVarWithDefault extends StringVar {
+
+       @Override /* StringVar*/
+       public String doResolve(String s) {
+               int i = s.indexOf(',');
+               if (i == -1)
+                       return resolve(s);
+               String[] s2 = StringUtils.split(s, ',');
+               String v = resolve(s2[0]);
+               if (v == null)
+                       v = s2[1];
+               return v;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$1.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$1.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$1.class
new file mode 100755
index 0000000..6ca8b6e
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$1.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$NoCloseOutputStream.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$NoCloseOutputStream.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$NoCloseOutputStream.class
new file mode 100755
index 0000000..d759a42
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream$NoCloseOutputStream.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream.class
new file mode 100755
index 0000000..edffdca
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/utils/TeeOutputStream.class
 differ

Reply via email to