ModifierLeafSpec.java

/*
 * Copyright 2013 Bazaarvoice, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.bazaarvoice.jolt.modifier.spec;

import com.bazaarvoice.jolt.common.Optional;
import com.bazaarvoice.jolt.common.SpecStringParser;
import com.bazaarvoice.jolt.common.tree.MatchedElement;
import com.bazaarvoice.jolt.common.tree.WalkedPath;
import com.bazaarvoice.jolt.modifier.OpMode;
import com.bazaarvoice.jolt.modifier.TemplatrSpecBuilder;
import com.bazaarvoice.jolt.modifier.function.Function;
import com.bazaarvoice.jolt.modifier.function.FunctionArg;
import com.bazaarvoice.jolt.modifier.function.FunctionEvaluator;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@SuppressWarnings( "deprecated" )
public class ModifierLeafSpec extends ModifierSpec {

    private final List<FunctionEvaluator> functionEvaluatorList;

    @SuppressWarnings( "unchecked" )
    public ModifierLeafSpec( final String rawJsonKey, Object rhsObj, final OpMode opMode, final Map<String, Function> functionsMap ) {
        super(rawJsonKey, opMode);
        functionEvaluatorList = new LinkedList<>(  );

        FunctionEvaluator functionEvaluator;

        // "key": "expression1"
        if ( (rhsObj instanceof String) ) {
            functionEvaluator = buildFunctionEvaluator( (String) rhsObj, functionsMap );
            functionEvaluatorList.add( functionEvaluator );
        }
        // "key": ["expression1", "expression2", "expression3"]
        else if(rhsObj instanceof List && ((List)rhsObj).size() > 0) {
            List rhsList = (List) rhsObj;
            for(Object rhs: rhsList) {
                if(rhs instanceof String) {
                    functionEvaluator = buildFunctionEvaluator( rhs.toString(), functionsMap );
                    functionEvaluatorList.add( functionEvaluator );
                }
                else {
                    functionEvaluator = FunctionEvaluator.forArgEvaluation( FunctionArg.forLiteral( rhs, false ) );
                    functionEvaluatorList.add( functionEvaluator );
                }
            }
        }
        // "key": anyObjectOrLiteral --- just set as-is
        else {
            functionEvaluator = FunctionEvaluator.forArgEvaluation( FunctionArg.forLiteral( rhsObj, false ) );
            functionEvaluatorList.add( functionEvaluator );
        }
    }

    @Override
    public void applyElement( final String inputKey, final Optional<Object> inputOptional, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map<String, Object> context ) {

        Object parent = walkedPath.lastElement().getTreeRef();

        walkedPath.add( inputOptional.get(), thisLevel );

        Optional<Object> valueOptional = getFirstAvailable( functionEvaluatorList, inputOptional, walkedPath, context );

        if(valueOptional.isPresent()) {
            setData( parent, thisLevel, valueOptional.get(), opMode );
        }

        walkedPath.removeLast();
    }

    private static FunctionEvaluator buildFunctionEvaluator( final String rhs, final Map<String, Function> functionsMap ) {
        final FunctionEvaluator functionEvaluator;
        // "key": "@0" --- evaluate expression then set
        if(!rhs.startsWith( TemplatrSpecBuilder.FUNCTION )) {
            return FunctionEvaluator.forArgEvaluation( constructSingleArg( rhs, false ) );
        }
        else {
            String functionName;
            // "key": "=abs" --- call function with current value then set output if present
            if ( !rhs.contains( "(" ) && !rhs.endsWith( ")" ) ) {
                functionName = rhs.substring( TemplatrSpecBuilder.FUNCTION.length() );
                return FunctionEvaluator.forFunctionEvaluation( functionsMap.get( functionName ) );
            }
            // "key": "=abs(@(1,&0))" --- evaluate expression then call function with
            //                            expression-output, then set output if present
            else {
                String fnString = rhs.substring( TemplatrSpecBuilder.FUNCTION.length() );
                List<String> fnArgs = SpecStringParser.parseFunctionArgs( fnString );
                functionName = fnArgs.remove( 0 );
                functionEvaluator = FunctionEvaluator.forFunctionEvaluation( functionsMap.get( functionName ), constructArgs( fnArgs ) );
            }
        }
        return functionEvaluator;
    }

    private static Optional<Object> getFirstAvailable(List<FunctionEvaluator> functionEvaluatorList, Optional<Object> inputOptional, WalkedPath walkedPath, Map<String, Object> context) {
        Optional<Object> valueOptional = Optional.empty();
        for(FunctionEvaluator functionEvaluator: functionEvaluatorList) {
            try {
                valueOptional = functionEvaluator.evaluate( inputOptional, walkedPath, context );
                if(valueOptional.isPresent()) {
                    return valueOptional;
                }
            }
            catch(Exception ignored) {}
        }
        return valueOptional;
    }

    private static FunctionArg[] constructArgs( List<String> argsList ) {
        FunctionArg[] argsArray = new FunctionArg[argsList.size()];
        for(int i=0; i<argsList.size(); i++) {
            String arg = argsList.get( i );
            argsArray[i] = constructSingleArg( arg, true );
        }
        return argsArray;
    }

    private static FunctionArg constructSingleArg( String arg, boolean forFunction ) {
        if(arg.startsWith( TemplatrSpecBuilder.CARET )) {
            return FunctionArg.forContext( TRAVERSAL_BUILDER.build( arg.substring( 1 ) ) );
        }
        else if(arg.startsWith( TemplatrSpecBuilder.AT )) {
            return FunctionArg.forSelf( TRAVERSAL_BUILDER.build( arg ) );
        }
        else {
            return FunctionArg.forLiteral( arg, forFunction );
        }
    }
}