/*******************************************************************************
*
* Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Greg Wilkins
*
*******************************************************************************/
// ========================================================================
// 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 doulbe 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 StringBuffer _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 StringBuffer(_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
* embeded 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
* embeded 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;
}
StringBuffer b = new StringBuffer(s.length() - 2);
synchronized (b) {
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 = "\\\"' ";
}