001/*
002 * Copyright (c) 2016-2017 Chris K Wensel. All Rights Reserved.
003 *
004 * Project and contact information: http://www.cascading.org/
005 *
006 * This file is part of the Cascading project.
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *     http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020
021package cascading.nested.core;
022
023import java.util.Map;
024import java.util.function.BiConsumer;
025
026import cascading.flow.FlowProcess;
027import cascading.operation.Function;
028import cascading.operation.FunctionCall;
029import cascading.operation.OperationCall;
030import cascading.tuple.Fields;
031import cascading.tuple.Tuple;
032import cascading.tuple.TupleEntry;
033import cascading.tuple.Tuples;
034import heretical.pointer.operation.Copier;
035
036/**
037 * Class NestedBaseCopyFunction is the base class for {@link Function} implementations that rely on the
038 * {@link CopySpec} class when declaring transformations on nested object types.
039 * <p>
040 * Specifically, {@code *CopyAsFunction} and {@code *CopyIntoFunction} classes create or update (respectively)
041 * nested object types from a Function argument where the field value is a nest object type.
042 * <p>
043 * In the case of a {@code *CopyIntoFunction} the last argument in the {@code arguments} {@link TupleEntry} will be
044 * the object the CopySpec copies values into.
045 * <p>
046 * In the case of a {@code *CopyAsFunction} a new root object will be created for the CopySpec to copy values into.
047 * <p>
048 * Note the arguments TupleEntry will be passed to any {@link Transform} instances that are resettable
049 * {@link Transform#isResettable()} allowing for parameterized transformations on child values as they are copied
050 * to the new location.
051 * <p>
052 * In the case of JSON objects, a single JSON object is selected as an argument so that values contained in that object
053 * can be copied into the new object.
054 * <p>
055 * For selecting the values from multiple existing field values in order to create a new object or update an existing one
056 * see {@link NestedBaseBuildFunction} sub-classes.
057 *
058 * @see CopySpec
059 */
060public abstract class NestedBaseCopyFunction<Node, Result> extends NestedSpecBaseOperation<Node, Result, NestedBaseCopyFunction.Context> implements Function<NestedBaseCopyFunction.Context>
061  {
062  protected static class Context
063    {
064    public BiConsumer<TupleEntry, Fields> resetTransform = ( t, f ) -> {};
065    public Tuple result;
066    public Fields fields;
067
068    public Context( NestedBaseCopyFunction<?, ?> function, boolean resetTransform, Tuple result, Fields fields )
069      {
070      if( resetTransform )
071        this.resetTransform = function::resetTransforms;
072
073      this.result = result;
074      this.fields = fields;
075      }
076    }
077
078  protected Copier<Node, Result> copier;
079
080  public NestedBaseCopyFunction( NestedCoercibleType<Node, Result> nestedCoercibleType, Fields fieldDeclaration, CopySpec... copySpecs )
081    {
082    super( nestedCoercibleType, fieldDeclaration );
083    this.copier = new Copier<>( getNestedPointerCompiler(), copySpecs );
084
085    if( fieldDeclaration.isDefined() && fieldDeclaration.size() != 1 )
086      throw new IllegalArgumentException( "can only return a single field" );
087    }
088
089  @Override
090  public void prepare( FlowProcess flowProcess, OperationCall<Context> operationCall )
091    {
092    super.prepare( flowProcess, operationCall );
093
094    boolean resetTransform = operationCall.getArgumentFields().size() > ( isInto() ? 2 : 1 );
095    Tuple result = Tuple.size( 1 );
096    Fields fields = operationCall.getArgumentFields().subtract( Fields.FIRST );
097
098    operationCall.setContext( new Context( this, resetTransform, result, fields ) );
099    }
100
101  @Override
102  public void operate( FlowProcess flowProcess, FunctionCall<Context> functionCall )
103    {
104    Context context = functionCall.getContext();
105    TupleEntry arguments = functionCall.getArguments();
106
107    context.resetTransform.accept( arguments, context.fields );
108
109    Node fromNode = (Node) arguments.getObject( 0, getCoercibleType() );
110    Node resultNode = getResultNode( functionCall );
111
112    copier.copy( fromNode, resultNode );
113
114    context.result.set( 0, resultNode );
115
116    functionCall.getOutputCollector().add( context.result );
117    }
118
119  protected void resetTransforms( TupleEntry arguments, Fields fields )
120    {
121    Map<Comparable, Object> values = Tuples.asComparableMap( fields, arguments );
122
123    copier.resetTransforms( values );
124    }
125  }