/*
* 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 );
}
}