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. <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
