/* * This file or a portion of this file is licensed under the terms of * the Globus Toolkit Public License, found in file ../GTPL, or at * http://www.globus.org/toolkit/download/license.html. This notice must * appear in redistributions of this file, with or without modification. * * Redistributions of this Software, with or without modification, must * reproduce the GTPL in: (1) the Software, or (2) the Documentation or * some other similar material which is provided with the Software (if * any). * * Copyright 1999-2004 University of Chicago and The University of * Southern California. All rights reserved. */ package org.griphyn.vdl.classes; import edu.isi.pegasus.common.util.Currently; import org.griphyn.vdl.classes.*; import org.griphyn.vdl.util.*; import java.util.*; import java.io.*; /** * This class implements the container to carry any number of * <code>Transformation</code> and <code>Derivation</code> instances. * In addition, it captures some attributes from the root element of * the XML document. * * @author Jens-S. Vöckler * @author Yong Zhao * @version $Revision$ * * @see Definition * @see Transformation * @see Derivation */ public class Definitions extends VDL implements Serializable { /** * The "official" namespace URI of the VDLx schema. */ public static final String SCHEMA_NAMESPACE = "http://www.griphyn.org/chimera/VDL"; /** * The "not-so-official" location URL of the VDLx schema definition. */ public static final String SCHEMA_LOCATION = "http://www.griphyn.org/chimera/vdl-1.24.xsd"; // attributes of "definitions" element /** * Capture the global namespace given to all child elements that * do not set their own namespace definition. */ private String m_vdlns; /** * Capture the version of the XML document. */ private String m_version; /** * children are either {@link Transformation}s or {@link Derivation}s, * both of which are {@link Derivation}s. */ private ArrayList m_definitionList; /** * ctor. It is strongly suggested that you set the namespace and * version of the object before adding any other {@link Definition} * objects. */ public Definitions() { this.m_definitionList = new ArrayList(); this.m_vdlns = null; this.m_version = null; } /** * ctor: Create a new container, using the given namespace. It is * highly recommended that you set the version number before you add * any {@link Definition} instance. * * @param vdlns is the namespace to use for elements w/o namespace. */ public Definitions( String vdlns ) { this.m_definitionList = new ArrayList(); this.m_vdlns = vdlns; this.m_version = null; } /** * ctor: Create a new container, using a namespace and version. * * @param vdlns is the namespace to propagate to children w/o namespace. * @param version is a version of the XML document used to transport * the data. */ public Definitions( String vdlns, String version ) { this.m_definitionList = new ArrayList(); this.m_vdlns = vdlns; this.m_version = version; } /** * updating old Use.linkage with new Use.linkage. This table uses * the old/stored linkage in the top row, and the new/found linkage * in the first column. (-) denotes no action to be taken, and # * an illegal combination.<p> * <pre> * | -1 | NONE| IN | OUT | IO * ----+-----+-----+-----+-----+---- * -1 | (-) | (-) | (-) | (-) | (-) * NONE| NONE| (-) | # | # | # * IN| IN | # | (-) | IO | (-) * OUT| OUT | # | IO | (-) | (-) * IO| IO | # | IO | IO | (-) * ----+-----+-----+-----+-----+---- * </pre> * The table uses -1 for no action to do, and -2 for an illegal state. */ private int m_state[][] = { { -1, -1, -1, -1, -1 }, // newlink == -1 { 0, -1, -2, -2, -2 }, // newlink == NONE { 1, -2, -1, 3, -1 }, // newlink == IN { 2, -2, 3, -1, -1 }, // newlink == OUT { 3, -2, 3, 3, -1 } }; /** * Checks the linkage of a transformation between a declared, previously * used and currently used variable of the same name. * * @param use is a table of previously used variables and their linkage * @param u is the variable at the "cursor position". * @param tr is the transformation to be checked. */ private void checkLinkage( Map use, Use u, Transformation tr ) { if ( use.containsKey( u.getName() ) ) { // key exists, check/modify linkage int linkage = ((Integer) use.get( u.getName() )).intValue(); int newlink = u.getLink(); int result = this.m_state[newlink+1][linkage+1]; if ( result == -2 ) { // illegal combination of linkages, usually NONE w/ I,O,IO throw new IncompatibleLinkageException( "Transformation " + tr.shortID() + "uses variable " + u.getName() + " with incompatibles linkages" ); } else if ( result > -1 ) { // store new result use.put( u.getName(), new Integer(result) ); } } else { // key does not exist, add use.put( u.getName(), new Integer(u.getLink()) ); } } /** * Clean-up definition and perform abstract type checks before * submitting them into the document. * * @exception IllegalArgumentException will be thrown if the * <code>Definition</code> is neither a <code>Derivation</code> nor a * <code>Transformation</code>. This should not happen. * @exception UndeclaredVariableException will be thrown, if * a <code>Transformation</code> uses a bound variable via <code>Use</code>, * but fails to declare the formal argument with <code>Declare</code>. * @exception IncompatibleLinkageException will be thrown, if * the declared linkage of a formal argument is incompatible with the * usage of such a bound variable within a <code>Transformation</code>. * @exception IllegalTransformationException will be thrown, if * the <code>Transformation</code> has simultaneously <code>Call</code> * and <code>Argument</code> items. This exception is bound to vanish * with the next major re-design. * * @see Transformation * @see Derivation * @see Use * @see Declare */ protected void sanitizeDefinition( Definition d ) throws IllegalArgumentException, IncompatibleLinkageException, UndeclaredVariableException, IllegalTransformationException { String newline = System.getProperty( "line.separator", "\r\n" ); // update definition with namespace and version, if necessary // Note: results may still be null if ( d.getNamespace() == null && this.m_vdlns != null ) d.setNamespace(this.m_vdlns); if ( d.getVersion() == null && this.m_version != null ) d.setVersion(this.m_version); switch ( d.getType() ) { case Definition.TRANSFORMATION: Transformation tr = (Transformation) d; HashMap use = new HashMap(); // a TR must not be simultaneously simple and compound if ( tr.getArgumentCount() > 0 && tr.getCallCount() > 0 ) throw new IllegalTransformationException( "TR " + tr.identify() + " is simultaneously simple and compound" + newline + tr.toXML("\t",null) ); // // collect all unique bindings of class Use // if ( tr.isSimple() ) { // collect from Argument list for ( Iterator e=tr.iterateArgument(); e.hasNext(); ) { for ( Iterator f=((Argument) e.next()).iterateLeaf(); f.hasNext(); ) { Leaf l = (Leaf) f.next(); if ( l instanceof Use ) checkLinkage( use, (Use) l, tr ); } } } else { // collect from Call list, this is slightly more complex... // only 3..4 nested for loops, why do you worry... for ( Iterator e=tr.iterateCall(); e.hasNext(); ) { for ( Iterator f=((Call) e.next()).iteratePass(); f.hasNext(); ) { Value v = (Value) ((Pass) f.next()).getValue(); switch ( v.getContainerType() ) { case Value.SCALAR: for ( Iterator g=((Scalar) v).iterateLeaf(); g.hasNext(); ) { Leaf l = (Leaf) g.next(); if ( l instanceof Use ) checkLinkage( use, (Use) l, tr ); } break; case Value.LIST: for ( Iterator h=((List) v).iterateScalar(); h.hasNext(); ) { for ( Iterator g=((Scalar) h.next()).iterateLeaf(); g.hasNext(); ) { Leaf l = (Leaf) g.next(); if ( l instanceof Use ) checkLinkage( use, (Use) l, tr ); } } break; } } } } // collect from Profile list for ( Iterator e=tr.iterateProfile(); e.hasNext(); ) { for ( Iterator f=((Profile) e.next()).iterateLeaf(); f.hasNext(); ) { Leaf l = (Leaf) f.next(); if ( l instanceof Use ) checkLinkage( use, (Use) l, tr ); } } // check usages against all declared and temporary variables. Also // check linkage. Note that the declared variables must have a // linkage. It is permissable to declare variables, but not use // them. for ( Iterator i=use.keySet().iterator(); i.hasNext(); ) { String name = (String) i.next(); // check that the used variable is declared Declare dec = (Declare) tr.getDeclare(name); Local local = (Local) tr.getLocal(name); if ( dec == null && local == null ) throw new UndeclaredVariableException( "variable " + name + " is used, but not declared" + newline + tr.toXML("\t",null) ); // match up linkages. Note that a use linkage of -1 means // that we don't have any information on the used linkage. int dLinkage = ( dec==null ? local.getLink() : dec.getLink() ); int uLinkage = ((Integer) use.get(name)).intValue(); if ( uLinkage > -1 ) { if ( dLinkage == LFN.NONE && uLinkage != LFN.NONE || dLinkage == LFN.INPUT && uLinkage != LFN.INPUT || dLinkage == LFN.OUTPUT && uLinkage != LFN.OUTPUT || dLinkage == LFN.INOUT && uLinkage == LFN.NONE ) throw new IncompatibleLinkageException( "variable " + name + " uses incompatible linkages" + newline + tr.toXML("\t",null) ); } } break; case Definition.DERIVATION: Derivation dv = (Derivation) d; if ( dv.getUsesspace() == null ) { if ( this.m_vdlns != null ) { // either default uses namespace to vdlns dv.setUsesspace(this.m_vdlns); } else if ( d.getNamespace() != null ) { // or default uses namespace to derivation namespace dv.setUsesspace( d.getNamespace() ); } } // nothing really to check for derivations // note: Do *not* check here, if a DV has a matching TR, because // in the future, TR will be stored in distributed database(s). break; default: // this must not happen throw new IllegalArgumentException("Definition " + d.identify() + " is neither TR nor DV"); } } /** * Accessor: Appends a {@link Definition} to the container. The * namespace and version information will be, in case they are * missing, updated from the definitions namespace and version * respectively. * * @param d is the {@link Transformation} or {@link Derivation} * to append to the internal container. * @throws IndexOutOfBoundsException if the definition does not fit into * the container. */ public void addDefinition( Definition d ) throws IndexOutOfBoundsException { this.sanitizeDefinition(d); this.m_definitionList.add(d); } /** * Accessor: Inserts a {@link Definition} at a particular place. The * namespace and version information will be, in case they are * missing, updated from the definitions namespace and version * respectively.<p> * * Each component in this vector with an index greater or equal to the * specified index is shifted upward to have an index one greater than * the value it had previously. * * @param index is the position to insert a {@link Definition} * @param d is the {@link Transformation} or {@link Derivation} * to append to the internal container. * @throws IndexOutOfBoundsException if the definition does not fit into * the container. */ public void addDefinition( int index, Definition d ) throws IndexOutOfBoundsException { this.sanitizeDefinition(d); this.m_definitionList.add(index, d); } /** * Accessor: Search the database for the existence of a Definition with * the same primary keys and type as the parameter. * @param d is the Definition to search for * @return the position of the selfsame Definition, or -1 if not found. */ public int positionOfDefinition( Definition d ) { int n=0; for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ++n ) { if ( d.equals(i.next()) ) return n; } return -1; } /** * Accessor: Provides an iterator for the list of {@link Transformation} * and {@link Derivation}. * * @return the iterator to traverse the container of {@link Definition}s. * @see java.util.Enumeration * @deprecated Use the new Collection based interfaces */ public Enumeration enumerateDefinition() { return Collections.enumeration(this.m_definitionList); } /** * Obtains a vector of all definition instances that share the same * instance type. Please note that the definitions below may change * after the vector is obtained. * * @return a vector with all {@link Transformation} or {@link Derivation} * objects. The vector may have zero size, if no such instances exist. */ public java.util.List getDefinitionOfAKind( int type ) { ArrayList result = new ArrayList(); for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ) { Definition d = (Definition) i.next(); if ( d.getType() == type ) { result.add(d); } } return result; } /** * Accessor: Obtains a <code>Definition</code> at a particular place * in this container. * * @param index is the place to look up the element. * @return the <code>Definition</code> at the specified place. * @throws IndexOutOfBoundsException if the referenced position does * not exist. * @see Definition */ public Definition getDefinition(int index) throws IndexOutOfBoundsException { //-- check bound for index if ((index < 0) || (index >= this.m_definitionList.size())) throw new IndexOutOfBoundsException(); return (Definition) this.m_definitionList.get(index); } /** * Accessor: Obtains all {@link Definition}s available. * This array is a copy to avoid write-through modifications. * * @return an array containing either a {@link Transformation} * or {@link Derivation} at each position. * @deprecated Use the new Collection based interfaces */ public Definition[] getDefinition() { int size = this.m_definitionList.size(); Definition[] mDefinition = new Definition[size]; System.arraycopy( this.m_definitionList.toArray(new Definition[0]), 0, mDefinition, 0, size ); return mDefinition; } /** * Accessor: Counts the number of {@link Transformation} and * {@link Derivation} definitions. * @return item count. */ public int getDefinitionCount() { return this.m_definitionList.size(); } /** * Accessor: Obtains all {@link Definition}s available. * This list is read-only. * * @return an array containing either a {@link Transformation} * or {@link Derivation} at each position. */ public java.util.List getDefinitionList() { return Collections.unmodifiableList(this.m_definitionList); } /** * Accessor: Obtains the document namespace. * @return the namespace of the document, or null, if not used. * @see #setVdlns(java.lang.String) */ public String getVdlns() { return this.m_vdlns; } /** Accessor: Obtains the document version number. * * @return the version number from the document header, or null, * if unset. Since the version number is a required attribute, * it should never return null, only an empty string. */ public String getVersion() { return this.m_version; } /** * Accessor: Provides an iterator for the list of {@link Transformation} * and {@link Derivation} references. * * @return a list iterator to traverse the container of {@link Definition}s. * @see java.util.ListIterator */ public Iterator iterateDefinition() { return this.m_definitionList.iterator(); } /** * Accessor: Provides an iterator for the list of {@link Transformation} * and {@link Derivation} references. * * @return a list iterator to traverse the container of {@link Definition}s. * @see java.util.ListIterator */ public ListIterator listIterateDefinition() { return this.m_definitionList.listIterator(); } /** * Accessor: Provides an iterator for the list of {@link Transformation} * and {@link Derivation} references. * * @param start is the starting point of the iteration. * @return a list iterator to traverse the container of {@link Definition}s. * @see java.util.ListIterator */ public ListIterator listIterateDefinition( int start ) { return this.m_definitionList.listIterator(start); } /** * Accessor: Removes all definitions we know about. * @see Definition */ public void removeAllDefinition() { this.m_definitionList.clear(); } /** * Accessor: Removes a definition. Each component in this vector with * an index greater or equal to the specified index is shifted * downward to have an index one smaller than the value it had * previously. The size of this vector is decreased by 1. * * @param index is the position to remove the argument fragment from. * @return the removed Definition. * @exception ArrayIndexOutOfBoundsException if the index was invalid. * @see Definition */ public Definition removeDefinition(int index) { return (Definition) this.m_definitionList.remove(index); } /** * Accessor: Removes a definition named by its reference. Removes the * first occurrence of the specified element in this Vector. * * @param d is a definition instance that originated from this list. * @return true, if the first occurance of the element was deleted, * false, if there was nothing found to be removed. * @see Definition */ public boolean removeDefinition( Definition d ) { return this.m_definitionList.remove(d); } /** * Accessor: Sets the component at the specified index of this vector * to be the specified object. The previous component at that position * is discarded. The index must be a value greater than or equal to 0 * and less than the current size of the vector. * * @param index is the postion at which to replace a {@link Definition}. * @param d is either a {@link Transformation} or * {@link Derivation} to use for replacement. * @throws IndexOutOfBoundsException if the index was invalid. */ public Definition setDefinition(int index, Definition d) throws IndexOutOfBoundsException { //-- check bounds for index if ((index < 0) || (index >= this.m_definitionList.size())) { throw new IndexOutOfBoundsException(); } this.sanitizeDefinition(d); return (Definition) this.m_definitionList.set(index, d); } /** * Accessor: Replace all {@link Definition}s with a new list. * * @param definitionArray is an array of possibly mixed * {@link Transformation} and {@link Derivation} elements. * @deprecated Use the new Collection based interfaces */ public void setDefinition(Definition[] definitionArray) { this.m_definitionList.clear(); this.m_definitionList.addAll( Arrays.asList(definitionArray) ); } /** * Accessor: Replace all {@link Definition}s with a new list. * * @param definitions is an collection of possibly mixed * {@link Transformation} and {@link Derivation} elements. */ public void setDefinition(Collection definitions) { this.m_definitionList.clear(); this.m_definitionList.addAll(definitions); } /** * Accessor: Sets the document default namespace. * * @param vdlns is the new namespace to use. Note that the change will * <b>not</b> be propagated to contained elememts. * @see #getVdlns() */ public void setVdlns(String vdlns) { this.m_vdlns = vdlns; } /** * Accessor: Replaces the version number of the document. * * @param version is the new version number. * @see #getVersion() */ public void setVersion( String version ) { this.m_version = version; } /** * Dumps the content of the given element into a string. This function * traverses all sibling classes as necessary and converts the * data into textual output. * * @param stream is a stream opened and ready for writing. This can also * be a string stream for efficient output. * @exception IOException if something fishy happens to the stream. */ public void toString( Writer stream ) throws IOException { for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ) { ((Definition) i.next()).toString(stream); } } /** * Writes the header of the XML output. The output contains the special * strings to start an XML document, some comments, and the root element. * The latter points to the XML schema via XML Instances. * * @param stream is a stream opened and ready for writing. This can also * be a string stream for efficient output. * @param indent is a <code>String</code> of spaces used for pretty * printing. The initial amount of spaces should be an empty string. * The parameter is used internally for the recursive traversal. * @param namespace is the XML schema namespace prefix. If neither * empty nor null, each element will be prefixed with this prefix, * and the root element will map the XML namespace. * @exception IOException if something fishy happens to the stream. */ public void writeXMLHeader( Writer stream, String indent, String namespace ) throws IOException { String newline = System.getProperty( "line.separator", "\r\n" ); if ( indent != null && indent.length() > 0 ) stream.write( indent ); stream.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ); stream.write( newline ); if ( indent != null && indent.length() > 0 ) stream.write( indent ); stream.write( "<!-- generated: " ); stream.write( Currently.iso8601(false) ); stream.write( " -->" ); stream.write( newline ); if ( indent != null && indent.length() > 0 ) stream.write( indent ); stream.write( "<!-- generated by: " ); stream.write( System.getProperties().getProperty("user.name", "unknown") ); stream.write( " [" ); stream.write( System.getProperties().getProperty("user.region","??") ); stream.write( "] -->" ); stream.write( newline ); // start root element if ( indent != null && indent.length() > 0 ) stream.write( indent ); stream.write( '<' ); if ( namespace != null && namespace.length() > 0 ) { stream.write( namespace ); stream.write( ':' ); } stream.write( "definitions xmlns" ); if ( namespace != null && namespace.length() > 0 ) { stream.write( ':' ); stream.write( namespace ); } stream.write( "=\""); stream.write( SCHEMA_NAMESPACE ); stream.write( "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\""); stream.write( SCHEMA_NAMESPACE ); stream.write( ' ' ); stream.write( SCHEMA_LOCATION ); stream.write( '"' ); writeAttribute( stream, " vdlns=\"", this.m_vdlns ); writeAttribute( stream, " version=\"", this.m_version ); stream.write( '>' ); if ( indent != null ) stream.write( newline ); } /** * Dump the state of the current element as XML output. This function * traverses all sibling classes as necessary, and converts the data * into pretty-printed XML output. The stream interface should be able * to handle large output efficiently, if you use a buffered writer. * * @param stream is a stream opened and ready for writing. This can also * be a string stream for efficient output. * @param indent is a <code>String</code> of spaces used for pretty * printing. The initial amount of spaces should be an empty string. * The parameter is used internally for the recursive traversal. * @param namespace is the XML schema namespace prefix. If neither * empty nor null, each element will be prefixed with this prefix, * and the root element will map the XML namespace. * @exception IOException if something fishy happens to the stream. */ public void toXML( Writer stream, String indent, String namespace ) throws IOException { // write prefix writeXMLHeader( stream, indent, namespace ); // optionally write content if ( this.m_definitionList.size() > 0 ) { String newindent = indent==null ? null : indent + " "; for ( Iterator i=this.m_definitionList.iterator(); i.hasNext(); ) { ((Definition) i.next()).toXML( stream, newindent, namespace ); } } // finish document if ( indent != null && indent.length() > 0 ) stream.write( indent ); stream.write( "</" ); if ( namespace != null && namespace.length() > 0 ) { stream.write( namespace ); stream.write( ':' ); } stream.write( "definitions>" ); stream.write( System.getProperty( "line.separator", "\r\n" ) ); stream.flush(); // this is the only time we flush } }