org.ballerinalang.langserver.command.testgen.ValueSpaceGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.ballerinalang.langserver.command.testgen.ValueSpaceGenerator.java

Source

/*
 * Copyright (c) 2018, WSO2 Inc. (http://wso2.com) All Rights Reserved.
 *
 *  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 org.ballerinalang.langserver.command.testgen;

import org.apache.commons.lang3.RandomStringUtils;
import org.ballerinalang.langserver.command.testgen.TestGenerator.TestFunctionGenerator;
import org.ballerinalang.langserver.common.utils.CommonUtil;
import org.ballerinalang.model.elements.PackageID;
import org.wso2.ballerinalang.compiler.semantics.model.symbols.BObjectTypeSymbol;
import org.wso2.ballerinalang.compiler.semantics.model.symbols.BTypeSymbol;
import org.wso2.ballerinalang.compiler.semantics.model.symbols.BVarSymbol;
import org.wso2.ballerinalang.compiler.semantics.model.types.BArrayType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BField;
import org.wso2.ballerinalang.compiler.semantics.model.types.BFiniteType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BInvokableType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BMapType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BObjectType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BRecordType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BTupleType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BType;
import org.wso2.ballerinalang.compiler.semantics.model.types.BUnionType;
import org.wso2.ballerinalang.compiler.tree.BLangNode;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangExpression;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangLiteral;
import org.wso2.ballerinalang.compiler.tree.statements.BLangAssignment;
import org.wso2.ballerinalang.compiler.tree.statements.BLangTupleDestructure;
import org.wso2.ballerinalang.compiler.tree.types.BLangFunctionTypeNode;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * This class is responsible for generating value spaces for a given length.
 *
 * @since 0.985.0
 */
public class ValueSpaceGenerator {
    public static final String PLACE_HOLDER = "{%1}";
    public static final int RANDOM_WORDS_LENGTH = 3;

    /**
     * Returns a value space for a given BLangNode.
     *
     * @param importsAcceptor imports acceptor
     * @param currentPkgId    current package id
     * @param bLangNode       BLangNode to evaluate
     * @param template        templates to be modified
     * @return {@link String}  modified templates
     * @see #createTemplateArray(int)
     */
    public static String[] getValueSpaceByNode(BiConsumer<String, String> importsAcceptor, PackageID currentPkgId,
            BLangNode bLangNode, String[] template) {
        if (bLangNode.type == null && bLangNode instanceof BLangTupleDestructure) {
            // Check for tuple assignment eg. (int, int)
            List<BLangExpression> varRefs = ((BLangTupleDestructure) bLangNode).getVariableRefs();
            String[][] list = new String[varRefs.size()][template.length];
            IntStream.range(0, varRefs.size()).forEach(j -> {
                BLangExpression bLangExpression = varRefs.get(j);
                if (bLangExpression.type != null) {
                    String[] values = getValueSpaceByType(importsAcceptor, currentPkgId, bLangExpression.type,
                            createTemplateArray(template.length));
                    IntStream.range(0, values.length).forEach(i -> list[i][j] = values[i]);
                }
            });
            IntStream.range(0, template.length).forEach(index -> {
                template[index] = template[index].replace(PLACE_HOLDER, "(" + String.join(", ", list[index]) + ")");
            });
            return template;
        } else if (bLangNode instanceof BLangLiteral) {
            return (String[]) Stream.of(template)
                    .map(s -> s.replace(PLACE_HOLDER, ((BLangLiteral) bLangNode).getValue().toString())).toArray();
        } else if (bLangNode instanceof BLangAssignment) {
            return (String[]) Stream.of(template).map(s -> s.replace(PLACE_HOLDER, "0")).toArray();
        } else if (bLangNode instanceof BLangFunctionTypeNode) {
            BLangFunctionTypeNode funcType = (BLangFunctionTypeNode) bLangNode;
            TestFunctionGenerator generator = new TestFunctionGenerator(importsAcceptor, currentPkgId, funcType);
            StringJoiner params = new StringJoiner(", ");
            String[][] valueSpace = generator.getValueSpace();
            String[] typeSpace = generator.getTypeSpace();
            String[] nameSpace = generator.getNamesSpace();
            IntStream.range(0, typeSpace.length - 1).forEach(index -> {
                String type = typeSpace[index];
                String name = nameSpace[index];
                params.add(type + " " + name);
            });
            String returnType = "(" + typeSpace[typeSpace.length - 1] + ")";
            IntStream.range(0, template.length).forEach(index -> {
                String builder = "function (" + params.toString() + ") returns " + returnType + "{ "
                        + ((valueSpace != null) ? "return " + valueSpace[index][funcType.params.size()] : "")
                        + "; }";
                template[index] = template[index].replace(PLACE_HOLDER, builder);
            });
            return template;
        }
        return (bLangNode.type != null)
                ? getValueSpaceByType(importsAcceptor, currentPkgId, bLangNode.type, template)
                : template;
    }

    /**
     * Returns value space for a given BType.
     *
     * @param importsAcceptor imports acceptor
     * @param currentPkgId    current package id
     * @param bType           BType to evaluate
     * @param template        templates to be modified
     * @return {@link String}  modified templates
     */
    public static String[] getValueSpaceByType(BiConsumer<String, String> importsAcceptor, PackageID currentPkgId,
            BType bType, String[] template) {
        if ((bType.tsymbol == null || bType.tsymbol.name.value.isEmpty()) && bType instanceof BArrayType) {
            BArrayType bArrayType = (BArrayType) bType;
            String[] values = getValueSpaceByTypeSymbol(bArrayType.eType.tsymbol,
                    createTemplateArray(template.length));
            IntStream.range(0, template.length).forEach(
                    index -> template[index] = template[index].replace(PLACE_HOLDER, "[" + values[index] + "]"));
            return template;
        } else if (bType instanceof BFiniteType) {
            // Check for finite set assignment
            BFiniteType bFiniteType = (BFiniteType) bType;
            Set<BLangExpression> valueSpace = bFiniteType.valueSpace;
            if (!valueSpace.isEmpty()) {
                return getValueSpaceByNode(importsAcceptor, currentPkgId, valueSpace.stream().findFirst().get(),
                        template);
            }
        } else if (bType instanceof BMapType && ((BMapType) bType).constraint != null) {
            // Check for constrained map assignment eg. map<Student>
            BType constraintType = ((BMapType) bType).constraint;
            String[] values = getValueSpaceByType(importsAcceptor, currentPkgId, constraintType,
                    createTemplateArray(template.length));
            IntStream.range(0, template.length).forEach(index -> {
                template[index] = template[index].replace(PLACE_HOLDER, "{key: " + values[index] + "}");
            });
            return template;
        } else if (bType instanceof BUnionType) {
            // Check for union assignment int|string
            BUnionType bUnionType = (BUnionType) bType;
            Set<BType> memberTypes = bUnionType.getMemberTypes();
            if (!memberTypes.isEmpty()) {
                return getValueSpaceByType(importsAcceptor, currentPkgId, memberTypes.stream().findFirst().get(),
                        template);
            }
        } else if (bType instanceof BTupleType) {
            // Check for tuple assignment (int, string)
            BTupleType bTupleType = (BTupleType) bType;
            List<BType> tupleTypes = bTupleType.tupleTypes;
            String[][] vSpace = new String[template.length][tupleTypes.size()];
            IntStream.range(0, tupleTypes.size()).forEach(j -> {
                BType type = tupleTypes.get(j);
                String[] values = getValueSpaceByType(importsAcceptor, currentPkgId, type,
                        createTemplateArray(template.length));
                IntStream.range(0, values.length).forEach(i -> vSpace[i][j] = values[i]);
            });
            IntStream.range(0, template.length).forEach(index -> {
                template[index] = template[index].replace(PLACE_HOLDER,
                        "(" + String.join(", ", vSpace[index]) + ")");
            });
            return template;
        } else if (bType instanceof BRecordType) {
            BRecordType bRecordType = (BRecordType) bType;
            List<BField> params = bRecordType.fields;
            String[][] list = new String[template.length][params.size()];
            IntStream.range(0, params.size()).forEach(paramIndex -> {
                BField field = params.get(paramIndex);
                String[] values = getValueSpaceByType(importsAcceptor, currentPkgId, field.type,
                        createTemplateArray(template.length));
                IntStream.range(0, values.length).forEach(valIndex -> {
                    list[valIndex][paramIndex] = field.name + ": " + values[valIndex];
                });
            });

            IntStream.range(0, template.length).forEach(index -> {
                String paramsStr = String.join(", ", list[index]);
                String newObjStr = "{" + paramsStr + "}";
                template[index] = template[index].replace(PLACE_HOLDER, newObjStr);
            });
            return template;
        } else if (bType instanceof BObjectType && ((BObjectType) bType).tsymbol instanceof BObjectTypeSymbol) {
            BObjectTypeSymbol bStructSymbol = (BObjectTypeSymbol) ((BObjectType) bType).tsymbol;
            List<BVarSymbol> params = bStructSymbol.initializerFunc.symbol.params;
            String[][] list = new String[template.length][params.size()];
            IntStream.range(0, params.size()).forEach(paramIndex -> {
                BVarSymbol param = params.get(paramIndex);
                String[] values = getValueSpaceByType(importsAcceptor, currentPkgId, param.type,
                        createTemplateArray(template.length));
                IntStream.range(0, values.length).forEach(valIndex -> {
                    list[valIndex][paramIndex] = values[valIndex];
                });
            });

            IntStream.range(0, template.length).forEach(index -> {
                String pkgPrefix = CommonUtil.getPackagePrefix(importsAcceptor, currentPkgId, bStructSymbol.pkgID);
                String paramsStr = String.join(", ", list[index]);
                String newObjStr = "new " + pkgPrefix + bStructSymbol.name.getValue() + "(" + paramsStr + ")";
                template[index] = template[index].replace(PLACE_HOLDER, newObjStr);
            });
            return template;
        } else if (bType instanceof BInvokableType) {
            BInvokableType invokableType = (BInvokableType) bType;
            TestFunctionGenerator generator = new TestFunctionGenerator(importsAcceptor, currentPkgId,
                    invokableType);
            StringJoiner params = new StringJoiner(", ");
            String[][] valueSpace = generator.getValueSpace();
            String[] typeSpace = generator.getTypeSpace();
            String[] nameSpace = generator.getNamesSpace();
            IntStream.range(0, typeSpace.length - 1).forEach(index -> {
                String type = typeSpace[index];
                String name = nameSpace[index];
                params.add(type + " " + name);
            });
            String returnType = "(" + typeSpace[typeSpace.length - 1] + ")";
            IntStream.range(0, template.length).forEach(index -> {
                String functionStr = "function (" + params.toString() + ") returns " + returnType + "{ "
                        + ((valueSpace != null) ? "return " + valueSpace[index][invokableType.paramTypes.size()]
                                : "")
                        + "; }";
                template[index] = template[index].replace(PLACE_HOLDER, functionStr);
            });
            return template;
        }
        return (bType.tsymbol != null) ? getValueSpaceByTypeSymbol(bType.tsymbol, template)
                : (String[]) Stream.of(template).map(s -> s.replace(PLACE_HOLDER, "()")).toArray();
    }

    /**
     * Returns value space for a given BTypeSymbol.
     *
     * @param tSymbol  BTypeSymbol to evaluate
     * @param template templates to be modified
     * @return {@link String}  modified templates
     */
    public static String[] getValueSpaceByTypeSymbol(BTypeSymbol tSymbol, String[] template) {
        if (tSymbol == null) {
            String[] result = new String[] { "()" };
            populateValueSpace(result, template, () -> "()");
            return template;
        }
        Random random = new Random();
        switch (tSymbol.name.getValue()) {
        case "int":
        case "any":
            String[] resultInt = new String[] { "-1", "0", "1" };
            populateValueSpace(resultInt, template, () -> String.valueOf(random.nextInt((template.length))));
            break;
        case "string":
            String[] resultStr = new String[] { "\"\"", "\"foo\"", "\"bar\"" };
            populateValueSpace(resultStr, template,
                    () -> "\"" + RandomStringUtils.randomAlphabetic(RANDOM_WORDS_LENGTH) + "\"");
            break;
        case "float":
            String[] resultFloat = new String[] { "-1.0", "0.0", "1.0" };
            populateValueSpace(resultFloat, template, () -> String.format("%.1f", random.nextDouble()));
            break;
        case "json":
            String[] resultJson = new String[] { "{\"key\": \"value\"}", "{}", "{\"foo\": \"bar\"}" };
            populateValueSpace(resultJson, template, () -> {
                String key = RandomStringUtils.randomAlphabetic(RANDOM_WORDS_LENGTH);
                String value = RandomStringUtils.randomAlphabetic(RANDOM_WORDS_LENGTH);
                return "{\"" + key + "\": \"" + value + "\"}";
            });
            break;
        case "boolean":
            String[] resultBool = new String[] { "false", "true" };
            populateValueSpace(resultBool, template, () -> String.valueOf(random.nextBoolean()));
            break;
        case "xml":
            String[] resultXml = new String[] { "xml `<msg>hello</msg>`", "xml `<foo>bar</foo>`",
                    "xml `<bar>foo</bar>`" };
            populateValueSpace(resultXml, template, () -> {
                String key = RandomStringUtils.randomAlphabetic(RANDOM_WORDS_LENGTH);
                String value = RandomStringUtils.randomAlphabetic(RANDOM_WORDS_LENGTH);
                return "xml `<" + key + ">" + value + "</" + key + ">`";
            });
            break;
        case "byte":
            String[] resultBlob = new String[] { "0" };
            populateValueSpace(resultBlob, template, () -> String.valueOf("0"));
            break;
        default:
            String[] result = new String[] { "()" };
            populateValueSpace(result, template, () -> "()");
            break;
        }
        return template;
    }

    /**
     * Populates defined space into the templates. Random value generator will be used for the spare values.
     *
     * @param definedSpace defined value space
     * @param template     templates set
     * @param random       random value generator
     */
    public static void populateValueSpace(String[] definedSpace, String[] template, Supplier<String> random) {
        // Populate defined values
        int min = Math.min(definedSpace.length, template.length);
        IntStream.range(0, min).forEach(i -> template[i] = template[i].replace(PLACE_HOLDER, definedSpace[i]));
        // Populate random values
        int max = Math.max(min, template.length);
        IntStream.range(0, max)
                .forEach(index -> template[index] = template[index].replace(PLACE_HOLDER, random.get()));
    }

    /**
     * Returns a set of templates pre-populated with placeholders.
     *
     * @param length length of the templates set
     * @return pre-populated templates
     */
    public static String[] createTemplateArray(int length) {
        String[] templates = new String[length];
        Arrays.fill(templates, PLACE_HOLDER);
        return templates;
    }
}