All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.brooklyn.util.text.StringEscapes Maven / Gradle / Ivy

Go to download

Utility classes and methods developed for Brooklyn but not dependendent on Brooklyn or much else

There is a newer version: 1.1.0
Show newest version
/*
 * 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 java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;

import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.net.URLParamEncoder;
import org.apache.brooklyn.util.yaml.Yamls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;

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")

        public static class WrapBashFunction implements Function {
            @Override
            public String apply(String input) {
                return wrapBash(input);
            }
        }
        public static Function wrapBash() {
            return new WrapBashFunction();
        }

        /** 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 wrapJavaStrings(Iterable values) {
            if (values==null) return null;
            List 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 unwrapQuotedJavaStringList(String s, String separator) {
            List result = new ArrayList();
            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 unwrapJsonishListIfPossible(String input) {
            return unwrapJsonishListStringIfPossible(input);
        }

        /** converts a comma separated list in a single string to a list of json primitives or maps, 
         * falling back to returning the input.
         * 

* specifically: *

  • 1) if of form [ X ] (in brackets after trim), parse as YAML; * if parse succeeds return the result, or if parse fails, return {@link Maybe#absent()}. * 2) if not of form [ X ], wrap in brackets and parse as YAML, * and if that succeeds and is a list, return the result. *
  • 3) otherwise, expect comma-separated tokens which after trimming are of the form "A" or B, * where "A" is a valid Java string or B is any string not containing any of the chars ",. * and not starting with ', and returns the list of those tokens, where A is * returned as its string value, and B as a primitive if it is a number or boolean or null, * or else a string (including the empty string if empty) *
  • 4) if such tokens are not found, return {@link Maybe#absent()}. *

    * @see #unwrapOptionallyQuotedJavaStringList(String) **/ public static Maybe> tryUnwrapJsonishList(String input) { if (input==null) return Maybe.absent("null input cannot unwrap to a list"); String inputT = input.trim(); String inputYaml = null; if (!inputT.startsWith("[") && !inputT.endsWith("]")) { inputYaml = "[" + inputT + "]"; } if (inputT.startsWith("[") && inputT.endsWith("]")) { inputYaml = inputT; } if (inputYaml!=null) { try { Object r = Iterables.getOnlyElement( Yamls.parseAll(inputYaml) ); if (r instanceof List) { @SuppressWarnings("unchecked") List result = (List)r; return Maybe.of(result); } } catch (Exception e) { Exceptions.propagateIfFatal(e); // Otherwise ignore; logic below decides whether to return absent or to keep trying } if (inputT.startsWith("[")) { // if supplied as yaml, don't allow failures return Maybe.absent("Supplied format looked like YAML but could not parse as YAML"); } } List result = MutableList.of(); // double quote: ^ \s* " ([not quote or backslash] or [backslash any-char])* " \s* (, or $) Pattern dq = Pattern.compile("^\\s*(\"([^\"\\\\]|[\\\\.])*\")\\s*(,|$)"); // could also support this, but we need new unescape routines // // single quote: ^ \s* ' ([not quote or backslash] or [backslash any-char])* ' \s* (, or $) // Pattern sq = Pattern.compile("^\\s*'([^\'\\\\]|[\\\\.])'*\\s*(,|$)"); // no quote: ^ \s* (empty, or [not ' or " or space] ([not , or "]* [not , or " or space])?) \s* (, or $) Pattern nq = Pattern.compile("^\\s*(|[^,\"\\s]([^,\"]*[^,\"\\s])?)\\s*(,|$)"); int removedChars = 0; while (true) { Object ri; Matcher m = dq.matcher(input); if (m.find()) { try { ri = unwrapJavaString(m.group(1)); } catch (Exception e) { Exceptions.propagateIfFatal(e); return Maybe.absent("Could not match valid quote pattern" + (removedChars>0 ? " at position "+removedChars : "") + ": "+ Exceptions.collapseText(e)); } } else { m = nq.matcher(input); if (m.find()) { String w = m.group(1); ri = w; if (w.matches("[0-9]*\\.[0-9]+")) { try { ri = Double.parseDouble(w); } catch (Exception e) {} } else if (w.matches("[0-9]+")) { try { ri = Integer.parseInt(w); } catch (Exception e) {} } else if (Boolean.TRUE.toString().equals(w)) { ri = true; } else if (Boolean.FALSE.toString().equals(w)) { ri = false; } else if ("null".equals(w)) { ri = null; } } else { return Maybe.absent("Could not match valid quote pattern" + (removedChars>0 ? " at position "+removedChars : "")); } } result.add(ri); input = input.substring(m.end()); removedChars += m.end(); if (!m.group(0).endsWith(",")) break; } return Maybe.of(result); } /** 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. *

    * this method does not throw exceptions on invalid input, * but just returns that input wrapped in a list *

    * specifically, uses the following rules (executed once in sequence: *

  • 1) if of form [ X ] (in brackets after trim), * then removes brackets and applies following rules to X (for any X including quoted or with commas) *
  • 2) if of form "X" * (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) *
  • 3) if of form X or X, Y, ... * (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) *
  • 4) for any other form X returns [ X ], including empty list for empty string *

    * @see #unwrapOptionallyQuotedJavaStringList(String) **/ public static List unwrapJsonishListStringIfPossible(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 unwrapOptionallyQuotedJavaStringList(String input) { if (input==null) return null; MutableList result = MutableList.of(); String i1 = input.trim(); boolean inBrackets = (i1.startsWith("[") && i1.endsWith("]")); if (inBrackets) i1 = i1.substring(1, i1.length()-1).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