001/*
002 * Copyright (c) 2016-2017 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 the fieldDeclaration Fields instance declares a type information, the {@code nestedCoercibleType} will be used to coerce
046 * any referenced child value to the expected field type.
047 */
048public class NestedGetFunction<Node, Result> extends NestedBaseOperation<Node, Result, Tuple> implements Function<Tuple>
049  {
050  protected NestedPointer<Node, Result>[] pointers;
051  protected boolean failOnMissingNode = true;
052
053  /**
054   * Constructor NestedGetFunction creates a new NestedGetFunction instance.
055   *
056   * @param nestedCoercibleType of NestedCoercibleType
057   * @param fieldDeclaration    of Fields
058   * @param failOnMissingNode   of boolean
059   * @param stringPointers      of String...
060   */
061  public NestedGetFunction( NestedCoercibleType<Node, Result> nestedCoercibleType, Fields fieldDeclaration, boolean failOnMissingNode, String... stringPointers )
062    {
063    super( nestedCoercibleType, fieldDeclaration );
064    this.failOnMissingNode = failOnMissingNode;
065
066    if( fieldDeclaration.size() != stringPointers.length )
067      throw new IllegalArgumentException( "pointers not same length as declared fields" );
068
069    NestedPointerCompiler compiler = getNestedPointerCompiler();
070
071    this.pointers = new BaseNestedPointer[ stringPointers.length ];
072
073    for( int i = 0; i < stringPointers.length; i++ )
074      this.pointers[ i ] = compiler.nested( stringPointers[ i ] );
075    }
076
077  @Override
078  public void prepare( FlowProcess flowProcess, OperationCall<Tuple> operationCall )
079    {
080    operationCall.setContext( Tuple.size( pointers.length ) );
081    }
082
083  @Override
084  public void operate( FlowProcess flowProcess, FunctionCall<Tuple> functionCall )
085    {
086    Node node = (Node) functionCall.getArguments().getObject( 0, getCoercibleType() );
087
088    for( int i = 0; i < pointers.length; i++ )
089      {
090      Node result = pointers[ i ].at( node );
091
092      if( failOnMissingNode && result == null )
093        throw new OperationException( "node missing from json node tree: " + pointers[ i ] );
094
095      Type declaredType = getFieldDeclaration().getType( i );
096      Object value = getCoercibleType().coerce( result, declaredType );
097
098      functionCall.getContext().set( i, value );
099      }
100
101    functionCall.getOutputCollector().add( functionCall.getContext() );
102    }
103
104  protected static String[] asArray( Collection<String> values )
105    {
106    return values.toArray( new String[ values.size() ] );
107    }
108
109  protected static Fields asFields( Set<Fields> fields )
110    {
111    return fields.stream().reduce( Fields.NONE, Fields::append );
112    }
113  }