/**
* 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.gridstart;
/**
* This class tries to define a mechanism to encode arguments for
* pegasus-exitcode, as DAGMan does not handle whitespaces correctly for
* postscript arguments.
*
* The default rules are
* single space gets encoded to +
* + gets escaped to \+
* non printing asci characters are flagged
*
* here are some examples of this encoding rule
* <pre>
* Error Message is encoded to Error+Message
* Error Message is encoded to Error+++Message
* Error + Message is encoded to Error+\++Message
* Error + \Message is encoded to Error+\++\Message
* Error + Message\ is encoded to Error+\++Message\
* Error + \\ Message is encoded to Error+\++\\+Message
* </pre>
*
*
* @author Karan Vahi
* @version $Revision$
*/
public class PegasusExitCodeEncode {
/**
* Defines the character used to escape characters.
*/
private char mEscape;
/**
* Defines the set of characters that require escaping.
*/
private String mEscapable;
/**
* Defines the character that requires encoding
*/
private char mEncodeable;
/**
* The value to encode to
*/
private char mEncode;
/**
* Defines the default encoding rules
* escape + with \+
* encode single whitespace with +
*
*/
public PegasusExitCodeEncode() {
mEscapable = "+";
mEscape = '\\';
mEncodeable = ' ';
mEncode = '+';
}
/**
* Constructs arbitrary escaping rules.
*
* @param escapable is the set of characters that require escaping
* @param escape is the escape character itself.
*/
public PegasusExitCodeEncode(String escapable, char escape) {
mEscape = escape;
mEscapable = escapable;
// ensure that the escape character is part of the escapable char set
if (escapable.indexOf(escape) == -1) {
mEscapable += mEscape;
}
}
/**
* Transforms a given string by encoding single whitespace with the escape
* character set ( defaults to + ), and escapes the escape itself
*
* <pre>
* error message is encoded to error+message
* error +message is encoded to error+\+message
* <>
*
* @param s is the string to encode.
* @return the encoded string
*
* @see #unescape( String )
*/
public String encode(String s) {
// sanity check
if (s == null) {
return null;
}
StringBuilder result = new StringBuilder(s.length());
for (int i = 0; i < s.length(); ++i) {
char ch = s.charAt(i);
if (Character.isWhitespace(ch)) {
if ( ch == mEncodeable ) {
result.append( mEncode );
} else {
throw new IllegalArgumentException("Invalid whitespace character \'" + ch + "\' passed for encoding " + s);
}
continue;
}
//after whitespace check for other non printing characters
//to distinguish error between invalid whitespace and non printing character
if ( !this.isAsciiPrintable( ch ) ){
throw new IllegalArgumentException( "Invalid non printing character \'" + ch + "\' passed for encoding " + s);
}
else if (mEscapable.indexOf(ch) != -1) {
//we need to escape the character
result.append(mEscape);
result.append(ch);
}
else{
result.append( ch );
}
}
return result.toString();
}
/**
* Transforms a given string by decoding all characters and unescaping where
* required.
*
* @param s is the string to remove escapes from.
* @return the decoded string
*/
public String decode(String s) {
// sanity check
if (s == null) {
return null;
}
StringBuilder result = new StringBuilder(s.length());
int state = 0;
for (int i = 0; i < s.length(); ++i) {
char ch = s.charAt(i);
if (state == 0) {
// default state
if( ch == mEncode ){
result.append( mEncodeable );
}
else if (ch == mEscape) {
state = 1;
//fix for \ as last character
if( i == s.length() - 1 ){
result.append( ch );
}
} else {
result.append(ch);
}
}
else {
// "found escape" state
if (mEscapable.indexOf(ch) == -1) {
result.append(mEscape);
}
result.append(ch);
state = 0;
}
}
return result.toString();
}
/**
* <p>Checks whether the character is ASCII 7 bit printable.</p>
*
* <pre>
* CharUtils.isAsciiPrintable('a') = true
* CharUtils.isAsciiPrintable('A') = true
* CharUtils.isAsciiPrintable('3') = true
* CharUtils.isAsciiPrintable('-') = true
* CharUtils.isAsciiPrintable('\n') = false
* CharUtils.isAsciiPrintable('©') = false
* </pre>
*
* @param ch the character to check
* @return true if between 32 and 126 inclusive
*/
public boolean isAsciiPrintable(char ch) {
return ch >= 32 && ch < 127;
}
public void test( String s ){
String e = this.encode(s);
String s1 = this.decode( e );
System.out.println( s + " is encoded to " + e );
System.out.println( e + " is decoded to " + s1 );
if( s.equals( s1 )){
System.out.println( "[Success] Encoding and decoding is symmetric " );
}
else{
System.out.println( "[Error] Encoding and decoding is asymmetric " );
}
System.out.println();
}
/**
* Test program.
*
* @param args are command-line arguments
*/
public static void main(String args[]) {
PegasusExitCodeEncode me = new PegasusExitCodeEncode(); // defaults
me.test( "Error Message");
me.test( "Error Message");
me.test( "Error + Message" );
me.test( "Error + \\Message" );
me.test( "Error + Message\\" );
me.test( "Error + \\\\ Message" );
me.test( "Error + Message\\\\" );
//should throw errors
try{
me.test( "Error + " + "\t" + "Message" );
}
catch ( IllegalArgumentException e ){
System.out.println( e.getMessage() );
}
try{
char ch = 163; //the pound sign
me.test( "Error Message " + ch);
}
catch ( IllegalArgumentException e ){
System.out.println( e.getMessage() );
}
}
}