001/*
002 * Copyright (c) 2016-2021 Chris K Wensel <chris@wensel.net>. 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.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 heretical.pointer.path.BaseNestedPointer;
035import heretical.pointer.path.NestedPointer;
036import heretical.pointer.path.NestedPointerCompiler;
037
038/**
039 * Class NestedGetFunction is the base class for {@link Function} implementations that want to simply retrieve
040 * values in nested object trees and return them as tuple fields.
041 * <p>
042 * For every field named in the fieldDeclaration {@link Fields} argument, there must be a corresponding
043 * {@code stringPointer} value.
044 * <p>
045 * If {@code failOnMissingNode} is {@code true} and the pointer returns a {@code null} value, the operation
046 * will fail.
047 * <p>
048 * If the fieldDeclaration Fields instance declares a type information, the {@code nestedCoercibleType} will be used to coerce
049 * any referenced child value to the expected field type.
050 */
051public class NestedGetFunction<Node, Result> extends NestedBaseOperation<Node, Result, Tuple> implements Function<Tuple>
052  {
053  protected NestedPointer<Node, Result>[] pointers;
054  protected boolean failOnMissingNode;
055
056  /**
057   * Constructor NestedGetFunction creates a new NestedGetFunction instance.
058   *
059   * @param nestedCoercibleType of NestedCoercibleType
060   * @param fieldDeclaration    of Fields
061   * @param failOnMissingNode   of boolean
062   * @param stringPointers      of String...
063   */
064  public NestedGetFunction( NestedCoercibleType<Node, Result> nestedCoercibleType, Fields fieldDeclaration, boolean failOnMissingNode, String... stringPointers )
065    {
066    super( nestedCoercibleType, fieldDeclaration );
067    this.failOnMissingNode = failOnMissingNode;
068
069    if( fieldDeclaration.size() != stringPointers.length )
070      throw new IllegalArgumentException( "pointers not same length as declared fields" );
071
072    NestedPointerCompiler<Node, Result> compiler = getNestedPointerCompiler();
073
074    this.pointers = new BaseNestedPointer[ stringPointers.length ];
075
076    for( int i = 0; i < stringPointers.length; i++ )
077      this.pointers[ i ] = compiler.nested( stringPointers[ i ] );
078    }
079
080  @Override
081  public void prepare( FlowProcess flowProcess, OperationCall<Tuple> operationCall )
082    {
083    operationCall.setContext( Tuple.size( pointers.length ) );
084    }
085
086  @Override
087  public void operate( FlowProcess flowProcess, FunctionCall<Tuple> functionCall )
088    {
089    Tuple resultTuple = functionCall.getContext();
090    Node argument = (Node) functionCall.getArguments().getObject( 0, getCoercibleType() );
091
092    extractResult( resultTuple, argument );
093
094    functionCall.getOutputCollector().add( resultTuple );
095    }
096
097  protected void extractResult( Tuple resultTuple, Node node )
098    {
099    for( int i = 0; i < pointers.length; i++ )
100      {
101      Node result = pointers[ i ].at( node );
102
103      if( failOnMissingNode && result == null )
104        throw new OperationException( "node missing from json node tree: " + pointers[ i ] );
105
106      Type declaredType = getFieldDeclaration().getType( i );
107      Object value = getCoercibleType().coerce( result, declaredType );
108
109      resultTuple.set( i, value );
110      }
111    }
112
113  protected static String[] asArray( Collection<String> values )
114    {
115    return values.toArray( new String[ values.size() ] );
116    }
117
118  protected static Fields asFields( Set<Fields> fields )
119    {
120    return fields.stream().reduce( Fields.NONE, Fields::append );
121    }
122  }