no.ssb.vtl.script.expressions.FunctionExpression.java Source code

Java tutorial

Introduction

Here is the source code for no.ssb.vtl.script.expressions.FunctionExpression.java

Source

package no.ssb.vtl.script.expressions;

/*
 * ========================LICENSE_START=================================
 * Java VTL
 * %%
 * Copyright (C) 2016 - 2017 Hadrien Kohl
 * %%
 * 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.
 * =========================LICENSE_END==================================
 */

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import no.ssb.vtl.model.VTLExpression;
import no.ssb.vtl.model.VTLFunction;
import no.ssb.vtl.model.VTLObject;

import javax.script.Bindings;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * Helper class that transforms a VTLFunction to a VTLExpression.
 *
 * It takes care of resolving all the function parameters in
 * its {@link #resolve(Bindings)} method.
 */
public class FunctionExpression<T extends VTLObject> implements VTLExpression {

    private static final String INVALID_TYPE = "invalid argument type for %s, expected %s but got %s";
    private static final String TOO_MANY_ARGUMENTS = "too many arguments, expected %s but got %s";
    private static final String UNKNOWN_ARGUMENTS = "unknown argument(s): %s";
    private static final String DUPLICATE_ARGUMENTS = "duplicate argument(s): %s";
    private static final String MISSING_ARGUMENTS = "missing argument(s): %s";

    private final VTLFunction<T> wrappedFunction;
    private final List<VTLExpression> arguments;
    private final Map<String, VTLExpression> namedArguments;

    public FunctionExpression(VTLFunction<T> wrappedFunction, List<VTLExpression> arguments,
            Map<String, VTLExpression> namedArguments) {
        this.wrappedFunction = wrappedFunction;
        this.arguments = arguments;
        this.namedArguments = namedArguments;
        checkTypes(wrappedFunction, mergeArguments(wrappedFunction.getSignature(), arguments, namedArguments));
    }

    public FunctionExpression(VTLFunction<T> wrappedFunction, List<VTLExpression> arguments) {
        this(wrappedFunction, arguments, Collections.emptyMap());
    }

    public FunctionExpression(VTLFunction<T> wrappedFunction, Map<String, VTLExpression> namedArguments) {
        this(wrappedFunction, Collections.emptyList(), namedArguments);
    }

    public FunctionExpression(VTLFunction<T> wrappedFunction, VTLExpression... arguments) {
        this(wrappedFunction, Arrays.asList(arguments));
    }

    // TODO: Move to VTLFunction or AbstractVTLFunction.
    private void checkTypes(VTLFunction<?> function, Map<String, VTLExpression> arguments) {
        VTLFunction.Signature signature = function.getSignature();
        for (String argumentName : arguments.keySet()) {
            Class<?> expectedType = signature.get(argumentName).getVTLType();
            Class<?> argumentType = arguments.get(argumentName).getVTLType();

            // Null values are always the correct type.
            if (argumentType.equals(VTLObject.class))
                continue;

            checkArgument(expectedType.isAssignableFrom(argumentType), INVALID_TYPE, argumentName, expectedType,
                    argumentType);
        }
    }

    // TODO: Move to VTLFunction or AbstractVTLFunction.
    @VisibleForTesting
    static Map<String, VTLExpression> mergeArguments(VTLFunction.Signature signature, List<VTLExpression> arguments,
            Map<String, VTLExpression> namedArguments) {

        // Check unnamed arguments count.
        checkArgument(arguments.size() <= signature.size(), TOO_MANY_ARGUMENTS, signature.size(), arguments.size());

        ImmutableMap.Builder<String, VTLExpression> builder = ImmutableMap.builder();

        // Match the list with the signature names.
        Iterator<String> namesIterator = signature.keySet().iterator();
        for (VTLExpression argument : arguments) {
            builder.put(namesIterator.next(), argument);
        }

        // Check for duplicates
        Set<String> duplicates = Sets.intersection(namedArguments.keySet(), builder.build().keySet());
        checkArgument(duplicates.isEmpty(), DUPLICATE_ARGUMENTS, String.join(", ", duplicates));

        ImmutableMap<String, VTLExpression> computedArguments = builder.putAll(namedArguments).build();

        // Check for unknown arguments.
        Set<String> unknown = Sets.difference(computedArguments.keySet(), signature.keySet());
        checkArgument(unknown.isEmpty(), UNKNOWN_ARGUMENTS, String.join(", ", unknown));

        // Check for missing arguments
        Set<String> required = Maps.filterValues(signature, VTLFunction.Argument::isRequired).keySet();
        Set<String> missing = Sets.difference(required, computedArguments.keySet());
        checkArgument(missing.isEmpty(), MISSING_ARGUMENTS, String.join(", ", missing));

        return computedArguments;
    }

    @Override
    public VTLObject resolve(Bindings bindings) {
        // Resolve the parameters.
        List<VTLObject> resolvedParameters = Lists.newArrayList();
        for (VTLExpression expression : arguments) {
            resolvedParameters.add(expression.resolve(bindings));
        }
        Map<String, VTLObject> resolvedNamedParameters = Maps.newLinkedHashMap();
        for (Map.Entry<String, VTLExpression> entry : namedArguments.entrySet()) {
            resolvedNamedParameters.put(entry.getKey(), entry.getValue().resolve(bindings));
        }

        return wrappedFunction.invoke(resolvedParameters, resolvedNamedParameters);
    }

    @Override
    public Class getVTLType() {
        return wrappedFunction.getVTLType();
    }
}