    001/*
    002 * Copyright (c) 2016-2017 Chris K Wensel. All Rights Reserved.
    003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
    004 *
    005 * Project and contact information: http://www.cascading.org/
    006 *
    007 * This file is part of the Cascading project.
    008 *
    009 * Licensed under the Apache License, Version 2.0 (the "License");
    010 * you may not use this file except in compliance with the License.
    011 * You may obtain a copy of the License at
    012 *
    013 *     http://www.apache.org/licenses/LICENSE-2.0
    014 *
    015 * Unless required by applicable law or agreed to in writing, software
    016 * distributed under the License is distributed on an "AS IS" BASIS,
    017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    018 * See the License for the specific language governing permissions and
    019 * limitations under the License.
    020 */
    021
    022package cascading.tuple;
    023
    024import java.io.IOException;
    025import java.util.function.Supplier;
    026
    027/**
    028 * Interface TupleEntryCollector is used to allow {@link cascading.operation.BaseOperation} instances to emit
    029 * one or more result {@link Tuple} values.
    030 * <p/>
    031 * The general rule in Cascading is if you are handed a Tuple, you cannot change or cache it. Attempts at modifying
    032 * such a Tuple will result in an Exception. Preventing caching is harder, see below.
    033 * <p/>
    034 * If you create the Tuple, you can re-use or modify it.
    035 * <p/>
    036 * When calling {@link #add(Tuple)} or {@link #add(TupleEntry)}, you are passing a Tuple to the down stream pipes and
    037 * operations. Since no downstream operation may modify or cache the Tuple instance, it is safe to re-use the Tuple
    038 * instance when {@code add()} returns.
    039 * <p/>
    040 * That said, Tuple copies do get cached in order to perform specific operations in the underlying platforms. Currently
    041 * only a shallow copy is made (via the {@link Tuple} copy constructor). Thus, any mutable type or collection
    042 * placed inside a Tuple will not be copied, but will likely be cached if a copy of the Tuple passed downstream is
    043 * copied.
    044 * <p/>
    045 * So any subsequent changes to that nested type or collection will be reflected in the cached copy, a likely
    046 * source of hard to find errors.
    047 * <p/>
    048 * There is currently no way to specify that a deep copy must be performed when making a Tuple copy.
    049 */
    050public abstract class TupleEntryCollector implements AutoCloseable
    051  {
    052  protected TupleEntry tupleEntry = new TupleEntry( Fields.UNKNOWN, null, true );
    053
    054  protected TupleEntryCollector()
    055    {
    056    }
    057
    058  /**
    059   * Constructor TupleCollector creates a new TupleCollector instance.
    060   *
    061   * @param declared of type Fields
    062   */
    063  public TupleEntryCollector( Fields declared )
    064    {
    065    setFields( declared );
    066    }
    067
    068  public void setFields( Fields declared )
    069    {
    070    if( declared == null )
    071      throw new IllegalArgumentException( "declared fields must not be null" );
    072
    073    if( declared.isUnknown() || declared.isAll() )
    074      return;
    075
    076    // if operation declared ARGS, then the arguments are a selector and must be forced to declared
    077    declared = Fields.asDeclaration( declared );
    078
    079    this.tupleEntry = new TupleEntry( declared, Tuple.size( declared.size() ), true );
    080    }
    081
    082  /**
    083   * Method addTupleEntry inserts the given {@link TupleEntry} into the outgoing stream. Note the method {@link #add(Tuple)} is
    084   * more efficient as it simply calls {@link TupleEntry#getTuple()};
    085   * <p/>
    086   * See {@link cascading.tuple.TupleEntryCollector} on when and how to re-use a Tuple instance.
    087   *
    088   * @param supplier of type Supplier
    089   */
    090  public void addTupleEntry( Supplier<TupleEntry> supplier )
    091    {
    092    add( supplier.get() );
    093    }
    094
    095  /**
    096   * Method add inserts the given {@link TupleEntry} into the outgoing stream. Note the method {@link #add(Tuple)} is
    097   * more efficient as it simply calls {@link TupleEntry#getTuple()};
    098   * <p/>
    099   * See {@link cascading.tuple.TupleEntryCollector} on when and how to re-use a Tuple instance.
    100   *
    101   * @param tupleEntry of type TupleEntry
    102   */
    103  public void add( TupleEntry tupleEntry )
    104    {
    105    Fields expectedFields = this.tupleEntry.getFields();
    106    TupleEntry outgoingEntry = this.tupleEntry;
    107
    108    if( expectedFields.isUnknown() || expectedFields.equals( tupleEntry.getFields() ) )
    109      outgoingEntry = tupleEntry;
    110    else
    111      outgoingEntry.setTuple( selectTupleFrom( tupleEntry, expectedFields ) );
    112
    113    safeCollect( outgoingEntry );
    114    }
    115
    116  private Tuple selectTupleFrom( TupleEntry tupleEntry, Fields expectedFields )
    117    {
    118    try
    119      {
    120      return tupleEntry.selectTuple( expectedFields );
    121      }
    122    catch( TupleException exception )
    123      {
    124      Fields givenFields = tupleEntry.getFields();
    125      String string = "given TupleEntry fields: " + givenFields.printVerbose();
    126      string += " do not match the operation declaredFields: " + expectedFields.printVerbose();
    127      string += ", operations must emit tuples that match the fields they declare as output";
    128
    129      throw new TupleException( string, exception );
    130      }
    131    }
    132
    133  /**
    134   * Method addTuple inserts the given {@link Tuple} into the outgoing stream.
    135   * <p/>
    136   * See {@link cascading.tuple.TupleEntryCollector} on when and how to re-use a Tuple instance.
    137   *
    138   * @param supplier of type Supplier
    139   */
    140  public void addTuple( Supplier<Tuple> supplier )
    141    {
    142    add( supplier.get() );
    143    }
    144
    145  /**
    146   * Method add inserts the given {@link Tuple} into the outgoing stream.
    147   * <p/>
    148   * See {@link cascading.tuple.TupleEntryCollector} on when and how to re-use a Tuple instance.
    149   *
    150   * @param tuple of type Tuple
    151   */
    152  public void add( Tuple tuple )
    153    {
    154    if( !tupleEntry.getFields().isUnknown() && tupleEntry.getFields().size() != tuple.size() )
    155      throw new TupleException( "operation added the wrong number of fields, expected: " + tupleEntry.getFields().print() + ", got result size: " + tuple.size() );
    156
    157    boolean isUnmodifiable = tuple.isUnmodifiable();
    158
    159    tupleEntry.setTuple( tuple );
    160
    161    try
    162      {
    163      safeCollect( tupleEntry );
    164      }
    165    finally
    166      {
    167      Tuples.setUnmodifiable( tuple, isUnmodifiable );
    168      }
    169    }
    170
    171  private void safeCollect( TupleEntry tupleEntry )
    172    {
    173    try
    174      {
    175      collect( tupleEntry );
    176      }
    177    catch( IOException exception )
    178      {
    179      throw new TupleException( "unable to collect tuple", exception );
    180      }
    181    }
    182
    183  protected abstract void collect( TupleEntry tupleEntry ) throws IOException;
    184
    185  /**
    186   * Method close closes the underlying resource being written to.
    187   * <p/>
    188   * This method should be called when when an instance is returned via
    189   * {@link cascading.tap.Tap#openForWrite(cascading.flow.FlowProcess)}
    190   * and no more {@link Tuple} instances will be written out.
    191   * <p/>
    192   * This method must not be called when an instance is returned from {@code getOutputCollector()} from any of
    193   * the relevant {@link cascading.operation.OperationCall} implementations (inside a Function, Aggregator, or Buffer).
    194   */
    195  @Override
    196  public void close()
    197    {
    198    // do nothing
    199    }
    200  }