/** * 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.common.util; import edu.isi.pegasus.planner.catalog.classes.Profiles; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import edu.isi.pegasus.planner.classes.Profile; /** * Converts between the string version of a profile specification * and the parsed triples and back again. * * @author Gaurang Mehta * @author Jens-S. Vöckler */ public class ProfileParser { /** * 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 | adu | , | ; | : | \ | " | = |other| * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | * -----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ * 0 | -,F |Cn,0 | -,E1| -,E1| -,1 | -,E1| -,E1| -,E1| -,E1| * 1 | -,E2| -,E1| -,E1| -,E1| -,2 | -,E1| -,E1| -,E1| -,E1| * 2 | -,F |Ck,2 | -,E1| -,E1| -,E1| -,E1| -,E1| -,3 |Ck,E1| * 3 | -,E2|Cv,6 | -E1 | -,E1| -,E1| -,E1| -,4 | -,E1|Cv,6 | * 4 | -,E2|Cv,4 |Cv,4 |Cv,4 |Cv,4 | -,5 | -,7 |Cv,4 |Cv,4 | * 5 | -,E2|Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 | * 6 |A1,F |Cv,6 |A2,2 |A1,0 | -,E1| -,E1| -,E1| -,E1|Cv,6 | * 7 |A1,F | -,E1|A2,2 |A1,0 | -,E1| -,E1| -,E1| -,E1| -,E1| * -----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ * F | 8 | final state * E1 | 9 | error1: illegal character in input * E2 | 10 | error2: premature end of input * </pre> * * The state variable collects the new state for a given * state (rows) and input character set (column) identifier. */ private static final byte c_state[][] = { // E A , ; : \ " = O { 8, 0, 9, 9, 1, 9, 9, 9, 9 }, // 0: recognize ns { 10, 9, 9, 9, 2, 9, 9, 9, 9 }, // 1: found colon { 8, 2, 9, 9, 9, 9, 9, 3, 9 }, // 2: recognize key { 10, 6, 9, 9, 9, 9, 4, 9, 6 }, // 3: seen equals { 10, 4, 4, 4, 4, 5, 7, 4, 4 }, // 4: quoted value { 10, 4, 4, 4, 4, 4, 4, 4, 4 }, // 5: backslashed qv { 8, 6, 2, 0, 9, 9, 9, 9, 6 }, // 6: unquoted value { 8, 9, 2, 0, 9, 9, 9, 9, 9 } // 7: closed quote }; /** * There are six identified actions. * * <pre> * - | 0 | noop * Cn | 1 | append input character to namespace field * Ck | 2 | append input character to key field * Cv | 3 | append input character to value field * A1 | 4 | create triple and flush all fields * A2 | 5 | create triple and flush key and value only * </pre> * * The action variable collects the action to take for a * given state (rows) and input character set (column). */ private static final byte c_action[][] = { // E A , ; : \ " = O { 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // 0: recognize ns { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 1: found colon { 0, 2, 0, 0, 0, 0, 0, 0, 0 }, // 2: recognize key { 0, 3, 0, 0, 0, 0, 0, 0, 3 }, // 3: seen equals { 0, 3, 3, 3, 3, 0, 0, 3, 3 }, // 4: quoted value { 0, 3, 3, 3, 3, 3, 3, 3, 3 }, // 5: backslashed qv { 4, 3, 5, 4, 0, 0, 0, 0, 3 }, // 6: unquoted value { 4, 0, 5, 4, 0, 0, 0, 0, 0 } // 7: closed quote }; /** * Parses a given user profile specification into a map of maps. * * @param s is the input string to parse * @return a map of namespaces mapping to maps of key value pairs. * @throws ProfileParserException if the input cannot be recognized * @see #combine( List m ) */ public static List parse( String s ) throws ProfileParserException { char ch = '?'; List result = new ArrayList(); // sanity check if ( s == null ) return result; StringBuffer namespace = new StringBuffer(); StringBuffer key = new StringBuffer(); StringBuffer value = new StringBuffer(); int index = 0; byte charset, state = 0; while ( state < 8 ) { // // determine character class // switch ( (ch = ( index < s.length() ? s.charAt(index++) : '\0' )) ) { case '\0': charset = 0; break; case '_': charset = 1; break; case '.': charset = 1; break; case '@': charset = 1; break; case '-': charset = 1; break; case '+': charset = 1; break; case '/': charset = 1; break; case ',': charset = 2; break; case ';': charset = 3; break; case ':': charset = 4; break; case '\\': charset = 5; break; case '"': charset = 6; break; case '=': charset = 7; break; default: if ( Character.isLetter(ch) || Character.isDigit(ch) ) charset = 1; else charset = 8; break; } // // perform action // switch ( c_action[state][charset] ) { case 1: // collect namespace namespace.append(ch); break; case 2: // collect key key.append(ch); break; case 3: // collect value value.append(ch); break; case 4: // flush result.add( new Profile( namespace.toString().toLowerCase(), key.toString(), value.toString() ) ); namespace.delete( 0, namespace.length() ); key.delete( 0, key.length() ); value.delete( 0, value.length() ); break; case 5: // partial flush result.add( new Profile( namespace.toString(), key.toString(), value.toString() ) ); key.delete( 0, key.length() ); value.delete( 0, value.length() ); break; } // // progress state // state = c_state[state][charset]; } if ( state > 8 ) { switch ( state ) { case 9: throw new ProfileParserException( "Illegal character '" + ch + "'", index ); case 10: throw new ProfileParserException( "Premature end-of-string", index ); default: throw new ProfileParserException( "Unknown error", index ); } } return result; } /*** * Creates a profile string from the internal representation. * * @param l is a list of profiles * @return a string containing the representation. The string can be * empty (FIXME: should it be "null" or null?) for an empty list. * @see #parse( String s ) */ public static String combine( Profiles p ) { return combine( p.getProfiles() ); } /** * Creates a profile string from the internal representation. * * @param l is a list of profiles * @return a string containing the representation. The string can be * empty (FIXME: should it be "null" or null?) for an empty list. * @see #parse( String s ) */ public static String combine( List l ) { StringBuffer result = new StringBuffer(); // faster, shorter, less mem, retains ordering; alas, no minimal output boolean flag = false; String previous = "invalid namespace"; for ( Iterator i=l.iterator(); i.hasNext(); ) { Profile p = (Profile) i.next(); String ns = p.getProfileNamespace(); if ( ns.equals(previous) ) result.append(','); else { if ( flag ) result.append(';'); result.append(ns).append("::"); } result.append( p.getProfileKey() ).append('=').append('"'); // escape all dquote and backslash with backslash String value = p.getProfileValue(); for ( int k=0; k<value.length(); ++k ) { char ch = value.charAt(k); if ( ch == '"' || ch == '\\' ) result.append('\\'); result.append(ch); } result.append('"'); previous = ns; flag = true; } return result.toString(); } /** * Test program. * * @param args are command-line arguments */ public static void main( String args[] ) { ProfileParser me = new ProfileParser(); args=new String[1]; args[0] = "condor::+version=\"2.0\""; for ( int i=0; i<args.length; ++i ) { System.out.println( "input string in next line\n" + args[i] ); List l = null; try { l = me.parse(args[i]); } catch ( ProfileParserException ppe ) { for ( int x=1; x<ppe.getPosition(); ++x ) System.err.print(' '); System.err.println( "^" ); System.err.println( "ERROR: " + ppe.getMessage() + " at position " + ppe.getPosition() ); continue; } System.out.println( "output mappings in next line\n" + l.toString() ); String s = me.combine(l); System.out.println( "recombination in next line\n" + s ); System.out.println(); } } }