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.lang.reflect.Type;
024import java.util.Collection;
025import java.util.Set;
026
027import cascading.flow.FlowProcess;
028import cascading.operation.Function;
029import cascading.operation.FunctionCall;
030import cascading.operation.OperationCall;
031import cascading.operation.OperationException;
032import cascading.tuple.Fields;
033import cascading.tuple.Tuple;
034import cascading.util.Util;
035import heretical.pointer.path.BaseNestedPointer;
036import heretical.pointer.path.NestedPointer;
037import heretical.pointer.path.NestedPointerCompiler;
038
039/**
040 * Class NestedGetFunction is the base class for {@link Function} implementations that want to simply retrieve
041 * values in nested object trees and return them as tuple fields.
042 * <p>
043 * For every field named in the fieldDeclaration {@link Fields} argument, there must be a corresponding
044 * {@code stringPointer} value.
045 * <p>
046 * If {@code failOnMissingNode} is {@code true} and the pointer returns a {@code null} value, the operation
047 * will fail.
048 * <p>
049 * If the fieldDeclaration Fields instance declares a type information, the {@code nestedCoercibleType} will be used to coerce
050 * any referenced child value to the expected field type.
051 */
052public class NestedGetFunction<Node, Result> extends NestedBaseOperation<Node, Result, Tuple> implements Function<Tuple>
053  {
054  protected interface Setter<Node>
055    {
056    void set( int i, Node value );
057    }
058  protected final NestedPointer<Node, Result>[] pointers;
059  protected final boolean failOnMissingNode;
060
061  /**
062   * Constructor NestedGetFunction creates a new NestedGetFunction instance.
063   *
064   * @param nestedCoercibleType of NestedCoercibleType
065   * @param fieldDeclaration    of Fields
066   * @param failOnMissingNode   of boolean
067   * @param stringPointers      of String...
068   */
069  public NestedGetFunction( NestedCoercibleType<Node, Result> nestedCoercibleType, Fields fieldDeclaration, boolean failOnMissingNode, String... stringPointers )
070    {
071    super( nestedCoercibleType, fieldDeclaration );
072    this.failOnMissingNode = failOnMissingNode;
073
074    verify( stringPointers );
075
076    NestedPointerCompiler<Node, Result> compiler = getNestedPointerCompiler();
077
078    this.pointers = new BaseNestedPointer[ stringPointers.length ];
079
080    for( int i = 0; i < stringPointers.length; i++ )
081      this.pointers[ i ] = compiler.nested( stringPointers[ i ] );
082    }
083
084  protected static String[] asArray( Collection<String> values )
085    {
086    return values.toArray( new String[ values.size() ] );
087    }
088
089  protected static Fields asFields( Set<Fields> fields )
090    {
091    return fields.stream().reduce( Fields.NONE, Fields::append );
092    }
093
094  protected void verify( String[] stringPointers )
095    {
096    if( getFieldDeclaration().size() != stringPointers.length )
097      throw new IllegalArgumentException( "pointers not same length as declared fields" );
098    }
099
100  @Override
101  public void prepare( FlowProcess flowProcess, OperationCall<Tuple> operationCall )
102    {
103    operationCall.setContext( Tuple.size( pointers.length ) );
104    }
105
106  @Override
107  public void operate( FlowProcess flowProcess, FunctionCall<Tuple> functionCall )
108    {
109    Tuple resultTuple = functionCall.getContext();
110    Node argument = (Node) functionCall.getArguments().getObject( 0, getCoercibleType() );
111
112    extractResult( resultTuple, argument );
113
114    functionCall.getOutputCollector().add( resultTuple );
115    }
116
117  protected void extractResult( Tuple resultTuple, Node node )
118    {
119    extractResult( ( i, result ) -> setInto( resultTuple, i, result ), node );
120    }
121
122  protected void setInto( Tuple resultTuple, int i, Node result )
123    {
124    Type declaredType = getFieldDeclaration().getType( i );
125    Object value = getCoercibleType().coerce( result, declaredType );
126
127    resultTuple.set( i, value );
128    }
129
130  protected void extractResult( Setter<Node> resultSetter, Node node )
131    {
132    for( int i = 0; i < pointers.length; i++ )
133      {
134      Node result = pointers[ i ].at( node );
135
136      if( failOnMissingNode && result == null )
137        throw new OperationException( "node missing from json node tree: " + pointers[ i ] );
138
139      try
140        {
141        resultSetter.set( i, result );
142        }
143      catch( Exception exception )
144        {
145        throw new OperationException( "value at: " + pointers[ i ] + ", cannot be handled, got: " + Util.truncate( result.toString(), 25 ), exception );
146        }
147      }
148    }
149  }