/*
* Copyright (c) 2007-2010 Concurrent, Inc. All Rights Reserved.
*
* Project and contact information: http://www.cascading.org/
*
* This file is part of the Cascading project.
*
* Cascading is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cascading is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cascading. If not, see <http://www.gnu.org/licenses/>.
*/
package cascading.pipe;
import java.beans.ConstructorProperties;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import cascading.flow.FlowElement;
import cascading.flow.Scope;
import cascading.tuple.Fields;
import cascading.util.Util;
/**
* Class Pipe is used to name branches in pipe assemblies, and as a base class for core
* processing model types, specifically {@link Each}, {@link Every}, {@link GroupBy}, and
* {@link CoGroup}.
* <p/>
* Pipes are chained together through their constructors.
* <p/>
* To effect a split in the pipe,
* simply pass a Pipe instance to two or more constructors of subsequent Pipe instances.
* </p>
* A join can be achieved by passing two or more Pipe instances to a {@link CoGroup} pipe.
* <p/>
* A merge can be achieved by passing two or more Pipe instances to a {@link GroupBy} pipe.
*
* @see Each
* @see Every
* @see GroupBy
* @see CoGroup
*/
public class Pipe implements FlowElement, Serializable
{
/** Field serialVersionUID */
private static final long serialVersionUID = 1L;
/** Field name */
private String name;
/** Field previous */
protected Pipe previous;
/** Field trace */
private String trace = Util.captureDebugTrace( getClass() );
/**
* Convenience method to create an array of Pipe instances.
*
* @param pipes vararg list of pipes
* @return array of pipes
*/
public static Pipe[] pipes( Pipe... pipes )
{
return pipes;
}
/**
* Convenience method for finding all Pipe names in an assembly.
*
* @param tails vararg list of all tails in given assembly
* @return array of Pipe names
*/
public static String[] names( Pipe... tails )
{
Set<String> names = new HashSet<String>();
collectNames( tails, names );
return names.toArray( new String[names.size()] );
}
private static void collectNames( Pipe[] pipes, Set<String> names )
{
for( Pipe pipe : pipes )
{
if( pipe instanceof SubAssembly )
names.addAll( Arrays.asList( ( (SubAssembly) pipe ).getTailNames() ) );
else
names.add( pipe.getName() );
collectNames( SubAssembly.unwind( pipe.getPrevious() ), names );
}
}
static Pipe[] resolvePreviousAll( Pipe... pipes )
{
Pipe[] resolved = new Pipe[pipes.length];
for( int i = 0; i < pipes.length; i++ )
resolved[ i ] = resolvePrevious( pipes[ i ] );
return resolved;
}
static Pipe resolvePrevious( Pipe pipe )
{
if( pipe instanceof Group || pipe instanceof Operator )
return pipe;
Pipe[] pipes = pipe.getPrevious();
if( pipes.length > 1 )
throw new IllegalStateException( "cannot resolve SubAssemblies with multiple tails at this time" );
for( Pipe previous : pipes )
{
if( previous instanceof Group || previous instanceof Operator )
return previous;
return resolvePrevious( previous );
}
return pipe;
}
protected Pipe()
{
}
@ConstructorProperties({"previous"})
protected Pipe( Pipe previous )
{
this.previous = previous;
verifyPipe();
}
/**
* Constructor Pipe creates a new Pipe instance with the given name. This is useful as the 'start' or head
* of a pipe assembly.
*
* @param name name for this branch of Pipes
*/
@ConstructorProperties({"name"})
public Pipe( String name )
{
this.name = name;
}
/**
* Constructor Pipe creates a new Pipe instance with the given name and previous Pipe instance. This is useful for
* naming a branch in a pipe assembly. Or renaming the branch mid-way down.
*
* @param name name for this branch of Pipes
* @param previous previous Pipe to receive input Tuples from
*/
@ConstructorProperties({"name", "previous"})
public Pipe( String name, Pipe previous )
{
this.name = name;
this.previous = previous;
verifyPipe();
}
private void verifyPipe()
{
if( !( previous instanceof SubAssembly ) )
return;
String[] strings = ( (SubAssembly) previous ).getTailNames();
if( strings.length != 1 )
throw new IllegalArgumentException( "pipe assembly must not return more than one tail pipe instance, found " + Util.join( strings, ", " ) );
}
/**
* Get the name of this pipe. Guaranteed non-null.
*
* @return String the name of this pipe
*/
public String getName()
{
if( name != null )
return name;
if( previous != null )
return previous.getName();
return "ANONYMOUS";
}
/**
* Get all the upstream pipes this pipe is connected to. This method will return the Pipe instances
* passed on the constructors as inputs to this Pipe instance.
*
* @return all the upstream pipes this pipe is connected to.
*/
public Pipe[] getPrevious()
{
if( previous == null )
return new Pipe[0];
return new Pipe[]{previous};
}
/**
* Method getHeads returns the first Pipe instances in this pipe assembly.
*
* @return the first (type Pipe[]) of this Pipe object.
*/
public Pipe[] getHeads()
{
Pipe[] pipes = getPrevious();
if( pipes.length == 0 )
return new Pipe[]{this};
if( pipes.length == 1 )
return pipes[ 0 ].getHeads();
Set<Pipe> heads = new HashSet<Pipe>();
for( Pipe pipe : pipes )
Collections.addAll( heads, pipe.getHeads() );
return heads.toArray( new Pipe[heads.size()] );
}
/** @see FlowElement#outgoingScopeFor */
public Scope outgoingScopeFor( Set<Scope> incomingScopes )
{
return incomingScopes.iterator().next();
}
/** @see FlowElement#resolveIncomingOperationFields */
public Fields resolveIncomingOperationFields( Scope incomingScope )
{
throw new IllegalStateException( "resolveIncomingOperationFields should never be called" );
}
/** @see FlowElement#resolveFields(Scope) */
public Fields resolveFields( Scope scope )
{
throw new IllegalStateException( "resolveFields should never be called" );
}
/**
* Method getTrace returns a String that pinpoint where this instance was created for debugging.
*
* @return String
*/
public String getTrace()
{
return trace;
}
@Override
public String toString()
{
return getClass().getSimpleName() + "(" + getName() + ")";
}
Scope getFirst( Set<Scope> incomingScopes )
{
return incomingScopes.iterator().next();
}
@Override
public boolean isEquivalentTo( FlowElement element )
{
if( element == null )
return false;
if( this == element )
return true;
return getClass() == element.getClass();
}
@Override
public boolean equals( Object object )
{
// we cannot test equality by names for this class, prevents detection of dupe names in heads or tails
return this == object;
}
@Override
public int hashCode()
{
return 31 * getName().hashCode() + getClass().hashCode();
}
/**
* Method print is used internally.
*
* @param scope of type Scope
* @return String
*/
public String print( Scope scope )
{
StringBuffer buffer = new StringBuffer();
printInternal( buffer, scope );
return buffer.toString();
}
protected void printInternal( StringBuffer buffer, Scope scope )
{
buffer.append( getClass().getSimpleName() ).append( "('" ).append( getName() ).append( "')" );
}
}