/** * 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; /** * This class solely defines the separators used in the textual in- * and output between namespace, name and version(s). A textual * representation of a definition looks like ns::name:version, and * a textual representation of a uses like ns::name:min,max.<p> * * @author Jens-S. Vöckler * @author Yong Zhao * @version $Revision$ * * @see org.griphyn.vdl.classes.Definition */ public class Separator { /** * This constant defines the separator between a namespace and * the identifier. */ public static final String NAMESPACE = "::"; /** * This constant defines the separator between an identifier and * its version. */ public static final String NAME = ":"; /** * This constant defines the separator that denotes a version range. * Version ranges are only used with the "uses" clause, which maps * from a derivation to a transformation. */ public static final String VERSION = ","; /** * Although not truly a separator, this is the name of the default * namespace, which is used in the absence of a namespace. * @deprecated The default namespace is <code>null</code>. */ public static final String DEFAULT = "default"; /** * Combines the three components that constitute a fully-qualified * definition identifier into a single string. * * @param namespace is the namespace, may be empty or null. * @param name is the name to use, must not be empty nor null. * @param version is the version to attach, may be empty or null. * @return the combination of namespace, name and version with separators. * @exception NullPointerException will be thrown on an empty or null * name, as no such identifier can be constructed. */ public static String combine( String namespace, String name, String version ) { StringBuffer result = new StringBuffer(32); if ( namespace != null && namespace.length() > 0 ) result.append(namespace).append(Separator.NAMESPACE); // postcondition: no namespace, no double colon if ( name != null && name.length() > 0 ) { result.append(name); } else { // gotta have a name throw new NullPointerException( "Missing identifier for definition" ); } if ( version != null && version.length() > 0 ) result.append( Separator.NAME ).append(version); // postcondition: If there is a version, it will be appended return result.toString(); } /** * Combines the four components that reference a fully-qualified * definition identifier into a single string. * * @param namespace is the namespace, may be empty or null. * @param name is the name to use, must not be empty nor null. * @param min is the lower version to attach, may be empty or null. * @param max is the upper version to attach, may be empty or null. * @return the combination of namespace, name and versions with * appropriate separators. * @exception NullPointerException will be thrown on an empty or null * name, as no such identifier can be constructed. */ public static String combine( String namespace, String name, String min, String max ) { StringBuffer result = new StringBuffer(32); if ( namespace != null && namespace.length() > 0 ) result.append(namespace).append(Separator.NAMESPACE); // postcondition: no namespace, no double colon if ( name != null && name.length() > 0 ) { result.append(name); } else { // gotta have a name throw new NullPointerException( "Missing identifier for definition" ); } if ( min != null && min.length() > 0 ) { // minimum version exists result.append(Separator.NAME).append(min).append(Separator.VERSION); if ( max != null && max.length() > 0 ) result.append(max); } else { // minimum version does not exist if ( max != null && max.length() > 0 ) result.append(Separator.NAME).append(Separator.VERSION).append(max); } return result.toString(); } /** * Maps the action associated with a state and char class. The following * actions were determined: * <table> * <tr><th>0</th><td>no operation</td></tr> * <tr><th>1</th><td>save character</td></tr> * <tr><th>2</th><td>empty save into ns</td></tr> * <tr><th>3</th><td>empty save into id</td></tr> * <tr><th>4</th><td>empty save into vs</td></tr> * <tr><th>5</th><td>empty save into id, save</td></tr> * </table> */ private static short actionmap2[][] = { { 3, 0, 1 }, { 3, 2, 5 }, { 3, 3, 1 }, { 4, 0, 1 } }; /** * Maps the new state from current state and character class. The * following character classes are distinguished: * <table> * <tr><th>0</th><td>EOS</td></tr> * <tr><th>1</th><td>colon (:)</td></tr> * <tr><th>2</th><td>other (*)</td></tr> * </table> */ private static short statemap2[][] = { { 8, 1, 0 }, { 3, 2, 3 }, { 8, 3, 2 }, { 8, 9, 3 } }; /** * Splits a fully-qualified definition identifier into separate * namespace, name and version. Certain extensions permit a spec * to distinguish between an empty namespace or version and a * null (wildcard match) namespace and version.<p> * * There is a subtle distinction between a null value and an * empty value for the namespace and version. A null value is * usually taken as a wildcard match. An empty string however * is an exact match of a definition without the namespace or * version.<p> * * In order to enable the DAX generation function to distinguish * these cases when specifying user input, the following convention * is supported, where * stands in for wild-card matches, and * (-) for a match of an empty element: * * <table> * <tr><th>INPUT</th> <th>NS</th> <th>ID</th> <th>VS</th></tr> * <tr><td>id</td> <td>*</td> <td>id</td> <td>*</td></tr> * <tr><td>::id</td> <td>(-)</td> <td>id</td> <td>*</td></tr> * <tr><td>::id:</td> <td>(-)</td> <td>id</td> <td>(-)</td></tr> * <tr><td>id:</td> <td>*</td> <td>id</td> <td>(-)</td></tr> * <tr><td>id:vs</td> <td>*</td> <td>id</td> <td>vs</td></tr> * <tr><td>n::id</td> <td>n</td> <td>id</td> <td>*</td></tr> * <tr><td>n::id:</td><td>n</td> <td>id</td> <td>(-)</td></tr> * <tr><td>n::i:v</td><td>n</td> <td>i</td> <td>v</td></tr> * <tr><td>::i:v</td> <td>(-)</td> <td>i</td> <td>v</td></tr> * </table> * * @param fqdi is the fully-qualified definition identifier. * @return an array with 3 entries representing namespace, name * and version. Namespace and version may be empty or even null. */ public static String[] splitFQDI( String fqdi ) throws IllegalArgumentException { String[] result = new String[3]; result[0] = result[1] = result[2] = null; StringBuffer save = new StringBuffer(); short state = 0; int pos = 0; char ch; int chclass; do { // obtain next character and character class if ( pos < fqdi.length() ) { // regular char ch = fqdi.charAt(pos); chclass = ( ch == ':' ) ? 1 : 2; ++pos; } else { // EOS ch = Character.MIN_VALUE; chclass = 0; } // perform the action appropriate for state transition switch ( actionmap2[state][chclass] ) { case 0: // no-op break; case 5: // Vi+S result[1] = save.toString(); save = new StringBuffer(); // NO break on purpose case 1: // S save.append( ch ); break; case 2: // Vn result[0] = save.toString(); save = new StringBuffer(); break; case 3: // Vi result[1] = save.toString(); save = new StringBuffer(); break; case 4: // Vv result[2] = save.toString(); save = new StringBuffer(); break; } // perform state transition state = statemap2[state][chclass]; } while ( state < 8 ); if ( state == 9 || result[1] == null || result[1].trim().length() == 0 ) throw new IllegalArgumentException( "Malformed fully-qualified definition identifier" ); // POSTCONDITION: state == 8 return result; } /** * Maps the action associated with a state and a character class. * The actions are as follows: * <table> * <tr><th>0</th><td>no operation</td></tr> * <tr><th>1</th><td>save character</td></tr> * <tr><th>2</th><td>empty save into ns</td></tr> * <tr><th>3</th><td>empty save into name</td></tr> * <tr><th>4</th><td>empty save into vs</td></tr> * <tr><th>5</th><td>empty save into vs, 4args</td></tr> * <tr><th>6</th><td>empty save into max</td></tr> * <tr><th>7</th><td>empty save into max, 4args</td></tr> * <tr><th>8</th><td>empty save into name, save</td></tr> * </table> */ private static int actionmap[][] = { { 0, 0, 0, 1 }, // 0 { 3, 0, 0, 1 }, // 1 { 0, 2, 0, 8 }, // 2 { 0, 0, 0, 1 }, // 3 { 3, 3, 0, 1 }, // 4 { 4, 0, 5, 1 }, // 5 { 7, 0, 0, 1 } // 6 }; /** * Maps the state and character class to the follow-up state. The * final state 16 is a regular final state, and final state 17 is * the error final state. All other states are intermediary states.<p> * * Four character classes are distinguished: * <table> * <tr><th>0</th><td>end of string (EOS)</td> * <tr><th>1</th><td>colon (:)</td> * <tr><th>2</th><td>comma (,)</td> * <tr><th>3</th><td>any other</td> * </table> */ private static short statemap[][] = { { 17, 17, 17, 1 }, // 0 { 16, 2, 17, 1 }, // 1 { 17, 3, 17, 5 }, // 2 { 17, 17, 6, 4 }, // 3 { 16, 5, 17, 4 }, // 4 { 16, 17, 6, 5 }, // 5 { 16, 17, 17, 6 } // 6 }; /** * Splits a fully-qualified identifier into its components. Please note * that you must check the length of the result. If it contains three * elements, it is a regular FQDN. If it contains four results, it is * a tranformation reference range. Note though, if the version portion * is not specified, a 3 argument string will always be returned, even * if the context requires a 4 argument string. * * @param fqdn is the string to split into components. * @return a vector with three or four Strings, if it was parsable. * <ol> * <li>namespace, may be null * <li>name, never null * <li>version for 3arg, or minimum version for 4arg, may be null * <li>maximum version for 4arg, may be null * </ol> * @exception IllegalArgumentException, if the identifier cannot * be parsed correctly. */ public static String[] split( String fqdn ) throws IllegalArgumentException { String namespace = null; String name = null; String version = null; String max = null; short state = 0; int pos = 0; boolean is4args = false; StringBuffer save = new StringBuffer(); char ch; int chclass; do { // obtain next character and character class if ( pos < fqdn.length() ) { // regular char ch = fqdn.charAt(pos); if ( ch == ':' ) chclass = 1; else if ( ch == ',' ) chclass = 2; else chclass = 3; ++pos; } else { // EOS ch = Character.MIN_VALUE; chclass = 0; } // perform the action appropriate for state transition switch ( actionmap[state][chclass] ) { case 0: // no-op break; case 8: if ( save.length() > 0 ) name = save.toString(); save = new StringBuffer(); // NO break on purpose case 1: // save save.append( ch ); break; case 2: // save(ns) if ( save.length() > 0 ) namespace = save.toString(); save = new StringBuffer(); break; case 3: // save(name) if ( save.length() > 0 ) name = save.toString(); save = new StringBuffer(); break; case 5: // save(version), 4args is4args = true; // NO break on purpose case 4: // save(version) if ( save.length() > 0 ) version = save.toString(); save = new StringBuffer(); break; case 7: // save(max), 4args is4args = true; // NO break on purpose case 6: // save(max) if ( save.length() > 0 ) max = save.toString(); save = new StringBuffer(); break; } // perform state transition state = statemap[state][chclass]; } while ( state < 16 ); if ( state == 17 || ( is4args && version == null && max == null ) ) throw new IllegalArgumentException( "Malformed fully-qualified definition identifier" ); // POSTCONDITION: state == 16 // assemble result String[] result = new String[ is4args ? 4 : 3 ]; result[0] = namespace; result[1] = name; result[2] = version; if ( is4args ) result[3] = max; return result; } }