/**
* Copyright 2007-2008 University Of Southern California
*
* 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 edu.isi.pegasus.planner.code.generator.condor;
/**
* A utility class to correctly quote arguments strings before handing over
* to Condor.
* <p>
* The following Condor Quoting Rules are followed while quoting a String.
*
* <pre>
* 1) \' => '' e.g \'Test\' is converted to ''Test''
* 2) \" => "" e.g \"Test\" is converted to ""Test""
* 3) ' => ' if not enclosed in surrounding double quotes
* e.g 'Test' is converted to 'Test'
* 4) ' => '' if enclosed in surrounding double quotes
* e.g "'Test'" is converted to ''Test''
* 5) " => ' if not enclosed in surrounding single quotes
* e.g Karan "Vahi" is converted to Karan 'Vahi'
* 6) " => "" if enclosed in surrounding single quotes.
* e.g 'Karan "Vahi"' is converted to 'Karan ""Vahi""'.
* 7) * => * if enclosed in single or double quotes, the enclosed characters
* are copied literally including \ (no escaping rules apply)
* 8) \\ => \ escaping rules apply if not enclosed in single or double quotes.
* e.g \\\\ becomes \\, and \\\ throws error.
* </pre>
*
* In order to pass \n etc in the arguments, either quote it or escape it.
* for e.g in the DAX the following are valid ways to pass Karan\nVahi to the
* as arguments
* <pre>
* 1) "Karan\nVahi"
* 2) 'Karan\nVahi'
* 3) Karan\\nVahi
* </pre>
*
* In addition while writing out to the SubmitFile the whole argument String
* should be in enclosing ". for e.g arguments = "Test";
*
* @author Karan Vahi
* @author Gaurang Mehta
*
* @version $Revision$
*/
public class CondorQuoteParser {
/**
* Table to contain the state transition diagram for the parser. The
* rows are defined as current states 0 through 7. The columns is the
* current input character. The cell contains first the action to be
* taken, followed by the new state to transition to:
*
* <pre>
* | EOS | \ | ' | " |other|
* | 0 | 1 | 2 | 3 | 4 |
* -----+-----+-----+-----+-----+-----+
* 0 | -,F | -,1| A2,2| A2,3| A1,0|
* 1 | -,E1| A1,0| A3,0| A4,0| A1,0|
* 2 | -,E2| A1,2| A2,0| A4,2| A1,2|
* 3 | -,E3| A1,3| A3,3| A2,0| A1,3|
* -----+-----+-----+-----+-----+-----+
* F | 4 | final state
* E1 | 5 | error1: unexpected end of input
* E2 | 6 | error2: unmatched single quotes
* E3 | 7 | error3: unmatched double quotes
* </pre>
*
* The state variable collects the new state for a given
* state (rows) and input character set (column) identifier.
*
* <p>
* The state diagram for the above table is shown as follows
*
* <br>
* <a href="doc-files/CondorQuote.jpg">
* <img src="doc-files/CondorQuote.jpg" height="350" width="400"></a>
*
*
*/
private static final byte cState[][] = {
// E \ ' " O
{ 4, 1, 2, 3, 0}, // 0: starting state
{ 5, 0, 0, 0, 0}, // 1: found a \
{ 6, 2, 0, 2, 2}, // 2: found an opening single quote
{ 7, 3, 3, 0, 3}, // 3: found an opening double quote
};
/**
* There are five identified actions.
*
* <pre>
* - | 0 | noop
* A1 | 1 | append input character to result
* A2 | 2 | append ' to result
* A3 | 3 | append '' to result
* A4 | 4 | append "" to result
* </pre>
*
* The action variable collects the action to take for a
* given state (rows) and input character set (column).
*/
private static final byte cAction[][] = {
// E \ ' " O
{ 0, 0, 2, 2, 1}, // 0: starting state
{ 0, 1, 3, 4, 1}, // 1: found a \
{ 0, 1, 2, 4, 1}, // 2: found an opening single quote
{ 0, 1, 3, 2, 1}, // 3: found an opening double quote
};
/**
* Parses a string and condor quotes it. The enclosing quotes are not
* generated around the String.
*
* @param s is the input string to parse and quote.
*
* @return the quoted String.
* @throws CondorQuoteParserException if the input cannot be recognized.
*/
public static String quote( String s) throws CondorQuoteParserException{
return quote( s , false );
}
/**
* Parses a string and condor quotes it. Enclosing quotes are generated
* around the whole string if boolean enclose parameter is set.
*
* @param s is the input string to parse and quote.
* @param enclose boolean indicating whether to generate enclosing quotes or
* not.
*
* @return the quoted String.
* @throws CondorQuoteParserException if the input cannot be recognized.
*/
public static String quote( String s, boolean enclose ) throws CondorQuoteParserException{
StringBuffer result = new StringBuffer();
//enclose the string with mandatory " to start
if(enclose) result.append("\"");
int index = 0;
byte charset, state = 0;
char ch = '?';
while ( state < 4 ) {
//
// determine character class
//
switch ( (ch = ( index < s.length() ? s.charAt(index++) : '\0' )) ) {
case '\0':
charset = 0;
break;
case '\\':
charset = 1;
break;
case '\'':
charset = 2;
break;
case '\"':
charset = 3;
break;
default:
charset = 4;
break;
}
//
// perform action
//
switch ( cAction[state][charset] ) {
case 0 :// do nothing
break;
case 1: // append the character to the result
result.append(ch);
break;
case 2: // append \ to the result
result.append('\'');
break;
case 3: // append '' to the result
result.append("\'\'");
break;
case 4: // append "" to the result
result.append("\"\"");
break;
}
//
// progress state
//
state = cState[state][charset];
}
if ( state > 4 ) {
switch ( state ) {
case 5:
//we have unmatched single quotes
throw new CondorQuoteParserException("Unexpected end of input in string " + s,
index);
case 6:
//we have unmatched single quotes
throw new CondorQuoteParserException("Unmatched Single Quotes in string " + s,
index);
case 7:
//we have unmatched double quotes
throw new CondorQuoteParserException("Unmatched Double Quotes in string " + s,
index );
default:
throw new CondorQuoteParserException( "Unknown error", index );
}
}
//end the result with the mandatory closing " to end
if( enclose ) result.append("\"");
return result.toString();
}
/**
* A Test program.
*/
public static void main(String[] args) {
test("Test Input"); //result should be Test Input
test("'Test Input'"); //result should be 'Test Input'
test("\"Test Input\""); //result should be 'Test Input'
test("\\'Test Input\\'"); //result should be ''Test Input''
test("\\\"Test Input\\\""); //result should be ""Test Input""
test("\\\'Test Input\\\'"); //result should be ''Test Input''
test("\"\'Test Input\'\""); //result should be '''Test Input'''
test("\'\"Test Input\"\'"); //result should be '""Test Input""'
test("\"\'Test \\Input\'\""); //result should be '''Test \Input'''
test("\\\"Test Input\\\""); //result should be ""Test Input""
test("\'Test \"Input\"\'"); //result should be 'Test ""Input""'
test("Test \"Input\""); //result should be Test 'Input'
test("\\\\Test Input"); //result should be \Test Input
test("\\\\"); //result should be \
test("\'\"Test Input\'"); //result should be '""Test Input'
//errorneous inputs
test("\'\"Test Input\" ");
test(" \"\"\" ");
test(" ''' ");
}
/**
* Helper test method that tries and catches exception
*
* @param s the string to be parsed.
*/
private static void test(String s){
try{
System.out.println(s + " condor quoted is " + quote(s) );
}
catch(CondorQuoteParserException e){
System.out.println("Error " + e + " at position " + e.getPosition());
}
//System.out.println();
}
}