001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015:
016: package org.griphyn.common.util;
017:
018: import java.util.List;
019: import java.util.ArrayList;
020: import java.util.Map;
021: import java.util.TreeMap;
022: import java.util.Iterator;
023: import org.griphyn.cPlanner.classes.Profile;
024:
025: /**
026: * Converts between the string version of a profile specification
027: * and the parsed triples and back again.
028: *
029: * @author Gaurang Mehta
030: * @author Jens-S. Vöckler
031: */
032: public class ProfileParser {
033: /**
034: * Table to contain the state transition diagram for the parser. The
035: * rows are defined as current states 0 through 7. The columns is the
036: * current input character. The cell contains first the action to be
037: * taken, followed by the new state to transition to:
038: *
039: * <pre>
040: * | EOS | adu | , | ; | : | \ | " | = |other|
041: * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
042: * -----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
043: * 0 | -,F |Cn,0 | -,E1| -,E1| -,1 | -,E1| -,E1| -,E1| -,E1|
044: * 1 | -,E2| -,E1| -,E1| -,E1| -,2 | -,E1| -,E1| -,E1| -,E1|
045: * 2 | -,F |Ck,2 | -,E1| -,E1| -,E1| -,E1| -,E1| -,3 | -,E1|
046: * 3 | -,E2|Cv,6 | -E1 | -,E1| -,E1| -,E1| -,4 | -,E1|Cv,6 |
047: * 4 | -,E2|Cv,4 |Cv,4 |Cv,4 |Cv,4 | -,5 | -,7 |Cv,4 |Cv,4 |
048: * 5 | -,E2|Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 |Cv,4 |
049: * 6 |A1,F |Cv,6 |A2,2 |A1,0 | -,E1| -,E1| -,E1| -,E1|Cv,6 |
050: * 7 |A1,F | -,E1|A2,2 |A1,0 | -,E1| -,E1| -,E1| -,E1| -,E1|
051: * -----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
052: * F | 8 | final state
053: * E1 | 9 | error1: illegal character in input
054: * E2 | 10 | error2: premature end of input
055: * </pre>
056: *
057: * The state variable collects the new state for a given
058: * state (rows) and input character set (column) identifier.
059: */
060: private static final byte c_state[][] = {
061: // E A , ; : \ " = O
062: { 8, 0, 9, 9, 1, 9, 9, 9, 9 }, // 0: recognize ns
063: { 10, 9, 9, 9, 2, 9, 9, 9, 9 }, // 1: found colon
064: { 8, 2, 9, 9, 9, 9, 9, 3, 9 }, // 2: recognize key
065: { 10, 6, 9, 9, 9, 9, 4, 9, 6 }, // 3: seen equals
066: { 10, 4, 4, 4, 4, 5, 7, 4, 4 }, // 4: quoted value
067: { 10, 4, 4, 4, 4, 4, 4, 4, 4 }, // 5: backslashed qv
068: { 8, 6, 2, 0, 9, 9, 9, 9, 6 }, // 6: unquoted value
069: { 8, 9, 2, 0, 9, 9, 9, 9, 9 } // 7: closed quote
070: };
071:
072: /**
073: * There are six identified actions.
074: *
075: * <pre>
076: * - | 0 | noop
077: * Cn | 1 | append input character to namespace field
078: * Ck | 2 | append input character to key field
079: * Cv | 3 | append input character to value field
080: * A1 | 4 | create triple and flush all fields
081: * A2 | 5 | create triple and flush key and value only
082: * </pre>
083: *
084: * The action variable collects the action to take for a
085: * given state (rows) and input character set (column).
086: */
087: private static final byte c_action[][] = {
088: // E A , ; : \ " = O
089: { 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // 0: recognize ns
090: { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 1: found colon
091: { 0, 2, 0, 0, 0, 0, 0, 0, 0 }, // 2: recognize key
092: { 0, 3, 0, 0, 0, 0, 0, 0, 3 }, // 3: seen equals
093: { 0, 3, 3, 3, 3, 0, 0, 3, 3 }, // 4: quoted value
094: { 0, 3, 3, 3, 3, 3, 3, 3, 3 }, // 5: backslashed qv
095: { 4, 3, 5, 4, 0, 0, 0, 0, 3 }, // 6: unquoted value
096: { 4, 0, 5, 4, 0, 0, 0, 0, 0 } // 7: closed quote
097: };
098:
099: /**
100: * Parses a given user profile specification into a map of maps.
101: *
102: * @param s is the input string to parse
103: * @return a map of namespaces mapping to maps of key value pairs.
104: * @throws ProfileParserException if the input cannot be recognized
105: * @see #combine( List m )
106: */
107: public static List parse(String s) throws ProfileParserException {
108: char ch = '?';
109: List result = new ArrayList();
110:
111: // sanity check
112: if (s == null)
113: return result;
114:
115: StringBuffer namespace = new StringBuffer();
116: StringBuffer key = new StringBuffer();
117: StringBuffer value = new StringBuffer();
118: int index = 0;
119: byte charset, state = 0;
120: while (state < 8) {
121: //
122: // determine character class
123: //
124: switch ((ch = (index < s.length() ? s.charAt(index++)
125: : '\0'))) {
126: case '\0':
127: charset = 0;
128: break;
129: case '_':
130: charset = 1;
131: break;
132: case '.':
133: charset = 1;
134: break;
135: case '@':
136: charset = 1;
137: break;
138: case '-':
139: charset = 1;
140: break;
141: case '/':
142: charset = 1;
143: break;
144: case ',':
145: charset = 2;
146: break;
147: case ';':
148: charset = 3;
149: break;
150: case ':':
151: charset = 4;
152: break;
153: case '\\':
154: charset = 5;
155: break;
156: case '"':
157: charset = 6;
158: break;
159: case '=':
160: charset = 7;
161: break;
162: default:
163: if (Character.isLetter(ch) || Character.isDigit(ch))
164: charset = 1;
165: else
166: charset = 8;
167: break;
168: }
169:
170: //
171: // perform action
172: //
173: switch (c_action[state][charset]) {
174: case 1: // collect namespace
175: namespace.append(ch);
176: break;
177: case 2: // collect key
178: key.append(ch);
179: break;
180: case 3: // collect value
181: value.append(ch);
182: break;
183: case 4: // flush
184: result.add(new Profile(namespace.toString(), key
185: .toString(), value.toString()));
186: namespace.delete(0, namespace.length());
187: key.delete(0, key.length());
188: value.delete(0, value.length());
189: break;
190: case 5: // partial flush
191: result.add(new Profile(namespace.toString(), key
192: .toString(), value.toString()));
193: key.delete(0, key.length());
194: value.delete(0, value.length());
195: break;
196: }
197:
198: //
199: // progress state
200: //
201: state = c_state[state][charset];
202: }
203:
204: if (state > 8) {
205: switch (state) {
206: case 9:
207: throw new ProfileParserException("Illegal character '"
208: + ch + "'", index);
209: case 10:
210: throw new ProfileParserException(
211: "Premature end-of-string", index);
212: default:
213: throw new ProfileParserException("Unknown error", index);
214: }
215: }
216:
217: return result;
218: }
219:
220: /**
221: * Creates a profile string from the internal representation.
222: *
223: * @param l is a list of profiles
224: * @return a string containing the representation. The string can be
225: * empty (FIXME: should it be "null" or null?) for an empty list.
226: * @see #parse( String s )
227: */
228: public static String combine(List l) {
229: StringBuffer result = new StringBuffer();
230:
231: // // phase 1: convert list into map of maps
232: // Map m = new TreeMap();
233: // for ( Iterator i=l.iterator(); i.hasNext(); ) {
234: // Profile p = (Profile) i.next();
235: // String ns = p.getProfileNamespace();
236: // if ( ! m.containsKey(ns) ) m.put( ns, new TreeMap() );
237: // Map kv = (Map) m.get(ns);
238: // kv.put( p.getProfileKey(), p.getProfileValue() );
239: // }
240: //
241: // // phase 2: convert map of maps into string using minimal space
242: // boolean flag1 = false;
243: // for ( Iterator i=m.keySet().iterator(); i.hasNext(); ) {
244: // String ns = (String) i.next();
245: // Map kv = (Map) m.get(ns);
246: // if ( flag1 ) result.append(';');
247: // result.append(ns).append("::");
248: //
249: // boolean flag2 = false;
250: // for ( Iterator j=kv.keySet().iterator(); j.hasNext(); ) {
251: // String key = (String) j.next();
252: // String value = (String) kv.get(key);
253: // if ( flag2 ) result.append(',');
254: // result.append(key).append('=').append('"');
255: //
256: // // escape all dquote and backslash with backslash
257: // for ( int k=0; k<value.length(); ++k ) {
258: // char ch = value.charAt(k);
259: // if ( ch == '"' || ch == '\\' ) result.append('\\');
260: // result.append(ch);
261: // }
262: //
263: // result.append('"');
264: // flag2 = true;
265: // }
266: // flag1 = true;
267: // }
268:
269: // faster, shorter, less mem, retains ordering; alas, no minimal output
270: boolean flag = false;
271: String previous = "invalid namespace";
272: for (Iterator i = l.iterator(); i.hasNext();) {
273: Profile p = (Profile) i.next();
274: String ns = p.getProfileNamespace();
275: if (ns.equals(previous))
276: result.append(',');
277: else {
278: if (flag)
279: result.append(';');
280: result.append(ns).append("::");
281: }
282: result.append(p.getProfileKey()).append('=').append('"');
283:
284: // escape all dquote and backslash with backslash
285: String value = p.getProfileValue();
286: for (int k = 0; k < value.length(); ++k) {
287: char ch = value.charAt(k);
288: if (ch == '"' || ch == '\\')
289: result.append('\\');
290: result.append(ch);
291: }
292: result.append('"');
293: previous = ns;
294: flag = true;
295: }
296:
297: return result.toString();
298: }
299:
300: /**
301: * Test program.
302: *
303: * @param args are command-line arguments
304: */
305: public static void main(String args[]) {
306: ProfileParser me = new ProfileParser();
307:
308: for (int i = 0; i < args.length; ++i) {
309: System.out.println("input string in next line\n" + args[i]);
310: List l = null;
311: try {
312: l = me.parse(args[i]);
313: } catch (ProfileParserException ppe) {
314: for (int x = 1; x < ppe.getPosition(); ++x)
315: System.err.print(' ');
316: System.err.println("^");
317: System.err.println("ERROR: " + ppe.getMessage()
318: + " at position " + ppe.getPosition());
319: continue;
320: }
321: System.out.println("output mappings in next line\n"
322: + l.toString());
323: String s = me.combine(l);
324: System.out.println("recombination in next line\n" + s);
325: System.out.println();
326: }
327: }
328: }
|