/**
* (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 = "\\\"' ";
}