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: /**
019: * This class solely defines the separators used in the textual in-
020: * and output between namespace, name and version(s). A textual
021: * representation of a definition looks like ns::name:version, and
022: * a textual representation of a uses like ns::name:min,max.<p>
023: *
024: * @author Jens-S. Vöckler
025: * @author Yong Zhao
026: * @version $Revision: 50 $
027: *
028: * @see org.griphyn.vdl.classes.Definition
029: */
030: public class Separator {
031: /**
032: * This constant defines the separator between a namespace and
033: * the identifier.
034: */
035: public static final String NAMESPACE = "::";
036:
037: /**
038: * This constant defines the separator between an identifier and
039: * its version.
040: */
041: public static final String NAME = ":";
042:
043: /**
044: * This constant defines the separator that denotes a version range.
045: * Version ranges are only used with the "uses" clause, which maps
046: * from a derivation to a transformation.
047: */
048: public static final String VERSION = ",";
049:
050: /**
051: * Although not truly a separator, this is the name of the default
052: * namespace, which is used in the absence of a namespace.
053: * @deprecated The default namespace is <code>null</code>.
054: */
055: public static final String DEFAULT = "default";
056:
057: /**
058: * Combines the three components that constitute a fully-qualified
059: * definition identifier into a single string.
060: *
061: * @param namespace is the namespace, may be empty or null.
062: * @param name is the name to use, must not be empty nor null.
063: * @param version is the version to attach, may be empty or null.
064: * @return the combination of namespace, name and version with separators.
065: * @exception NullPointerException will be thrown on an empty or null
066: * name, as no such identifier can be constructed.
067: */
068: public static String combine(String namespace, String name,
069: String version) {
070: StringBuffer result = new StringBuffer(32);
071:
072: if (namespace != null && namespace.length() > 0)
073: result.append(namespace).append(Separator.NAMESPACE);
074: // postcondition: no namespace, no double colon
075:
076: if (name != null && name.length() > 0) {
077: result.append(name);
078: } else {
079: // gotta have a name
080: throw new NullPointerException(
081: "Missing identifier for definition");
082: }
083:
084: if (version != null && version.length() > 0)
085: result.append(Separator.NAME).append(version);
086: // postcondition: If there is a version, it will be appended
087:
088: return result.toString();
089: }
090:
091: /**
092: * Combines the four components that reference a fully-qualified
093: * definition identifier into a single string.
094: *
095: * @param namespace is the namespace, may be empty or null.
096: * @param name is the name to use, must not be empty nor null.
097: * @param min is the lower version to attach, may be empty or null.
098: * @param max is the upper version to attach, may be empty or null.
099: * @return the combination of namespace, name and versions with
100: * appropriate separators.
101: * @exception NullPointerException will be thrown on an empty or null
102: * name, as no such identifier can be constructed.
103: */
104: public static String combine(String namespace, String name,
105: String min, String max) {
106: StringBuffer result = new StringBuffer(32);
107:
108: if (namespace != null && namespace.length() > 0)
109: result.append(namespace).append(Separator.NAMESPACE);
110: // postcondition: no namespace, no double colon
111:
112: if (name != null && name.length() > 0) {
113: result.append(name);
114: } else {
115: // gotta have a name
116: throw new NullPointerException(
117: "Missing identifier for definition");
118: }
119:
120: if (min != null && min.length() > 0) {
121: // minimum version exists
122: result.append(Separator.NAME).append(min).append(
123: Separator.VERSION);
124: if (max != null && max.length() > 0)
125: result.append(max);
126: } else {
127: // minimum version does not exist
128: if (max != null && max.length() > 0)
129: result.append(Separator.NAME).append(Separator.VERSION)
130: .append(max);
131: }
132:
133: return result.toString();
134: }
135:
136: /**
137: * Maps the action associated with a state and char class. The following
138: * actions were determined:
139: * <table>
140: * <tr><th>0</th><td>no operation</td></tr>
141: * <tr><th>1</th><td>save character</td></tr>
142: * <tr><th>2</th><td>empty save into ns</td></tr>
143: * <tr><th>3</th><td>empty save into id</td></tr>
144: * <tr><th>4</th><td>empty save into vs</td></tr>
145: * <tr><th>5</th><td>empty save into id, save</td></tr>
146: * </table>
147: */
148: private static short actionmap2[][] = { { 3, 0, 1 }, { 3, 2, 5 },
149: { 3, 3, 1 }, { 4, 0, 1 } };
150:
151: /**
152: * Maps the new state from current state and character class. The
153: * following character classes are distinguished:
154: * <table>
155: * <tr><th>0</th><td>EOS</td></tr>
156: * <tr><th>1</th><td>colon (:)</td></tr>
157: * <tr><th>2</th><td>other (*)</td></tr>
158: * </table>
159: */
160: private static short statemap2[][] = { { 8, 1, 0 }, { 3, 2, 3 },
161: { 8, 3, 2 }, { 8, 9, 3 } };
162:
163: /**
164: * Splits a fully-qualified definition identifier into separate
165: * namespace, name and version. Certain extensions permit a spec
166: * to distinguish between an empty namespace or version and a
167: * null (wildcard match) namespace and version.<p>
168: *
169: * There is a subtle distinction between a null value and an
170: * empty value for the namespace and version. A null value is
171: * usually taken as a wildcard match. An empty string however
172: * is an exact match of a definition without the namespace or
173: * version.<p>
174: *
175: * In order to enable the DAX generation function to distinguish
176: * these cases when specifying user input, the following convention
177: * is supported, where * stands in for wild-card matches, and
178: * (-) for a match of an empty element:
179: *
180: * <table>
181: * <tr><th>INPUT</th> <th>NS</th> <th>ID</th> <th>VS</th></tr>
182: * <tr><td>id</td> <td>*</td> <td>id</td> <td>*</td></tr>
183: * <tr><td>::id</td> <td>(-)</td> <td>id</td> <td>*</td></tr>
184: * <tr><td>::id:</td> <td>(-)</td> <td>id</td> <td>(-)</td></tr>
185: * <tr><td>id:</td> <td>*</td> <td>id</td> <td>(-)</td></tr>
186: * <tr><td>id:vs</td> <td>*</td> <td>id</td> <td>vs</td></tr>
187: * <tr><td>n::id</td> <td>n</td> <td>id</td> <td>*</td></tr>
188: * <tr><td>n::id:</td><td>n</td> <td>id</td> <td>(-)</td></tr>
189: * <tr><td>n::i:v</td><td>n</td> <td>i</td> <td>v</td></tr>
190: * <tr><td>::i:v</td> <td>(-)</td> <td>i</td> <td>v</td></tr>
191: * </table>
192: *
193: * @param fqdi is the fully-qualified definition identifier.
194: * @return an array with 3 entries representing namespace, name
195: * and version. Namespace and version may be empty or even null.
196: */
197: public static String[] splitFQDI(String fqdi)
198: throws IllegalArgumentException {
199: String[] result = new String[3];
200: result[0] = result[1] = result[2] = null;
201: StringBuffer save = new StringBuffer();
202:
203: short state = 0;
204: int pos = 0;
205:
206: char ch;
207: int chclass;
208: do {
209: // obtain next character and character class
210: if (pos < fqdi.length()) {
211: // regular char
212: ch = fqdi.charAt(pos);
213: chclass = (ch == ':') ? 1 : 2;
214: ++pos;
215: } else {
216: // EOS
217: ch = Character.MIN_VALUE;
218: chclass = 0;
219: }
220:
221: // perform the action appropriate for state transition
222: switch (actionmap2[state][chclass]) {
223: case 0: // no-op
224: break;
225: case 5: // Vi+S
226: result[1] = save.toString();
227: save = new StringBuffer();
228: // NO break on purpose
229: case 1: // S
230: save.append(ch);
231: break;
232: case 2: // Vn
233: result[0] = save.toString();
234: save = new StringBuffer();
235: break;
236: case 3: // Vi
237: result[1] = save.toString();
238: save = new StringBuffer();
239: break;
240: case 4: // Vv
241: result[2] = save.toString();
242: save = new StringBuffer();
243: break;
244: }
245:
246: // perform state transition
247: state = statemap2[state][chclass];
248: } while (state < 8);
249:
250: if (state == 9 || result[1] == null
251: || result[1].trim().length() == 0)
252: throw new IllegalArgumentException(
253: "Malformed fully-qualified definition identifier");
254:
255: // POSTCONDITION: state == 8
256: return result;
257: }
258:
259: /**
260: * Maps the action associated with a state and a character class.
261: * The actions are as follows:
262: * <table>
263: * <tr><th>0</th><td>no operation</td></tr>
264: * <tr><th>1</th><td>save character</td></tr>
265: * <tr><th>2</th><td>empty save into ns</td></tr>
266: * <tr><th>3</th><td>empty save into name</td></tr>
267: * <tr><th>4</th><td>empty save into vs</td></tr>
268: * <tr><th>5</th><td>empty save into vs, 4args</td></tr>
269: * <tr><th>6</th><td>empty save into max</td></tr>
270: * <tr><th>7</th><td>empty save into max, 4args</td></tr>
271: * <tr><th>8</th><td>empty save into name, save</td></tr>
272: * </table>
273: */
274: private static int actionmap[][] = { { 0, 0, 0, 1 }, // 0
275: { 3, 0, 0, 1 }, // 1
276: { 0, 2, 0, 8 }, // 2
277: { 0, 0, 0, 1 }, // 3
278: { 3, 3, 0, 1 }, // 4
279: { 4, 0, 5, 1 }, // 5
280: { 7, 0, 0, 1 } // 6
281: };
282:
283: /**
284: * Maps the state and character class to the follow-up state. The
285: * final state 16 is a regular final state, and final state 17 is
286: * the error final state. All other states are intermediary states.<p>
287: *
288: * Four character classes are distinguished:
289: * <table>
290: * <tr><th>0</th><td>end of string (EOS)</td>
291: * <tr><th>1</th><td>colon (:)</td>
292: * <tr><th>2</th><td>comma (,)</td>
293: * <tr><th>3</th><td>any other</td>
294: * </table>
295: */
296: private static short statemap[][] = { { 17, 17, 17, 1 }, // 0
297: { 16, 2, 17, 1 }, // 1
298: { 17, 3, 17, 5 }, // 2
299: { 17, 17, 6, 4 }, // 3
300: { 16, 5, 17, 4 }, // 4
301: { 16, 17, 6, 5 }, // 5
302: { 16, 17, 17, 6 } // 6
303: };
304:
305: /**
306: * Splits a fully-qualified identifier into its components. Please note
307: * that you must check the length of the result. If it contains three
308: * elements, it is a regular FQDN. If it contains four results, it is
309: * a tranformation reference range. Note though, if the version portion
310: * is not specified, a 3 argument string will always be returned, even
311: * if the context requires a 4 argument string.
312: *
313: * @param fqdn is the string to split into components.
314: * @return a vector with three or four Strings, if it was parsable.
315: * <ol>
316: * <li>namespace, may be null
317: * <li>name, never null
318: * <li>version for 3arg, or minimum version for 4arg, may be null
319: * <li>maximum version for 4arg, may be null
320: * </ol>
321: * @exception IllegalArgumentException, if the identifier cannot
322: * be parsed correctly.
323: */
324: public static String[] split(String fqdn)
325: throws IllegalArgumentException {
326: String namespace = null;
327: String name = null;
328: String version = null;
329: String max = null;
330:
331: short state = 0;
332: int pos = 0;
333: boolean is4args = false;
334: StringBuffer save = new StringBuffer();
335:
336: char ch;
337: int chclass;
338: do {
339: // obtain next character and character class
340: if (pos < fqdn.length()) {
341: // regular char
342: ch = fqdn.charAt(pos);
343: if (ch == ':')
344: chclass = 1;
345: else if (ch == ',')
346: chclass = 2;
347: else
348: chclass = 3;
349: ++pos;
350: } else {
351: // EOS
352: ch = Character.MIN_VALUE;
353: chclass = 0;
354: }
355:
356: // perform the action appropriate for state transition
357: switch (actionmap[state][chclass]) {
358: case 0: // no-op
359: break;
360: case 8:
361: if (save.length() > 0)
362: name = save.toString();
363: save = new StringBuffer();
364: // NO break on purpose
365: case 1: // save
366: save.append(ch);
367: break;
368: case 2: // save(ns)
369: if (save.length() > 0)
370: namespace = save.toString();
371: save = new StringBuffer();
372: break;
373: case 3: // save(name)
374: if (save.length() > 0)
375: name = save.toString();
376: save = new StringBuffer();
377: break;
378: case 5: // save(version), 4args
379: is4args = true;
380: // NO break on purpose
381: case 4: // save(version)
382: if (save.length() > 0)
383: version = save.toString();
384: save = new StringBuffer();
385: break;
386: case 7: // save(max), 4args
387: is4args = true;
388: // NO break on purpose
389: case 6: // save(max)
390: if (save.length() > 0)
391: max = save.toString();
392: save = new StringBuffer();
393: break;
394: }
395:
396: // perform state transition
397: state = statemap[state][chclass];
398: } while (state < 16);
399:
400: if (state == 17 || (is4args && version == null && max == null))
401: throw new IllegalArgumentException(
402: "Malformed fully-qualified definition identifier");
403:
404: // POSTCONDITION: state == 16
405: // assemble result
406: String[] result = new String[is4args ? 4 : 3];
407: result[0] = namespace;
408: result[1] = name;
409: result[2] = version;
410: if (is4args)
411: result[3] = max;
412: return result;
413: }
414: }
|