/* * 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.Separator; import org.griphyn.vdl.util.SequenceGenerator; import java.util.*; import java.io.IOException; import java.io.Writer; import java.io.Serializable; /** * <code>Call</code> is an implementation of an anonymous * <code>Derivation</code>. A call describes the mutable part * concerning input, processing, and output (IPO). Calls can * only be part of a <code>CompoundTransformation</code>.<p> * * A call parametrizes the template provided by a * <code>Transformation</code> with actual values. Think of a call * as something akin to a C function call. The call provides the * actual parameter to a job. The immutable parts are hidden in a * <code>Transformation</code>.<p> * * FIXME: A <code>Call</code> is essentially an anonymous * <code>Derivation</code> that occurs within a * <code>CompoundTransformation</code>. Thus, the first two should * share code. Also, the latter two already share code. Therefore, * the class hierarchies need to be re-designed, and attribute groups * should be out-sourced. * * @author Jens-S. Vöckler * @author Yong Zhao * @version $Revision $ * * @see Definition * @see Definitions * @see Transformation * @see Derivation */ public class Call extends VDL implements HasPass, Serializable { /** * Though most <code>Call</code>s may have a name of their own, * most of the times, though, calls are anonymous. A call * must provide the name of the <code>Transformation</code> that * it calls, though. * * @see Definition * @see Transformation */ private String m_uses; /** * The namespace in which a call resides can differ from the * namespace that the transformation lives in. This argument provides * the namespace of the <code>Transformation</code> to call. * @see Definition * @see Transformation */ private String m_usesspace; /** * Any <code>Transformation</code> may exist in multiple versions. * This argument specifies the minimum permissable version that can * be used. FIXME: versioning is not really supported. */ private String m_maxIncludeVersion; /** * Any <code>Transformation</code> may exist in multiple versions. * This argument specifies the maximum permissable version that can * be used. FIXME: versioning is not really supported. */ private String m_minIncludeVersion; /** * Actual arguments used when calling a {@link Transformation} are * matched up with the formal arguments of the transformation by their * names. */ private TreeMap m_passMap; /** * Since <code>Call</code> is an anonymous <code>Derivation</code>, * we still need some unique identifiers for the call. This is a * sequence generator. */ private static SequenceGenerator s_sequence = new SequenceGenerator(); /** * This is the sequence number assigned by the c'tor to this call. */ private String m_id; /** * ctor. */ public Call() { this.m_id = "anon" + Call.s_sequence.generate(); this.m_passMap = new TreeMap(); } /** * Convenience ctor: Names the used <code>Transformation</code> * * @param uses is the name of the <code>Transformation</code> * @see Transformation */ public Call( String uses ) { this.m_id = "anon" + Call.s_sequence.generate(); this.m_passMap = new TreeMap(); this.m_uses = uses; } /** * Convenience ctor: Supplies the used <code>Transformation</code> * as well as the permissable version range. * * @param uses is the name of the <code>Transformation</code>. * @param min is the minimum inclusive permissable version. * @param max is the maximum inclusive permissable version. * @see Transformation */ public Call( String uses, String min, String max ) { this.m_id = "anon" + Call.s_sequence.generate(); this.m_passMap = new TreeMap(); this.m_uses = uses; this.m_minIncludeVersion = min; this.m_maxIncludeVersion = max; } /** * Accessor: Adds an actual argument to the bag of arguments. * * @param vPass is the new actual argument to add. * @see Pass */ public void addPass( Pass vPass ) { this.m_passMap.put(vPass.getBind(),vPass); } /* * won't work with maps * public void addPass( int index, Pass vPass ) throws IndexOutOfBoundsException { this.m_passList.insertElementAt(vPass, index); } */ /** * Accessor: Provides an iterator for the bag of actual arguments. * @return the iterator for <code>Pass</code> elements. * @see Pass * @see java.util.Enumeration * @deprecated Use the new Collection based interfaces */ public Enumeration enumeratePass() { // return this.m_passMap.elements(); return Collections.enumeration(this.m_passMap.values()); } /** * Determines all LFN instances of a given scalar that match the * specified linkage. This is a higher-level method employing the * given API. Note that also linkage of NONE will not be found in * wildcard search mode. * * @param linkage is the linkage to check for, -1 for any linkage. * @return a set of all logical filenames that match the linkage and * were part of the scalar. The result may be an empty set, if no such * result were to be found. For a linkage of -1, complete LFNs will be * returned, for any other linkage, just the filename will be * returned. * * @see Value#getLFNList( int ) * @see LFN */ public java.util.List getLFNList( int linkage ) { java.util.List result = new ArrayList(); for ( Iterator i = this.iteratePass(); i.hasNext(); ) { Value actual = ((Pass) i.next()).getValue(); result.addAll( actual.getLFNList(linkage) ); } return result; } /** * Determines if the list contains an LFN of the specified linkage. * The logic uses short-circuit evaluation, thus finding things is * faster than not finding things. Searching a list is a potentially * expensive method. * * @param filename is the name of the LFN * @param linkage is the linkage to check for, -1 for any linkage type. * @return true if the LFN is contained in the scalar, false otherwise. * * @see Value#containsLFN( String, int ) * @see LFN */ public boolean containsLFN( String filename, int linkage ) { for ( Iterator i = this.iteratePass(); i.hasNext(); ) { Value actual = ((Pass) i.next()).getValue(); if ( actual.containsLFN( filename, linkage ) ) return true; } return false; } /** * Accessor: Obtains the maximum inclusive version permissable for * binding to a {@link Transformation}. * * @return the maximum inclusive version number. * @see #setMaxIncludeVersion( java.lang.String ) */ public String getMaxIncludeVersion() { return this.m_maxIncludeVersion; } /** * Accessor: Obtains the minimum inclusive version permissable for * binding to a {@link Transformation}. * * @return the minimum inclusive version number. * @see #setMinIncludeVersion( java.lang.String ) */ public String getMinIncludeVersion() { return this.m_minIncludeVersion; } /** * Accessor: Obtains an actual argument identified by the bound variable. * * @param name is the binding name. * @return the bound value to the given name. * @see Pass */ public Pass getPass(String name) { return (Pass) this.m_passMap.get(name); } /** * Accessor: Obtains the bag of actual arguments as array. Note that the * order is arbitrary. * * @return an array containing all bound variables. * @see Pass * @deprecated Use the new Collection based interfaces */ public Pass[] getPass() { int size = this.m_passMap.size(); Pass[] mPass = new Pass[size]; this.m_passMap.values().toArray(mPass); return mPass; } /** * Accessor: Counts the number of actual arguments. * * @return the number of actual arguments in the internal bag. */ public int getPassCount() { return this.m_passMap.size(); } /** * Accessor: Gets an array of all values that constitute the current * content. This list is read-only. * * @return an array with <code>Pass</code> elements. * @see Pass */ public java.util.List getPassList() { return Collections.unmodifiableList( new ArrayList(this.m_passMap.values()) ); } /** * Accessor: Obtains all actual arguments. The map is a read-only * map to avoid modifications outside the API. * * @return a map will all actual arguments. * @see Pass */ public java.util.Map getPassMap() { return Collections.unmodifiableMap( this.m_passMap ); } /** * Accessor: Provides an iterator for the bag of actual arguments. * @return an iterator to walk the <code>Pass</code> list with. * @see Pass */ public Iterator iteratePass() { return this.m_passMap.values().iterator(); } /* NOT APPLICABLE * * Accessor: Provides an iterator for the bag of actual arguments. * @return an iterator to walk the <code>Pass</code> list with. * @see Pass * public ListIterator listIteratePass() { return (new ArrayList( this.m_passMap.values() ).listIterator()); } */ /** * Accessor: Obtains the name of the logical {@link Transformation} * that this call refers to. * * @see #setUses( java.lang.String ) */ public java.lang.String getUses() { return this.m_uses; } /** * Accessor: Obtains the namespace of the logical {@link Transformation} * that this call refers to. * * @see #setUsesspace( java.lang.String ) */ public java.lang.String getUsesspace() { return this.m_usesspace; } /** * Instance method for matching an external version against the inclusive * version range. * @param version is an externally supplied version to be checked, * if it is within the inclusive interval of min and max. * @return true, if the version is in range, false otherwise. * @see Derivation#match( String, String, String ) */ public boolean match( String version ) { return Derivation.match( version, this.m_minIncludeVersion, this.m_maxIncludeVersion ); } /** * Accessor: Removes all actual arguments. Effectively empties the bag. */ public void removeAllPass() { this.m_passMap.clear(); } /** * Accessor: Removes a specific actual argument. * * @param name is the bound variable name of the argument to remove. * @return the object that was removed, or null, if not found. * @see Pass */ public Pass removePass( String name ) { return (Pass) this.m_passMap.remove(name); } /** * Accessor: Sets the maximum inclusive permissable version of * a logical transformation to run with. * * @param miv is the (new) maximum inclusive version. * @see #getMaxIncludeVersion() */ public void setMaxIncludeVersion(String miv ) { this.m_maxIncludeVersion = miv == null ? null : miv.trim(); } /** * Accessor: Sets the minimum inclusive permissable version of * a logical transformation to run with. * * @param miv is the (new) minimum inclusive version. * @see #getMinIncludeVersion() */ public void setMinIncludeVersion(String miv) { this.m_minIncludeVersion = miv == null ? null : miv.trim(); } /** * Accessor: Adds a new or overwrites an existing actual argument. * * @param vPass is a new actual argument with bound name and value. * @see Pass */ public void setPass(Pass vPass) { this.m_passMap.put(vPass.getBind(),vPass); } /** * Accessor: Replaces the bag of actual argument with new arguments. * * @param passArray is the new actual argument list. * @see Pass * @deprecated Use the new Collection based interfaces */ public void setPass(Pass[] passArray) { //-- copy array this.m_passMap.clear(); for (int i = 0; i < passArray.length; i++) { this.m_passMap.put(passArray[i].getBind(),passArray[i]); } } /** * Accessor: Replaces the bag of actual argument with a bag of * new arguments. * * @param passes is the new actual argument collection. * @see Pass */ public void setPass(Collection passes) { this.m_passMap.clear(); for ( Iterator i=passes.iterator(); i.hasNext(); ) { Pass p = (Pass) i.next(); this.m_passMap.put(p.getBind(),p); } } /** * Accessor: Replaces the bag of actual argument with a map of * new arguments. * * @param passes is the new actual argument map. * @see Pass */ public void setPass( Map passes ) { this.m_passMap.clear(); this.m_passMap.putAll(passes); } /** * Accessor: Sets a new name for a logical <code>Transformation</code> * to call. * * @param uses is the new name of the <code>Transformation</code> to use. * @see #getUses() * @see Transformation */ public void setUses(String uses) { this.m_uses = uses; } /** * Accessor: Sets a new namespace identifier for a logical * <code>Transformation</code> to call. * * @param usesspace is the new namespace of the * <code>Transformation</code>. * @see #getUsesspace() * @see Transformation */ public void setUsesspace(String usesspace) { this.m_usesspace = usesspace; } /** * Generates a pseudo id for this Call. * FIXME: With the advent of a database, we'll need to fix this to * something like the primary key. */ public String shortID() { return this.m_id; } /** * Since calls are anonymous derivations, this function can only * construct the mapped transformation * * @return a string describing the call */ public String identify() { StringBuffer result = new StringBuffer(); result.append( this.m_id ); result.append( "->" ); // and now for the called part result.append( Separator.combine( this.m_usesspace, this.m_uses, getMinIncludeVersion(), getMaxIncludeVersion() ) ); // result return result.toString(); } /** * 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. Note that order of the actual arguments * is not preserved. * * @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 { String newline = System.getProperty( "line.separator", "\r\n" ); String me = this.identify(); stream.write( "call " ); stream.write( me.substring( me.indexOf("->")+2 ) ); stream.write( '(' ); if ( this.m_passMap.size() > 0 ) { stream.write( newline ); for ( Iterator i=this.m_passMap.values().iterator(); i.hasNext(); ) { stream.write( '\t' ); ((Pass) i.next()).toString(stream); if ( i.hasNext() ) stream.write( "," + newline ); } } stream.write( " )" ); } /** * 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. * If a <code>null</code> value is specified, no indentation nor * linefeeds will be generated. * @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. * @see org.griphyn.vdl.Chimera#writeAttribute( Writer, String, String ) * @exception IOException if something fishy happens to the stream. */ public void toXML( Writer stream, String indent, String namespace ) throws IOException { String newline = System.getProperty( "line.separator", "\r\n" ); String tag = ( namespace != null && namespace.length() > 0 ) ? namespace + ":call" : "call"; // open tag if ( indent != null && indent.length() > 0 ) stream.write( indent ); stream.write( '<' ); stream.write( tag ); writeAttribute( stream, " usesspace=\"", this.m_usesspace ); writeAttribute( stream, " uses=\"", this.m_uses ); writeAttribute( stream, " minIncludeVersion=\"", this.m_minIncludeVersion ); writeAttribute( stream, " maxIncludeVersion=\"", this.m_maxIncludeVersion ); if ( this.m_passMap.size() == 0 ) { // empty argument list stream.write( "/>" ); } else { stream.write( '>' ); if ( indent != null ) stream.write( newline ); // dump content String newindent = indent==null ? null : indent+" "; for ( Iterator i=this.m_passMap.values().iterator(); i.hasNext(); ) { ((Pass) i.next()).toXML( stream, newindent, namespace ); } if ( indent != null && indent.length() > 0 ) stream.write( indent ); stream.write( "</" ); stream.write( tag ); stream.write( '>' ); } if ( indent != null ) stream.write( newline ); } }