/** * (C) Copyright 2004-2005 Mort Bay Consulting Pty. Ltd. * * Parts of this code was taken from the Jetty project, which can be * found at http://www.mortbay.org/jetty * * 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. */ // ======================================================================== // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // Licensed 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 hudson.util; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.List; import java.util.ArrayList; /* ------------------------------------------------------------ */ /** StringTokenizer with Quoting support. * * This class is a copy of the java.util.StringTokenizer API and * the behaviour is the same, except that single and double quoted * string values are recognized. * Delimiters within quotes are not considered delimiters. * Quotes can be escaped with '\'. * * @see java.util.StringTokenizer * @author Greg Wilkins (gregw) */ public class QuotedStringTokenizer extends StringTokenizer { private final static String __delim=" \t\n\r"; private String _string; private String _delim = __delim; private boolean _returnQuotes=false; private boolean _returnDelimiters=false; private StringBuilder _token; private boolean _hasToken=false; private int _i=0; private int _lastStart=0; private boolean _double=true; private boolean _single=true; public static String[] tokenize(String str) { return new QuotedStringTokenizer(str).toArray(); } public static String[] tokenize(String str, String delimiters) { return new QuotedStringTokenizer(str,delimiters).toArray(); } /* ------------------------------------------------------------ */ /** * * @param str * String to tokenize. * @param delim * List of delimiter characters as string. Can be null, to default to ' \t\n\r' * @param returnDelimiters * If true, {@link #nextToken()} will include the delimiters, not just tokenized * tokens. * @param returnQuotes * If true, {@link #nextToken()} will include the quotation characters when they are present. */ public QuotedStringTokenizer(String str, String delim, boolean returnDelimiters, boolean returnQuotes) { super(""); _string=str; if (delim!=null) _delim=delim; _returnDelimiters=returnDelimiters; _returnQuotes=returnQuotes; if (_delim.indexOf('\'')>=0 || _delim.indexOf('"')>=0) throw new Error("Can't use quotes as delimiters: "+_delim); _token=new StringBuilder(_string.length()>1024?512:_string.length()/2); } /* ------------------------------------------------------------ */ public QuotedStringTokenizer(String str, String delim, boolean returnDelimiters) { this(str,delim,returnDelimiters,false); } /* ------------------------------------------------------------ */ public QuotedStringTokenizer(String str, String delim) { this(str,delim,false,false); } /* ------------------------------------------------------------ */ public QuotedStringTokenizer(String str) { this(str,null,false,false); } public String[] toArray() { List<String> r = new ArrayList<String>(); while(hasMoreTokens()) r.add(nextToken()); return r.toArray(new String[r.size()]); } /* ------------------------------------------------------------ */ @Override public boolean hasMoreTokens() { // Already found a token if (_hasToken) return true; _lastStart=_i; int state=0; boolean escape=false; while (_i<_string.length()) { char c=_string.charAt(_i++); switch (state) { case 0: // Start if(_delim.indexOf(c)>=0) { if (_returnDelimiters) { _token.append(c); return _hasToken=true; } } else if (c=='\'' && _single) { if (_returnQuotes) _token.append(c); state=2; } else if (c=='\"' && _double) { if (_returnQuotes) _token.append(c); state=3; } else { _token.append(c); _hasToken=true; state=1; } continue; case 1: // Token _hasToken=true; if (escape) { escape=false; if(ESCAPABLE_CHARS.indexOf(c)<0) _token.append('\\'); _token.append(c); } else if(_delim.indexOf(c)>=0) { if (_returnDelimiters) _i--; return _hasToken; } else if (c=='\'' && _single) { if (_returnQuotes) _token.append(c); state=2; } else if (c=='\"' && _double) { if (_returnQuotes) _token.append(c); state=3; } else if (c=='\\') { escape=true; } else _token.append(c); continue; case 2: // Single Quote _hasToken=true; if (escape) { escape=false; if(ESCAPABLE_CHARS.indexOf(c)<0) _token.append('\\'); _token.append(c); } else if (c=='\'') { if (_returnQuotes) _token.append(c); state=1; } else if (c=='\\') { if (_returnQuotes) _token.append(c); escape=true; } else _token.append(c); continue; case 3: // Double Quote _hasToken=true; if (escape) { escape=false; if(ESCAPABLE_CHARS.indexOf(c)<0) _token.append('\\'); _token.append(c); } else if (c=='\"') { if (_returnQuotes) _token.append(c); state=1; } else if (c=='\\') { if (_returnQuotes) _token.append(c); escape=true; } else _token.append(c); continue; } } return _hasToken; } /* ------------------------------------------------------------ */ @Override public String nextToken() throws NoSuchElementException { if (!hasMoreTokens() || _token==null) throw new NoSuchElementException(); String t=_token.toString(); _token.setLength(0); _hasToken=false; return t; } /* ------------------------------------------------------------ */ @Override public String nextToken(String delim) throws NoSuchElementException { _delim=delim; _i=_lastStart; _token.setLength(0); _hasToken=false; return nextToken(); } /* ------------------------------------------------------------ */ @Override public boolean hasMoreElements() { return hasMoreTokens(); } /* ------------------------------------------------------------ */ @Override public Object nextElement() throws NoSuchElementException { return nextToken(); } /* ------------------------------------------------------------ */ /** Not implemented. */ @Override public int countTokens() { return -1; } /* ------------------------------------------------------------ */ /** Quote a string. * The string is quoted only if quoting is required due to * embedded delimiters, quote characters or the * empty string. * @param s The string to quote. * @return quoted string */ public static String quote(String s, String delim) { if (s==null) return null; if (s.length()==0) return "\"\""; for (int i=0;i<s.length();i++) { char c = s.charAt(i); if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0) { StringBuffer b=new StringBuffer(s.length()+8); quote(b,s); return b.toString(); } } return s; } /* ------------------------------------------------------------ */ /** Quote a string. * The string is quoted only if quoting is required due to * embedded delimiters, quote characters or the * empty string. * @param s The string to quote. * @return quoted string */ public static String quote(String s) { if (s==null) return null; if (s.length()==0) return "\"\""; StringBuffer b=new StringBuffer(s.length()+8); quote(b,s); return b.toString(); } /* ------------------------------------------------------------ */ /** Quote a string into a StringBuffer. * The characters ", \, \n, \r, \t, \f and \b are escaped * @param buf The StringBuffer * @param s The String to quote. */ public static void quote(StringBuffer buf, String s) { synchronized(buf) { buf.append('"'); for (int i=0;i<s.length();i++) { char c = s.charAt(i); switch(c) { case '"': buf.append("\\\""); continue; case '\\': buf.append("\\\\"); continue; case '\n': buf.append("\\n"); continue; case '\r': buf.append("\\r"); continue; case '\t': buf.append("\\t"); continue; case '\f': buf.append("\\f"); continue; case '\b': buf.append("\\b"); continue; default: buf.append(c); continue; } } buf.append('"'); } } /* ------------------------------------------------------------ */ /** Unquote a string. * @param s The string to unquote. * @return quoted string */ public static String unquote(String s) { if (s==null) return null; if (s.length()<2) return s; char first=s.charAt(0); char last=s.charAt(s.length()-1); if (first!=last || (first!='"' && first!='\'')) return s; StringBuilder b=new StringBuilder(s.length()-2); boolean escape=false; for (int i=1;i<s.length()-1;i++) { char c = s.charAt(i); if (escape) { escape=false; switch (c) { case 'n': b.append('\n'); break; case 'r': b.append('\r'); break; case 't': b.append('\t'); break; case 'f': b.append('\f'); break; case 'b': b.append('\b'); break; case 'u': b.append((char)( (convertHexDigit((byte)s.charAt(i++))<<24)+ (convertHexDigit((byte)s.charAt(i++))<<16)+ (convertHexDigit((byte)s.charAt(i++))<<8)+ (convertHexDigit((byte)s.charAt(i++))) ) ); break; default: b.append(c); } } else if (c=='\\') { escape=true; continue; } else b.append(c); } return b.toString(); } /* ------------------------------------------------------------ */ /** * @return handle double quotes if true */ public boolean getDouble() { return _double; } /* ------------------------------------------------------------ */ /** * @param d handle double quotes if true */ public void setDouble(boolean d) { _double=d; } /* ------------------------------------------------------------ */ /** * @return handle single quotes if true */ public boolean getSingle() { return _single; } /* ------------------------------------------------------------ */ /** * @param single handle single quotes if true */ public void setSingle(boolean single) { _single=single; } /** * @param b An ASCII encoded character 0-9 a-f A-F * @return The byte value of the character 0-16. */ public static byte convertHexDigit( byte b ) { if ((b >= '0') && (b <= '9')) return (byte)(b - '0'); if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10); if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10); return 0; } /** * Characters that can be escaped with \. * * Others, like, say, \W will be left alone instead of becoming just W. * This is important to keep Hudson behave on Windows, which uses '\' as * the directory separator. */ private static final String ESCAPABLE_CHARS = "\\\"' "; }