001/* 002 * Copyright (c) 2007-2022 The Cascading Authors. All Rights Reserved. 003 * 004 * Project and contact information: https://cascading.wensel.net/ 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 }