brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hadr...@apache.org
Subject [10/64] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply org.apache package prefix to utils-common
Date Tue, 18 Aug 2015 15:03:19 GMT
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/ByteSizeStrings.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/ByteSizeStrings.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/ByteSizeStrings.java
new file mode 100644
index 0000000..cc28efc
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/ByteSizeStrings.java
@@ -0,0 +1,416 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+import java.util.Formattable;
+import java.util.FormattableFlags;
+import java.util.Formatter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+import com.google.common.base.Function;
+
+/**
+ * A formatter to pretty-print numeric values representing sizes in byes.
+ * <p>
+ * The {@link ByteSizeStrings#builder()} presents a fluent interface to create
+ * various configurations of formatting. The defaults produce metric units in
+ * multiples of 1000 bytes at a precision of three significant figures. This is
+ * the way disk space is normally measured, for example {@literal 128.1GB}.
+ * <p>
+ * Alternatively the {@link ByteSizeStrings#iso()} convenience method produces
+ * ISO standard units in multiples of 1024 bytes, with the same precision as the
+ * metric output. This is how RAM is normally measured, for example {@literal 12.4MiB}
+ * or {@literal 1.04GiB}.
+ * <p>
+ * Finally, the {@link ByteSizeStrings#java()} convenience method will produce
+ * strings suitable for use with a Java command line, as part of the {@code -Xms}
+ * or {@code -Xmx} options. These output integer values only, so values up to
+ * 10GB will be reported in MB to preserve accuracy. For size values over 1000GB,
+ * the output will still be formatted as GB but rounded to a mutiple of 1000.
+ * <p>
+ * The class is immutable and thread safe once built and a single instance of
+ * the three pre-defined configurations is created and returned buy the methods
+ * described above.
+ *
+ * @see Strings#makeSizeString(long)
+ * @see Strings#makeISOSizeString(long)
+ * @see Strings#makeJavaSizeString(long)
+ */
+public class ByteSizeStrings implements Function<Long, String> {
+
+    /**
+     * Configures and builds a {@link ByteSizeStrings} formatter.
+     */
+    public static class Builder {
+
+        private String suffixBytes = "B";
+        private String suffixKilo = "kB";
+        private String suffixMega = "MB";
+        private String suffixGiga = "GB";
+        private String suffixTera = "TB";
+        private boolean addSpace = true;
+        private int bytesPerMetricUnit = 1000;
+        private int maxLen = 4;
+        private int precision = 3;
+        private int lowerLimit = 1;
+
+        /**
+         * The suffix to use when printing bytes.
+         */
+        public Builder suffixBytes(String suffixBytes) { this.suffixBytes = suffixBytes; return this; }
+
+        /**
+         * The suffix to use when printing Kilobytes.
+         */
+        public Builder suffixKilo(String suffixKilo) { this.suffixKilo = suffixKilo; return this; }
+
+        /**
+         * The suffix to use when printing Megabytes.
+         */
+        public Builder suffixMega(String suffixMega) { this.suffixMega = suffixMega; return this; }
+
+        /**
+         * The suffix to use when printing Gigabytes.
+         */
+        public Builder suffixGiga(String suffixGiga) { this.suffixGiga = suffixGiga; return this; }
+
+        /**
+         * The suffix to use when printing Terabytes.
+         */
+        public Builder suffixTera(String suffixTera) { this.suffixTera = suffixTera; return this; }
+
+        /**
+         * Whether to add a space between the value and the unit suffix.
+         * <p>
+         * Defaults is {@literal true} for '5 MiB' output.
+         */
+        public Builder addSpace(boolean addSpace) { this.addSpace = addSpace; return this; }
+        public Builder addSpace() { this.addSpace = true; return this; }
+        public Builder noSpace() { this.addSpace = false; return this; }
+
+        /**
+         * The number of bytes per metric usnit, usually either 1000 or 1024.
+         * <p>
+         * Used to determine when to use the next suffix string.
+         */
+        public Builder bytesPerMetricUnit(int bytesPerMetricUnit) { this.bytesPerMetricUnit = bytesPerMetricUnit; return this; }
+
+        /**
+         * The maximum length of the printed number.
+         *
+         * @see Strings#makeRealString(double, int, int, int, double, boolean)
+         */
+        public Builder maxLen(int maxLen) { this.maxLen = maxLen; return this; }
+
+        /**
+         * The number of digits accuracy desired in the printed number.
+         *
+         * @see Strings#makeRealString(double, int, int, int, double, boolean)
+         */
+        public Builder precision(int precision) { this.precision = precision; return this; }
+
+        /**
+         * Prints using a lower suffix until the size is greater than this limit multiplied
+         * by bytes per metric unit, when the next highest suffix will be used.ยง
+         * <p>
+         * If this has the value 5 then sizes up to 5000 will be printed as bytes, and over 5000
+         * as Kilobytes.
+         */
+        public Builder lowerLimit(int lowerLimit) { this.lowerLimit = lowerLimit; return this; }
+
+        /**
+         * Returns an immutable {@link ByteSizeStrings} formatter using the builder configuration.
+         */
+        public ByteSizeStrings build() {
+            String space = addSpace ? " " : "";
+            return new ByteSizeStrings(space + suffixBytes, space + suffixKilo, space + suffixMega, space + suffixGiga,
+                    space + suffixTera, bytesPerMetricUnit, maxLen, precision, lowerLimit);
+        }
+
+    }
+
+    /**
+     * Returns a builder for a {@link ByteSizeStrings} formatter.
+     */
+    public static Builder builder() { return new Builder(); }
+
+    /**
+     * Format byte sizes suitable for Java {@code -Xms} arguments.
+     */
+    public static final ByteSizeStrings java() { return JAVA; }
+
+    private static final ByteSizeStrings JAVA = ByteSizeStrings.builder()
+                .suffixBytes("")
+                .suffixKilo("k")
+                .suffixMega("m")
+                .suffixGiga("g")
+                .suffixTera("000g") // Java has no Tera suffix
+                .noSpace()
+                .bytesPerMetricUnit(1024)
+                .maxLen(6)
+                .precision(0)
+                .lowerLimit(10)
+                .build();
+
+    /**
+     * Formats byte sizes using ISO standard suffixes and binary multiples of 1024
+     */
+    public static ByteSizeStrings iso() { return ISO; }
+
+    private static ByteSizeStrings ISO = ByteSizeStrings.builder()
+                .suffixBytes("B")
+                .suffixKilo("KiB")
+                .suffixMega("MiB")
+                .suffixGiga("GiB")
+                .suffixTera("TiB")
+                .bytesPerMetricUnit(1024)
+                .build();
+
+    /**
+     * Default byte size formatter using metric multiples of 1000.
+     */
+    public static ByteSizeStrings metric() { return METRIC; }
+
+    private static ByteSizeStrings METRIC = ByteSizeStrings.builder().build();
+
+    private String suffixBytes;
+    private String suffixKilo;
+    private String suffixMega;
+    private String suffixGiga;
+    private String suffixTera;
+    private int bytesPerMetricUnit;
+    private int maxLen;
+    private int precision;
+    private int lowerLimit;
+
+    /**
+     * For use by the {@link Builder} only.
+     */
+    private ByteSizeStrings(String suffixBytes, String suffixKilo, String suffixMega, String suffixGiga,
+            String suffixTera, int bytesPerMetricUnit, int maxLen, int precision, int lowerLimit) {
+        this.suffixBytes = suffixBytes;
+        this.suffixKilo = suffixKilo;
+        this.suffixMega = suffixMega;
+        this.suffixGiga = suffixGiga;
+        this.suffixTera = suffixTera;
+        this.bytesPerMetricUnit = bytesPerMetricUnit;
+        this.maxLen = maxLen;
+        this.precision = precision;
+        this.lowerLimit = lowerLimit;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings#builder()} */
+    @Deprecated
+    public ByteSizeStrings() { }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#suffixBytes(String)} */
+    @Deprecated
+    public void setSuffixBytes(String suffixBytes) {
+        this.suffixBytes = suffixBytes;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#suffixKilo(String)} */
+    @Deprecated
+    public void setSuffixKilo(String suffixKilo) {
+        this.suffixKilo = suffixKilo;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#suffixMega(String)} */
+    @Deprecated
+    public void setSuffixMega(String suffixMega) {
+        this.suffixMega = suffixMega;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#suffixGiga(String)} */
+    @Deprecated
+    public void setSuffixGiga(String suffixGiga) {
+        this.suffixGiga = suffixGiga;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#suffixTera(String)} */
+    @Deprecated
+    public void setSuffixTera(String suffixTera) {
+        this.suffixTera = suffixTera;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#bytesPerMetricUnit(int)} */
+    @Deprecated
+    public void setBytesPerMetricUnit(int bytesPerMetricUnit) {
+        this.bytesPerMetricUnit = bytesPerMetricUnit;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#maxLen(int)} */
+    @Deprecated
+    public void setMaxLen(int maxLen) {
+        this.maxLen = maxLen;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#precision(int)} */
+    @Deprecated
+    public void setPrecision(int precision) {
+        this.precision = precision;
+    }
+
+    /** @deprecated Use {@link ByteSizeStrings.Builder#lowerLimit(int)} */
+    @Deprecated
+    public void setLowerLimit(int lowerLimit) {
+        this.lowerLimit = lowerLimit;
+    }
+
+    /**
+     * Format the {@literal size} bytes as a String.
+     */
+    public String makeSizeString(long size) {
+        return makeSizeString(size, precision);
+    }
+
+    /**
+     * Format the {@literal size} bytes as a String with the given precision.
+     */
+    public String makeSizeString(long size, int precision) {
+        long t = size;
+        if (t==0) return "0"+suffixBytes;
+        if (t<0) return "-"+makeSizeString(-t);
+        long b = t%bytesPerMetricUnit;
+        t = t/bytesPerMetricUnit;
+        long kb = t%bytesPerMetricUnit;
+        t = t/bytesPerMetricUnit;
+        long mb = t%bytesPerMetricUnit;
+        t = t/bytesPerMetricUnit;
+        long gb = t%bytesPerMetricUnit;
+        t = t/bytesPerMetricUnit;
+        long tb = t;
+
+        if (tb>lowerLimit)
+            return Strings.makeRealString(tb + (1.0*gb/bytesPerMetricUnit), -1, precision, 0) + suffixTera;
+        if (gb>lowerLimit)
+            return Strings.makeRealString((tb*bytesPerMetricUnit) + gb + (1.0*mb/bytesPerMetricUnit), maxLen, precision, 0) + suffixGiga;
+        if (mb>lowerLimit)
+            return Strings.makeRealString((gb*bytesPerMetricUnit) + mb + (1.0*kb/bytesPerMetricUnit), maxLen, precision, 0) + suffixMega;
+        if (kb>lowerLimit)
+            return Strings.makeRealString((mb*bytesPerMetricUnit) + kb + (1.0*b/bytesPerMetricUnit), maxLen, precision, 0) + suffixKilo;
+        return (kb*bytesPerMetricUnit) + b + suffixBytes;
+    }
+
+    /**
+     * Returns a {@link Formattable} object that can be used with {@link String#format(String, Object...)}.
+     * <p>
+     * When used as the argument for a {@literal %s} format string element, the {@literal bytes} value
+     * will be formatted using the current {@link ByteSizeStrings} values, or if the alternative
+     * flag is set (using the {@literal %#s} format string) it will use the {@link ByteSizeStrings#metric()}
+     * formatter. Finally, the precision of the formatted value can be adjusted using format string
+     * argumenbts like {@literal %.6s}.
+     *
+     * @see http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax
+     */
+    public Formattable formatted(final long bytes) {
+        return new Formattable() {
+            @Override
+            public void formatTo(Formatter formatter, int flags, int width, int precision) {
+                boolean alternate = (flags & FormattableFlags.ALTERNATE) == FormattableFlags.ALTERNATE;
+                ByteSizeStrings strings = alternate ? ByteSizeStrings.metric() : ByteSizeStrings.this;
+                if (precision != -1) {
+                    formatter.format("%s", strings.makeSizeString(bytes, precision));
+                } else {
+                    formatter.format("%s", strings.makeSizeString(bytes));
+                }
+            }
+        };
+    }
+
+    /**
+     * A {@link Function} implementation that formats its input using the current {@link ByteSizeStrings} values.
+     */
+    @Override
+    @Nullable
+    public String apply(@Nullable Long input) {
+        if (input == null) return null;
+        return makeSizeString(input);
+    }
+
+    public static long parse(String sizeString) {
+        return parse(sizeString, null);
+    }
+    public static long parse(String sizeString, String defaultUnits) {
+        return parse(sizeString, defaultUnits, null);
+    }
+    /** parses the given string as a byte size string, e.g. "4gb"
+     * @param sizeString string to parse
+     * @param defaultUnit optional units to append if a number (no units) are supplied
+     * @param bytesMode optional evaluation mode to force 1024 or 1000 as the interpretation of the unit prefix;
+     *   if omitted, it will depend on the units supplied,
+     *   1000 for {@link #metric()} (e.g. "1kB"), and   
+     *   1024 for {@link #java()} (e.g. "1k") and {@link #iso()} (e.g. "1KiB")
+     * @return number of bytes represented by this string
+     */
+    public static long parse(String sizeStringOriginal, String defaultUnit, ByteSizeStrings bytesMode) {
+        String sizeString = sizeStringOriginal.trim();
+        String units;
+        Matcher matcher = Pattern.compile("[A-Za-z]+").matcher(sizeString);
+        if (!matcher.find()) {
+            if (defaultUnit==null) {
+                throw new IllegalArgumentException("Cannot parse '"+sizeStringOriginal+"' as a size string");
+            }
+            units = defaultUnit;
+        } else {
+            units = matcher.group();
+            int unitsIndex = sizeString.indexOf(units);
+            if (sizeString.length() > unitsIndex+units.length()) {
+                throw new IllegalArgumentException("Cannot parse '"+sizeStringOriginal+"' as a size string");
+            }
+            sizeString = sizeString.substring(0, unitsIndex).trim();
+        }
+        
+        int exponent = -1;
+        ByteSizeStrings matchedMode = null;
+        for (ByteSizeStrings mode: new ByteSizeStrings[] { ISO, JAVA, METRIC } ) {
+            matchedMode = mode;
+            if (units.equalsIgnoreCase(mode.suffixBytes.trim())) { exponent = 0; break; }
+            if (units.equalsIgnoreCase(mode.suffixKilo.trim())) { exponent = 1; break; }
+            if (units.equalsIgnoreCase(mode.suffixMega.trim())) { exponent = 2; break; }
+            if (units.equalsIgnoreCase(mode.suffixGiga.trim())) { exponent = 3; break; }
+            if (units.equalsIgnoreCase(mode.suffixTera.trim())) { exponent = 4; break; }
+        }
+        
+        if (exponent==-1) {
+            // did not match; try other standard ones 
+            if (units.equalsIgnoreCase("t")) { 
+                exponent = 4;
+                matchedMode = java();
+            } else {
+                throw new IllegalArgumentException("Cannot parse '"+sizeStringOriginal+"' as a size string (as '"+sizeString+"' "+units+")");
+            }
+        }
+        
+        double base = Double.parseDouble(sizeString.trim());
+        
+        if (bytesMode==null) bytesMode=matchedMode;
+        
+        while (exponent>0) {
+            base *= bytesMode.bytesPerMetricUnit;
+            exponent--;
+        }
+        
+        return (long)base;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java
new file mode 100644
index 0000000..9065603
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/ComparableVersion.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+
+/** takes a version string, and compares to other versions, using {@link NaturalOrderComparator} */
+public class ComparableVersion implements Comparable<String> {
+
+    public final String version;
+    
+    public ComparableVersion(String version) {
+        this.version = version;
+    }
+
+    public int compareTo(String target) {
+        return new NaturalOrderComparator().compare(version, target);
+    }
+    
+    public boolean isGreaterThanOrEqualTo(String target) {
+        return compareTo(target) >= 0;
+    }
+    public boolean isGreaterThanAndNotEqualTo(String target) {
+        return compareTo(target) > 0;
+    }
+    public boolean isLessThanOrEqualTo(String target) {
+        return compareTo(target) <= 0;
+    }
+    public boolean isLessThanAndNotEqualTo(String target) {
+        return compareTo(target) < 0;
+    }
+
+    /** inclusive at endpoints */
+    public boolean isInRange(String lowerBound, String upperBound) {
+        return isGreaterThanAndNotEqualTo(lowerBound) && isLessThanAndNotEqualTo(upperBound);
+    }
+
+    /** parses a string expressed with common mathematical sematics,
+     * as either square brackets (inclusive), round brackets (exclusive), or one of each,
+     * surrounding a pair of version strings separated by a comma, where a version string 
+     * consists of any non-whitespace non-bracket characters 
+     * (ie numbers, letters, dots, hyphens, underscores) or is empty (to indicate no bound); 
+     * e.g. "[10.6,10.7)" to mean >= 10.6 and < 10.7;
+     * "[10.6,)" to mean >= 10.6.
+     */
+    public boolean isInRange(String range) {
+        String r = range.trim();
+        boolean strictLeft, strictRight;
+        
+        if (r.startsWith("(")) strictLeft = true;
+        else if (r.startsWith("[")) strictLeft = false;
+        else throw new IllegalArgumentException("Range must start with ( or [");
+        if (r.endsWith(")")) strictRight = true;
+        else if (r.endsWith("]")) strictRight = false;
+        else throw new IllegalArgumentException("Range must end with ) or ]");
+        
+        int i = r.indexOf(",");
+        if (i==-1) throw new IllegalArgumentException("Range must contain , following the open bracket and version");
+        String left = r.substring(1, i).trim();
+        String right = r.substring(i+1, r.length()-1).trim();
+        
+        if (left.length()>0) {
+            if (strictLeft && compareTo(left)<=0) return false; 
+            if (!strictLeft && compareTo(left)<0) return false; 
+        }
+        if (right.length()>0) {
+            if (strictRight && compareTo(right)>=0) return false; 
+            if (!strictRight && compareTo(right)>0) return false; 
+        }
+
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/FormattedString.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/FormattedString.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/FormattedString.java
new file mode 100644
index 0000000..cdb36f7
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/FormattedString.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+/** wraps a call to {@link String#format(String, Object...)} in a toString, i.e. using %s syntax,
+ * useful for places where we want deferred evaluation 
+ * (e.g. as message to {@link Preconditions} to skip concatenation when not needed) */
+public class FormattedString {
+    private final String pattern;
+    private final Object[] args;
+    public FormattedString(String pattern, Object[] args) {
+        this.pattern = pattern;
+        this.args = args;
+    }
+    @Override
+    public String toString() {
+        return String.format(pattern, args);
+    }
+    public String getPattern() {
+        return pattern;
+    }
+    public Object[] getArgs() {
+        return args;
+    }
+    public Supplier<String> supplier() {
+        return Strings.toStringSupplier(this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/Identifiers.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/Identifiers.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/Identifiers.java
new file mode 100644
index 0000000..8d3e035
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/Identifiers.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+import java.util.Random;
+
+public class Identifiers {
+    
+    private static Random random = new Random();
+    
+    public static final String JAVA_GOOD_START_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_";
+    public static final String JAVA_GOOD_NONSTART_CHARS = JAVA_GOOD_START_CHARS+"1234567890";
+    
+    public static final String JAVA_GENERATED_IDENTIFIER_START_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    public static final String JAVA_GENERATED_IDENTIFIERNONSTART_CHARS = JAVA_GENERATED_IDENTIFIER_START_CHARS+"1234567890";
+
+    public static final String BASE64_VALID_CHARS = JAVA_GENERATED_IDENTIFIERNONSTART_CHARS+"+=";
+    
+    public static final String ID_VALID_START_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    public static final String ID_VALID_NONSTART_CHARS = ID_VALID_START_CHARS+"1234567890";
+    
+    /** makes a random id string (letters and numbers) of the given length;
+     * starts with letter (upper or lower) so can be used as java-id;
+     * tests ensure random distribution, so random ID of length 5 
+     * is about 2^29 possibilities 
+     * <p>
+     * With ID of length 4 it is not unlikely (15% chance) to get
+     * duplicates in the first 2000 attempts.
+     * With ID of length 8 there is 1% chance to get duplicates
+     * in the first 1M attempts and 50% for the first 16M.
+     * <p>
+     * implementation is efficient, uses char array, and 
+     * makes one call to random per 5 chars; makeRandomId(5)
+     * takes about 4 times as long as a simple Math.random call,
+     * or about 50 times more than a simple x++ instruction;
+     * in other words, it's appropriate for contexts where random id's are needed,
+     * but use efficiently (ie cache it per object), and 
+     * prefer to use a counter where feasible
+     * <p>
+     * in general this is preferable to base64 as is more portable,
+     * can be used throughout javascript (as ID's which don't allow +)
+     * or as java identifiers (which don't allow numbers in the first char)
+     **/
+    public static String makeRandomId(int l) {
+        //this version is 30-50% faster than the old double-based one, 
+        //which computed a random every 3 turns --
+        //takes about 600 ns to do id of len 10, compared to 10000 ns for old version [on 1.6ghz machine]
+        if (l<=0) return "";
+        char[] id = new char[l];
+        int d = random.nextInt( (26+26) * (26+26+10) * (26+26+10) * (26+26+10) * (26+26+10));
+        int i = 0;    
+        id[i] = ID_VALID_START_CHARS.charAt(d % (26+26));
+        d /= (26+26);
+        if (++i<l) do {
+            id[i] = ID_VALID_NONSTART_CHARS.charAt(d%(26+26+10));
+            if (++i>=l) break;
+            if (i%5==0) {
+                d = random.nextInt( (26+26+10) * (26+26+10) * (26+26+10) * (26+26+10) * (26+26+10));
+            } else {
+                d /= (26+26+10);
+            }
+        } while (true);
+        //Message.message("random id is " + id);
+        return new String(id);
+    }
+
+    /** creates a short identifier comfortable in java and OS's, given an input hash code
+     * <p>
+     * result is always at least of length 1, shorter if the hash is smaller */ 
+    public static String makeIdFromHash(long d) {
+        StringBuffer result = new StringBuffer();
+        if (d<0) d=-d;
+        // correction for Long.MIN_VALUE
+        if (d<0) d=-(d+1000);
+        
+        result.append(ID_VALID_START_CHARS.charAt((int)(d % (26+26))));
+        d /= (26+26);
+        while (d!=0) {
+            result.append(ID_VALID_NONSTART_CHARS.charAt((int)(d%(26+26+10))));
+            d /= (26+26+10);
+        }
+        return result.toString();
+    }
+    
+    /** makes a random id string (letters and numbers) of the given length;
+     * starts with letter (upper or lower) so can be used as java-id;
+     * tests ensure random distribution, so random ID of length 5 
+     * is about 2^29 possibilities 
+     * <p>
+     * implementation is efficient, uses char array, and 
+     * makes one call to random per 5 chars; makeRandomId(5)
+     * takes about 4 times as long as a simple Math.random call,
+     * or about 50 times more than a simple x++ instruction;
+     * in other words, it's appropriate for contexts where random id's are needed,
+     * but use efficiently (ie cache it per object), and 
+     * prefer to use a counter where feasible
+     **/
+    public static String makeRandomJavaId(int l) {
+            // copied from Monterey util's com.cloudsoftcorp.util.StringUtils.
+            // TODO should share code with makeRandomId, just supplying different char sets (though the char sets in fact are the same..)
+
+            //this version is 30-50% faster than the old double-based one, 
+            //which computed a random every 3 turns --
+            //takes about 600 ns to do id of len 10, compared to 10000 ns for old version [on 1.6ghz machine]
+            if (l<=0) return "";
+            char[] id = new char[l];
+            int d = random.nextInt( (26+26) * (26+26+10) * (26+26+10) * (26+26+10) * (26+26+10));
+            int i = 0;    
+            id[i] = JAVA_GENERATED_IDENTIFIER_START_CHARS.charAt(d % (26+26));
+            d /= (26+26);
+            if (++i<l) do {
+                    id[i] = JAVA_GENERATED_IDENTIFIERNONSTART_CHARS.charAt(d%(26+26+10));
+                    if (++i>=l) break;
+                    if (i%5==0) {
+                            d = random.nextInt( (26+26+10) * (26+26+10) * (26+26+10) * (26+26+10) * (26+26+10));
+                    } else {
+                            d /= (26+26+10);
+                    }
+            } while (true);
+            //Message.message("random id is " + id);
+            return new String(id);
+    }
+
+    public static double randomDouble() {
+        return random.nextDouble();
+    }
+    public static long randomLong() {
+        return random.nextLong();
+    }
+    public static boolean randomBoolean() {
+        return random.nextBoolean();
+    }
+    public static int randomInt() {
+        return random.nextInt();
+    }
+    /** returns in [0,upbound) */
+    public static int randomInt(int upbound) {
+        return random.nextInt(upbound);
+    }
+    /** returns the array passed in */
+    public static byte[] randomBytes(byte[] buf) {
+        random.nextBytes(buf);
+        return buf;
+    }
+    public static byte[] randomBytes(int length) {
+        byte[] buf = new byte[length];
+        return randomBytes(buf);
+    }
+
+    public static String makeRandomBase64Id(int length) {
+        StringBuilder s = new StringBuilder();
+        while (length>0) {
+            appendBase64IdFromValueOfLength(randomLong(), length>10 ? 10 : length, s);
+            length -= 10;
+        }
+        return s.toString();
+    }
+    public static String getBase64IdFromValue(long value) {
+        return getBase64IdFromValue(value, 10);
+    }
+    public static String getBase64IdFromValue(long value, int length) {
+        StringBuilder s = new StringBuilder();
+        appendBase64IdFromValueOfLength(value, length, s);
+        return s.toString();
+    }
+    public static void appendBase64IdFromValueOfLength(long value, int length, StringBuffer sb) {
+        if (length>11)
+            throw new IllegalArgumentException("can't get a Base64 string longer than 11 chars from a long");
+        long idx = value;
+        for (int i=0; i<length; i++) {
+            byte x = (byte)(idx & 63);
+            sb.append(BASE64_VALID_CHARS.charAt(x));
+            idx = idx >> 6;
+        }
+    }
+    public static void appendBase64IdFromValueOfLength(long value, int length, StringBuilder sb) {
+        if (length>11)
+            throw new IllegalArgumentException("can't get a Base64 string longer than 11 chars from a long");
+        long idx = value;
+        for (int i=0; i<length; i++) {
+            byte x = (byte)(idx & 63);
+            sb.append(BASE64_VALID_CHARS.charAt(x));
+            idx = idx >> 6;
+        }
+    }
+    
+    public static boolean isValidToken(String token, String validStartChars, String validSubsequentChars) {
+        if (token==null || token.length()==0) return false;
+        if (validStartChars.indexOf(token.charAt(0))==-1) return false;
+        for (int i=1; i<token.length(); i++)
+            if (validSubsequentChars.indexOf(token.charAt(i))==-1) return false;
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/KeyValueParser.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/KeyValueParser.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/KeyValueParser.java
new file mode 100644
index 0000000..1e42b67
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/KeyValueParser.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.base.Splitter;
+
+
+/**
+ * Parses a String that consists of multiple arguments, which are either single or key-value pairs.
+ * The value may be in quotes.
+ * 
+ * For example:
+ *  a=x, b="x x", c, "d d"
+ *  
+ * Would return the ordered map:
+ *  "a" = "x"
+ *  "b" = "x x"
+ *  "c" = null
+ *  "d d" = null 
+ * 
+ * Consider instead using {@link Splitter#withKeyValueSeparator(char)}, but that doesn't give the
+ * same behaviour for values, see {@link QuotedStringTokenizer}. For example:
+ * <pre>
+ * {@code
+ * String val = "a=x,b=y";
+ * Map<String,String> map = Splitter.on(",").withKeyValueSeparator("=").split(val);
+ * }
+ * </pre>
+ * 
+ * @author aled
+ **/
+public class KeyValueParser {
+
+    public static String toLine(Map<String, String> parts) {
+        QuotedStringTokenizer tokenizer = new QuotedStringTokenizer("", true);
+        
+        StringBuilder result = new StringBuilder();
+        for (Map.Entry<String, String> entry : parts.entrySet()) {
+            if (result.length()>0) result.append(", ");
+            result.append(tokenizer.quoteToken(entry.getKey()));
+            if (entry.getValue() != null) result.append("="+tokenizer.quoteToken(entry.getValue()));
+        }
+        return result.toString();
+    }
+
+    public static String toLine(Collection<String> parts) {
+        QuotedStringTokenizer tokenizer = new QuotedStringTokenizer("", false);
+        
+        StringBuilder result = new StringBuilder();
+        for (String part : parts) {
+            result.append(tokenizer.quoteToken(part)+", ");
+        }
+        if (result.length() > 0) result.deleteCharAt(result.length()-1);
+        return result.toString();
+    }
+
+    public static List<String> parseList(String line) {
+        List<String> result = new ArrayList<String>();
+        QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(line, null, true, ",", false);
+        
+        while (tokenizer.hasMoreTokens()) {
+            result.add(tokenizer.unquoteToken(tokenizer.nextToken().trim()));
+        }
+        return result;
+    }
+    
+    @Deprecated // use parseMap
+    public static Map<String,String> parse(String line) {
+        return parseMap(line);
+    }
+    
+    /** takes a string of the form "key=value,key2=value2" and returns a map;
+     * values can be quoted (but not keys) */
+    public static Map<String,String> parseMap(String line) {
+        Map<String,String> result = new LinkedHashMap<String,String>();
+        
+        QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(line, null, true, ",", false);
+        
+        while (tokenizer.hasMoreTokens()) {
+            //String token = tokenizer.unquoteToken(tokenizer.nextToken().trim());
+            String token = tokenizer.nextToken().trim();
+            
+            int index = token.indexOf("=");
+            
+            if (index < 0) {
+                String unquotedKey = tokenizer.unquoteToken(token);
+                result.put(unquotedKey, null);
+                
+            } else if (index < (token.length()-1)) {
+                String unquotedKey = tokenizer.unquoteToken(token.substring(0, index).trim());
+                String unquotedVal = tokenizer.unquoteToken(token.substring(index+1).trim());
+                result.put(unquotedKey, unquotedVal);
+                
+            } else { // ends with =
+                assert index == token.length() -1;
+                String unquotedKey = tokenizer.unquoteToken(token.substring(0, index).trim());
+                result.put(unquotedKey, "");
+            }
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/NaturalOrderComparator.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/NaturalOrderComparator.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/NaturalOrderComparator.java
new file mode 100644
index 0000000..be53acc
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/NaturalOrderComparator.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * 
+
+BROOKLYN NOTE: This is based on code from Pierre-Luc Paour,
+adapted for the Brooklyn project in accordance with the original terms below.
+Main changes are the package and edits for more recent Java compatibility.
+
+--
+
+NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java.
+Copyright (C) 2003 by Pierre-Luc Paour <natorder@paour.com>
+
+Based on the C version by Martin Pool, of which this is more or less a straight conversion.
+Copyright (C) 2000 by Martin Pool <mbp@humbug.org.au>
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+ */
+package org.apache.brooklyn.util.text;
+
+import java.util.Comparator;
+
+/** comparator which takes two strings and puts them in an order with special rules for numbers to be placed in numeric order;
+ * e.g. "10">"9", including when those numbers occur in the midst of equal text; e.g. "a10" > "a9";
+ * but not if the text differs; e.g. "a10" < "b9"
+ * <p>
+ * class is thread-safe. nulls not supported. (to support nulls, wrap in guava:
+ * <code>Ordering.from(NaturalOrderComparator.INSTANCE).nullsFirst()</code>)
+ */
+public class NaturalOrderComparator implements Comparator<String> {
+    
+    public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
+    
+    int compareRight(String a, String b)
+    {
+        int bias = 0;
+        int ia = 0;
+        int ib = 0;
+
+        // The longest run of digits wins.  That aside, the greatest
+        // value wins, but we can't know that it will until we've scanned
+        // both numbers to know that they have the same magnitude, so we
+        // remember it in BIAS.
+        for (;; ia++, ib++) {
+            char ca = charAt(a, ia);
+            char cb = charAt(b, ib);
+
+            if (!Character.isDigit(ca)
+                    && !Character.isDigit(cb)) {
+                return bias;
+            } else if (!Character.isDigit(ca)) {
+                return -1;
+            } else if (!Character.isDigit(cb)) {
+                return +1;
+            } else if (ca < cb) {
+                if (bias == 0) {
+                    bias = -1;
+                }
+            } else if (ca > cb) {
+                if (bias == 0)
+                    bias = +1;
+            } else if (ca == 0 && cb == 0) {
+                return bias;
+            }
+        }
+    }
+
+    public int compare(String a, String b) {
+
+        int ia = 0, ib = 0;
+        int nza = 0, nzb = 0;
+        char ca, cb;
+        int result;
+
+        while (true) {
+            // only count the number of zeroes leading the last number compared
+            nza = nzb = 0;
+
+            ca = charAt(a, ia); cb = charAt(b, ib);
+
+            // skip over leading spaces or zeros
+            while (Character.isSpaceChar(ca) || ca == '0') {
+                if (ca == '0') {
+                    nza++;
+                } else {
+                    // only count consecutive zeroes
+                    nza = 0;
+                }
+
+                ca = charAt(a, ++ia);
+            }
+
+            while (Character.isSpaceChar(cb) || cb == '0') {
+                if (cb == '0') {
+                    nzb++;
+                } else {
+                    // only count consecutive zeroes
+                    nzb = 0;
+                }
+
+                cb = charAt(b, ++ib);
+            }
+
+            // process run of digits
+            if (Character.isDigit(ca) && Character.isDigit(cb)) {
+                if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) {
+                    return result;
+                }
+            }
+
+            if (ca == 0 && cb == 0) {
+                // The strings compare the same.  Perhaps the caller
+                // will want to call strcmp to break the tie.
+                return nza - nzb;
+            }
+
+            if (ca < cb) {
+                return -1;
+            } else if (ca > cb) {
+                return +1;
+            }
+
+            ++ia; ++ib;
+        }
+    }
+
+    static char charAt(String s, int i) {
+        if (i >= s.length()) {
+            return 0;
+        } else {
+            return s.charAt(i);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/QuotedStringTokenizer.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/QuotedStringTokenizer.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/QuotedStringTokenizer.java
new file mode 100644
index 0000000..0a0ade9
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/QuotedStringTokenizer.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/** As 'StringTokenizer' but items in quotes (single or double) are treated as single tokens
+ * (cf mortbay's QuotedStringTokenizer) 
+ */  
+public class QuotedStringTokenizer {
+
+    final StringTokenizer delegate;
+    final String quoteChars;
+    final boolean includeQuotes;
+    final String delimiters;
+    final boolean includeDelimiters;
+
+    public static String DEFAULT_QUOTE_CHARS = "\"\'";
+    
+    
+    protected String DEFAULT_QUOTE_CHARS() {
+        return DEFAULT_QUOTE_CHARS;
+    }
+    
+    public final static String DEFAULT_DELIMITERS = " \t\n\r\f";    
+    
+    /** default quoted tokenizer, using single and double quotes as quote chars and returning quoted results
+     * (use unquoteToken to unquote), and using whitespace chars as delimeters (not included as tokens);
+     * string may be null if the nothing will be tokenized and the class is used only for
+     * quoteToken(String) and unquote(String).
+     */
+    public QuotedStringTokenizer(String stringToTokenize) {
+        this(stringToTokenize, true);
+    }
+    public QuotedStringTokenizer(String stringToTokenize, boolean includeQuotes) {
+        this(stringToTokenize, null, includeQuotes);
+    }
+    public QuotedStringTokenizer(String stringToTokenize, String quoteChars, boolean includeQuotes) {
+        this(stringToTokenize, quoteChars, includeQuotes, null, false);
+    }
+
+    public QuotedStringTokenizer(String stringToTokenize, String quoteChars, boolean includeQuotes, String delimiters, boolean includeDelimiters) {
+        delegate = new StringTokenizer(stringToTokenize==null ? "" : stringToTokenize, (delimiters==null ? DEFAULT_DELIMITERS : delimiters), true);
+        this.quoteChars = quoteChars==null ? DEFAULT_QUOTE_CHARS() : quoteChars;
+        this.includeQuotes = includeQuotes;
+        this.delimiters = delimiters==null ? DEFAULT_DELIMITERS : delimiters;
+        this.includeDelimiters = includeDelimiters;
+        updateNextToken();
+    }
+    
+    public static class Builder {
+        private String quoteChars = DEFAULT_QUOTE_CHARS;
+        private boolean includeQuotes=true;
+        private String delimiterChars=DEFAULT_DELIMITERS;
+        private boolean includeDelimiters=false;
+
+        public QuotedStringTokenizer build(String stringToTokenize) {
+            return new QuotedStringTokenizer(stringToTokenize, quoteChars, includeQuotes, delimiterChars, includeDelimiters);
+        }
+        public List<String> buildList(String stringToTokenize) {
+            return new QuotedStringTokenizer(stringToTokenize, quoteChars, includeQuotes, delimiterChars, includeDelimiters).remainderAsList();
+        }
+        
+        public Builder quoteChars(String quoteChars) { this.quoteChars = quoteChars; return this; }
+        public Builder addQuoteChars(String quoteChars) { this.quoteChars = this.quoteChars + quoteChars; return this; }
+        public Builder includeQuotes(boolean includeQuotes) { this.includeQuotes = includeQuotes; return this; } 
+        public Builder delimiterChars(String delimiterChars) { this.delimiterChars = delimiterChars; return this; }
+        public Builder addDelimiterChars(String delimiterChars) { this.delimiterChars = this.delimiterChars + delimiterChars; return this; }
+        public Builder includeDelimiters(boolean includeDelimiters) { this.includeDelimiters = includeDelimiters; return this; } 
+    }
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    String peekedNextToken = null;
+    
+    public synchronized boolean hasMoreTokens() {
+        return peekedNextToken!=null;
+    }
+    
+    public synchronized String nextToken() {    
+        if (peekedNextToken==null) throw new NoSuchElementException();
+        String lastToken = peekedNextToken;
+        updateNextToken();
+        return includeQuotes ? lastToken : unquoteToken(lastToken);
+    }
+
+    /** this method removes all unescaped quote chars, i.e. quote chars preceded by no backslashes (or a larger even number of them);
+     * it also unescapes '\\' as '\'.  it does no other unescaping.  */
+    public String unquoteToken(String word) {
+        // ( (\\A|[^\\\\]) (\\\\\\\\)* ) [ Pattern.quote(quoteChars) ]  $1
+        word = word.replaceAll(
+                "((\\A|[^\\\\])(\\\\\\\\)*)["+
+                    //Pattern.quote(
+                        quoteChars
+                    //)
+                        +"]+",
+                "$1");
+        //above pattern removes any quote preceded by even number of backslashes
+        //now it is safe to replace any \c by c
+        word = word.replaceAll("\\\\"+"([\\\\"+
+                //Pattern.quote(
+                quoteChars
+                //)
+                +"])", "$1");
+                
+        return word;
+    }
+    
+    /** returns the input text escaped for use with unquoteTokens, and wrapped in the quoteChar[0] (usu a double quote) */
+    public String quoteToken(String unescapedText) {
+        String result = unescapedText;
+        //replace every backslash by two backslashes
+        result = result.replaceAll("\\\\", "\\\\\\\\");
+        //now replace every quote char by backslash quote char
+        result = result.replaceAll("(["+quoteChars+"])", "\\\\$1");
+        //then wrap in quote
+        result = quoteChars.charAt(0) + result + quoteChars.charAt(0);
+        return result;
+    }
+
+    protected synchronized void updateNextToken() {
+        peekedNextToken = null;
+        String token;
+        do {
+            if (!delegate.hasMoreTokens()) return;
+            token = delegate.nextToken();
+            //skip delimeters
+        } while (!includeDelimiters && token.matches("["+delimiters+"]+"));
+        
+        StringBuffer nextToken = new StringBuffer(token);
+        pullUntilValid(nextToken);
+        peekedNextToken = nextToken.toString();
+    }
+
+    private void pullUntilValid(StringBuffer nextToken) {
+        while (hasOpenQuote(nextToken.toString(), quoteChars) && delegate.hasMoreTokens()) {
+            //keep appending until the quote is ended or there are no more quotes
+            nextToken.append(delegate.nextToken());
+        }
+    }
+
+    public static boolean hasOpenQuote(String stringToCheck) {
+        return hasOpenQuote(stringToCheck, DEFAULT_QUOTE_CHARS);
+    }
+
+    public static boolean hasOpenQuote(String stringToCheck, String quoteChars) {        
+        String x = stringToCheck;
+        if (x==null) return false;
+
+        StringBuffer xi = new StringBuffer();
+        for (int i=0; i<x.length(); i++) {
+            char c = x.charAt(i);
+            if (c=='\\') i++;
+            else if (quoteChars.indexOf(c)>=0) {
+                xi.append(c);
+            }
+        }
+        x = xi.toString();
+        
+        while (x.length()>0) {
+            char c = x.charAt(0);
+            int match = x.indexOf(c, 1);
+            if (match==-1) return true;
+            x = x.substring(match+1);
+        }
+        return false;
+    }
+
+    public List<String> remainderAsList() {
+        List<String> l = new ArrayList<String>();
+        while (hasMoreTokens())
+            l.add(nextToken());
+        return l;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java
new file mode 100644
index 0000000..276d0fb
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringEscapes.java
@@ -0,0 +1,412 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.net.URLParamEncoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StringEscapes {
+
+    private static final Logger log = LoggerFactory.getLogger(StringEscapes.class);
+    
+    /** if s is wrapped in double quotes containing no unescaped double quotes */
+    public static boolean isWrappedInDoubleQuotes(String s) {
+        if (Strings.isEmpty(s)) return false;
+        if (!s.startsWith("\"") || !s.endsWith("\"")) return false;
+        return (s.substring(1, s.length()-1).replace("\\\\", "").replace("\\\"", "").indexOf("\"")==-1);
+    }
+
+    /** if s is wrapped in single quotes containing no unescaped single quotes */
+    public static boolean isWrappedInSingleQuotes(String s) {
+        if (Strings.isEmpty(s)) return false;
+        if (!s.startsWith("\'") || !s.endsWith("\'")) return false;
+        return (s.substring(1, s.length()-1).replace("\\\\", "").replace("\\\'", "").indexOf("\'")==-1);
+    }
+
+    /** if s is wrapped in single or double quotes containing no unescaped quotes of that type */
+    public static boolean isWrappedInMatchingQuotes(String s) {
+        return isWrappedInDoubleQuotes(s) || isWrappedInSingleQuotes(s);
+    }
+
+    /**
+     * Encodes a string suitable for use as a parameter in a URL.
+     */
+    public static String escapeUrlParam(String input) {
+        return URLParamEncoder.encode(input);
+    }
+
+    /** 
+     * Encodes a string suitable for use as a URL in an HTML form: space to +, and high-numbered chars assuming UTF-8.
+     * However, it will also convert the first "http://" to "http%3A%2F%2F" so is not suitable for converting an 
+     * entire URL.
+     * 
+     * Also note that parameter-conversion doesn't work in way you'd expect when trying to create a "normal" url.
+     * See http://stackoverflow.com/questions/724043/http-url-address-encoding-in-java
+     * 
+     * @see escapeUrlParam(String), and consider using that instead.
+     */
+    public static String escapeHtmlFormUrl(String url) {
+        try {
+            return URLEncoder.encode(url, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    /** encodes a string to SQL, that is ' becomes '' */
+    public static String escapeSql(String x) {
+        //identical to apache commons StringEscapeUtils.escapeSql
+        if (x==null) return null;
+        return x.replaceAll("'", "''");
+    }
+    
+    
+    
+    public static class BashStringEscapes {
+        // single quotes don't permit escapes!  e.g. echo 'hello \' world' doesn't work;
+        // you must do 'hello '\'' world' (to get "hello ' world")
+        
+        /** wraps plain text in double quotes escaped for use in bash double-quoting */
+        public static String wrapBash(String value) {
+            StringBuilder out = new StringBuilder();
+            try {
+                wrapBash(value, out);
+            } catch (IOException e) {
+                //shouldn't happen for string buffer
+                throw Exceptions.propagate(e);
+            }
+            return out.toString();
+        }
+
+        /** @see #wrapBash(String) */
+        public static void wrapBash(String value, Appendable out) throws IOException {
+            out.append('"');
+            escapeLiteralForDoubleQuotedBash(value, out);
+            out.append('"');
+        }
+
+        private static void escapeLiteralForDoubleQuotedBash(String value, Appendable out) throws IOException {
+            for (int i=0; i<value.length(); i++) {
+                char c = value.charAt(i);
+                if (c=='\\' || c=='\"' || c=='$' || c=='`') {
+                    appendEscaped(out, c);
+                } else if (c == '!') {
+                    out.append("\"'!'\"");
+                } else {
+                    out.append(c);
+                }
+            }
+        }
+
+        /** performs replacements on a string so that it can be legally inserted into a double-quoted bash context 
+         * (without the surrounding double quotes; see also {@link #wrapBash(String)}) */
+        public static String escapeLiteralForDoubleQuotedBash(String unquotedInputToBeEscaped) {
+            StringBuilder out = new StringBuilder();
+            try {
+                escapeLiteralForDoubleQuotedBash(unquotedInputToBeEscaped, out);
+            } catch (IOException e) {
+                // shouldn't happen for StringBuilder
+                throw Exceptions.propagate(e);
+            }
+            return out.toString();
+        }
+
+        /** transforms e.g. [ "-Dname=Bob Johnson", "-Dnet.worth=$100" ]  to 
+         * a java string "\"-Dname=Bob Johnson\" \"-Dnet.worth=\$100\"" --
+         * which can be inserted into a bash command where it will be picked up as 2 params
+         */
+        public static String doubleQuoteLiteralsForBash(String... args) {
+            StringBuilder result = new StringBuilder();
+            for (String arg: args) {
+                if (!Strings.isEmpty(result)) result.append(" ");
+                result.append("\"");
+                result.append(escapeLiteralForDoubleQuotedBash(arg));
+                result.append("\"");
+            }
+            return result.toString();
+        }
+
+        //between java and regex parsing, this gives a single backslash and double quote
+        private static final String BACKSLASH = "\\\\";
+        private static final String DOUBLE_QUOTE = "\\\"";
+
+        public static boolean isValidForDoubleQuotingInBash(String x) {
+            return (checkValidForDoubleQuotingInBash(x)==null);
+        }
+
+        public static void assertValidForDoubleQuotingInBash(String x) {
+            String problem = checkValidForDoubleQuotingInBash(x);
+            if (problem==null) return;
+            throw new IllegalArgumentException("String \""+x+"\" not acceptable for bash argument (including double quotes): "+problem);
+        }
+
+        private static String checkValidForDoubleQuotingInBash(String x) {
+            //double quotes must be preceded by a backslash (preceded by 0 or more bash-escaped backslashes)
+            if (x.matches(  "[^"+BACKSLASH+DOUBLE_QUOTE+"]*"+
+                    "("+BACKSLASH+BACKSLASH+")*"+
+                    DOUBLE_QUOTE+".*")) return "unescaped double quote";
+            return null;
+        }
+
+        /** given a string in bash notation, e.g. with quoted portions needing unescaped, returns the unescaped and unquoted version */
+        public static String unwrapBashQuotesAndEscapes(String s) {
+            return applyUnquoteAndUnescape(s, "Bash", true);
+        }
+    }
+    
+    
+    public static class JavaStringEscapes {
+        /** converts normal string to java escaped for double-quotes (but not wrapped in double quotes) */
+        public static String escapeJavaString(String value) {
+            StringBuilder out = new StringBuilder();
+            try {
+                escapeJavaString(value, out);
+            } catch (IOException e) {
+                //shouldn't happen for string builder
+                throw Exceptions.propagate(e);
+            }
+            return out.toString();
+        }
+
+        /** converts normal string to java escaped for double-quotes and wrapped in those double quotes */
+        public static String wrapJavaString(String value) {
+            StringBuilder out = new StringBuilder();
+            try {
+                wrapJavaString(value, out);
+            } catch (IOException e) {
+                //shouldn't happen for string builder
+                throw Exceptions.propagate(e);
+            }
+            return out.toString();
+        }
+        public static List<String> wrapJavaStrings(Iterable<String> values) {
+            if (values==null) return null;
+            List<String> result = MutableList.of();
+            for (String v: values) result.add(wrapJavaString(v));
+            return result;
+        }
+
+        /** as {@link #unwrapJavaString(String)} if the given string is wrapped in double quotes;
+         * otherwise just returns the given string */
+        public static String unwrapJavaStringIfWrapped(String s) {
+            if (!StringEscapes.isWrappedInDoubleQuotes(s)) return s;
+            return unwrapJavaString(s);
+        }
+
+        /** converts normal string to java escaped for double-quotes and wrapped in those double quotes */
+        public static void wrapJavaString(String value, Appendable out) throws IOException {
+            if (value==null) {
+                out.append("null");
+            } else {
+                out.append('"');
+                escapeJavaString(value, out);
+                out.append('"');
+            }
+        }
+
+        /** converts normal string to java escaped for double-quotes (but not wrapped in double quotes) */
+        public static void escapeJavaString(@Nonnull String value, Appendable out) throws IOException {
+            for (int i=0; i<value.length(); i++) {
+                char c = value.charAt(i);
+                if (c=='\\' || c=='"') {
+                    // NB do NOT escape single quotes; while valid for java, it is not in JSON (breaks jQuery.parseJSON)
+                    appendEscaped(out, c);
+                } else if (c=='\n') {
+                    appendEscaped(out, 'n');
+                } else if (c=='\t') {
+                    appendEscaped(out, 't');
+                } else if (c=='\r') {
+                    appendEscaped(out, 'r');
+                } else {
+                    out.append(c);
+                }
+            }
+        }
+
+        /** given a string in java syntax, e.g. wrapped in quotes and with backslash escapes, returns the literal value,
+         * without the surrounding quotes and unescaped; throws IllegalArgumentException if not a valid java string */
+        public static String unwrapJavaString(String s) {
+            return applyUnquoteAndUnescape(s, "Java", false);
+        }
+        
+        /**
+         * Unwraps a sequence of quoted java strings, that are each separated by the given separator.
+         * @param trimmedArg
+         * @return
+         */
+        public static List<String> unwrapQuotedJavaStringList(String s, String separator) {
+            List<String> result = new ArrayList<String>();
+            String remaining = s.trim();
+            while (remaining.length() > 0) {
+                int endIndex = findNextClosingQuoteOf(remaining);
+                result.add(unwrapJavaString(remaining.substring(0, endIndex+1)));
+                remaining = remaining.substring(endIndex+1).trim();
+                if (remaining.startsWith(separator)) {
+                    remaining = remaining.substring(separator.length()).trim();
+                } else if (remaining.length() > 0) {
+                    throw new IllegalArgumentException("String '"+s+"' has invalid separators, should be '"+separator+"'");
+                }
+            }
+            return result;
+        }
+        private static int findNextClosingQuoteOf(String s) {
+            boolean escaped = false;
+            boolean quoted = false;
+            for (int i=0; i<s.length(); i++) {
+                char c = s.charAt(i);
+                if (!quoted) {
+                    assert (i==0);
+                    assert !escaped;
+                    if (c=='"') quoted = true;
+                    else throw new IllegalArgumentException("String '"+s+"' is not a valid Java string (must start with double quote)");
+                } else {
+                    if (escaped) {
+                        escaped = false;
+                    } else {
+                        if (c=='\\') escaped = true;
+                        else if (c=='\"') {
+                            quoted = false;
+                            return i;
+                        } 
+                    }
+                }
+            }
+            
+            assert quoted;
+            throw new IllegalArgumentException("String '"+s+"' is not a valid Java string (unterminated string)");
+        }
+        
+        /** converts a comma separated list in a single string to a list of strings, 
+         * doing what would be expected if given java or json style string as input,
+         * and falling back to returning the input.
+         * <p>
+         * this method does <b>not</b> throw exceptions on invalid input,
+         * but just returns that input
+         * <p>
+         * specifically, uses the following rules (executed once in sequence:
+         * <li> 1) if of form <code>[ X ]</code> (in brackets after trim), 
+         *      then removes brackets and applies following rules to X (for any X including quoted or with commas)
+         * <li> 2) if of form <code>"X"</code> 
+         *      (in double quotes after trim, 
+         *      where X contains no internal double quotes unless escaped with backslash) 
+         *      then returns list containing X unescaped (\x replaced by x)
+         * <li> 3) if of form <code>X</code> or <code>X, Y, ...</code> 
+         *      (where X, Y, ... each satisfy the constraint given in 2, or have no double quotes or commas in them)
+         *      then returns the concatenation of rule 2 applied to non-empty X, Y, ...
+         *      (if you want an empty string in a list, you must double quote it)
+         * <li> 4) for any other form X returns [ X ], including empty list for empty string
+         * <p>
+         * @see #unwrapOptionallyQuotedJavaStringList(String)
+         **/
+        public static List<String> unwrapJsonishListIfPossible(String input) {
+            try {
+                return unwrapOptionallyQuotedJavaStringList(input);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (e instanceof IllegalArgumentException) {
+                    if (log.isDebugEnabled()) 
+                        log.debug("Unable to parse JSON list '"+input+"' ("+e+"); treating as single-element string list");
+                } else {
+                    log.warn("Unable to parse JSON list '"+input+"' ("+e+"); treating as single-element string list", e);
+                }
+                return MutableList.of(input);
+            }
+        }
+        
+        /** as {@link #unwrapJsonishListIfPossible(String)} but throwing errors 
+         * if something which looks like a string or set of brackets is not well-formed
+         * (this does the work for that method) 
+         * @throws IllegalArgumentException if looks to have quoted list or surrounding brackets but they are not syntactically valid */
+        public static List<String> unwrapOptionallyQuotedJavaStringList(String input) {
+            if (input==null) return null;
+            MutableList<String> result = MutableList.of();
+            String i1 = input.trim();
+            
+            boolean inBrackets = (i1.startsWith("[") && i1.endsWith("]"));
+            if (inBrackets) i1 = i1.substring(1, i1.length()-2).trim();
+                
+            QuotedStringTokenizer qst = new QuotedStringTokenizer(i1, "\"", true, ",", false);
+            while (qst.hasMoreTokens()) {
+                String t = qst.nextToken().trim();
+                if (isWrappedInDoubleQuotes(t))
+                    result.add(unwrapJavaString(t));
+                else {
+                    if (inBrackets && (t.indexOf('[')>=0 || t.indexOf(']')>=0))
+                        throw new IllegalArgumentException("Literal square brackets must be quoted, in element '"+t+"'");
+                    result.add(t.trim());
+                }
+            }
+            
+            return result;
+        }
+    }
+    
+    private static void appendEscaped(Appendable out, char c) throws IOException {
+        out.append('\\');
+        out.append(c);
+    }
+    private static String applyUnquoteAndUnescape(String s, String mode, boolean allowMultipleQuotes) {
+        StringBuilder result = new StringBuilder();
+        boolean escaped = false;
+        boolean quoted = false;
+        for (int i=0; i<s.length(); i++) {
+            char c = s.charAt(i);
+            if (!quoted) {
+                assert (i==0 || allowMultipleQuotes);
+                assert !escaped;
+                if (c=='"') quoted = true;
+                else if (!allowMultipleQuotes)
+                    throw new IllegalArgumentException("String '"+s+"' is not a valid "+mode+" string (must start with double quote)");
+                else result.append(c);
+            } else {
+                if (escaped) {
+                    if (c=='\\' || c=='"' || c=='\'') result.append(c);
+                    else if (c=='n') result.append('\n');
+                    else if (c=='t') result.append('\t');
+                    else if (c=='r') result.append('\r');
+                    else throw new IllegalArgumentException("String '"+s+"' is not a valid "+mode+" string (unsupported escape char '"+c+"' at position "+i+")");
+                    escaped = false;
+                } else {
+                    if (c=='\\') escaped = true;
+                    else if (c=='\"') {
+                        quoted = false;
+                        if (!allowMultipleQuotes && i<s.length()-1)
+                            throw new IllegalArgumentException("String '"+s+"' is not a valid "+mode+" string (unescaped interior double quote at position "+i+")");
+                    } else result.append(c); 
+                }
+            }
+        }
+        if (quoted)
+            throw new IllegalArgumentException("String '"+s+"' is not a valid "+mode+" string (unterminated string)");
+        assert !escaped;
+        return result.toString();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java
new file mode 100644
index 0000000..3eec640
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.util.text;
+
+import javax.annotation.Nullable;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+
+public class StringFunctions {
+
+    public static Function<String,String> append(final String suffix) {
+        return new Function<String, String>() {
+            @Override
+            @Nullable
+            public String apply(@Nullable String input) {
+                if (input==null) return null;
+                return input + suffix;
+            }
+        };
+    }
+
+    public static Function<String,String> prepend(final String prefix) {
+        return new Function<String, String>() {
+            @Override
+            @Nullable
+            public String apply(@Nullable String input) {
+                if (input==null) return null;
+                return prefix + input;
+            }
+        };
+    }
+
+    /** given e.g. "hello %s" returns a function which will insert a string into that pattern */
+    public static Function<Object, String> formatter(final String pattern) {
+        return new Function<Object, String>() {
+            public String apply(@Nullable Object input) {
+                return String.format(pattern, input);
+            }
+        };
+    }
+
+    /** given e.g. "hello %s %s" returns a function which will insert an array of two strings into that pattern */
+    public static Function<Object[], String> formatterForArray(final String pattern) {
+        return new Function<Object[], String>() {
+            public String apply(@Nullable Object[] input) {
+                return String.format(pattern, input);
+            }
+        };
+    }
+
+    /** joins the given objects in a collection as a toString with the given separator */
+    public static Function<Iterable<?>, String> joiner(final String separator) {
+        return new Function<Iterable<?>, String>() {
+            public String apply(@Nullable Iterable<?> input) {
+                return Strings.join(input, separator);
+            }
+        };
+    }
+
+    /** joins the given objects as a toString with the given separator, but expecting an array of objects, not a collection */
+    public static Function<Object[], String> joinerForArray(final String separator) {
+        return new Function<Object[], String>() {
+            public String apply(@Nullable Object[] input) {
+                if (input == null) return Strings.EMPTY;
+                return Strings.join(input, separator);
+            }
+        };
+    }
+
+    /** provided here as a convenience; prefer {@link Functions#toStringFunction()} */
+    public static Function<Object,String> toStringFunction() {
+        return Functions.toStringFunction();
+    }
+
+    /** returns function which gives length of input, with -1 for nulls */
+    public static Function<String,Integer> length() {
+        return new Function<String,Integer>() {
+            @Override
+            public Integer apply(@Nullable String input) {
+                if (input == null) return -1;
+                return input.length();
+            }
+        };
+    }
+
+    /** Surrounds an input string with the given prefix and suffix */
+    public static Function<String,String> surround(final String prefix, final String suffix) {
+        Preconditions.checkNotNull(prefix);
+        Preconditions.checkNotNull(suffix);
+        return new Function<String,String>() {
+            @Override
+            public String apply(@Nullable String input) {
+                if (input == null) return null;
+                return prefix+input+suffix;
+            }
+        };
+    }
+
+    public static Function<String, String> trim() {
+        return new Function<String, String>() {
+            @Override
+            public String apply(@Nullable String input) {
+                if (input == null) return null;
+                if (Strings.isBlank(input)) return Strings.EMPTY;
+                return CharMatcher.BREAKING_WHITESPACE.trimFrom(input);
+            }
+        };
+    }
+
+    public static Function<String, String> toLowerCase() {
+        return new Function<String, String>() {
+            @Override
+            public String apply(String input) {
+                return input.toLowerCase();
+            }
+        };
+    }
+
+    public static Function<String, String> toUpperCase() {
+        return new Function<String, String>() {
+            @Override
+            public String apply(String input) {
+                return input.toUpperCase();
+            }
+        };
+    }
+
+    public static Function<String, String> convertCase(final CaseFormat src, final CaseFormat target) {
+        return new Function<String, String>() {
+            @Override
+            public String apply(String input) {
+                return src.to(target, input);
+            }
+        };
+    }
+
+}


Mime
View raw message