All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.exactpro.sf.aml.iomatrix.JSONMatrixReader Maven / Gradle / Ivy

There is a newer version: 3.4.260
Show newest version
/*******************************************************************************
 * Copyright 2009-2019 Exactpro (Exactpro Systems Limited)
 *
 * 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.exactpro.sf.aml.iomatrix;

import com.exactpro.sf.aml.AMLBlockBrace;
import com.exactpro.sf.aml.generator.matrix.Column;
import com.exactpro.sf.common.util.EPSCommonException;
import com.exactpro.sf.common.util.Pair;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Table;
import com.google.common.reflect.Reflection;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.NoSuchElementException;
import java.util.HashMap;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;


public class JSONMatrixReader implements IMatrixReader {

    private static final Supplier SUPPLIER = () -> {
        throw new NoSuchElementException();
    };
    private final Logger logger = LoggerFactory.getLogger(this.getClass().getName() + "@" + Integer.toHexString(hashCode()));

    private final DEBUG dbg = new DEBUG();

    private Iterator rowIterator;
    private Supplier rowsSupplier;

    private final AtomicInteger tableRowCounter = new AtomicInteger(1);


    public JSONMatrixReader(File file, MatrixFileTypes type ) throws IOException {

        Pair, Supplier> parseResult = parseToTable(JSONMatrixParser.readValue(file, type));
        rowIterator = parseResult.getFirst();
        rowsSupplier = parseResult.getSecond();
    }

    private Pair, Supplier> parseToTable(CustomValue jsonNode) throws IOException {

        Table table = Reflection.newProxy(Table.class, new SafetyCheckInvocationHandler());

        for(CustomValue testCaseWrapper:jsonNode.getArrayValue()) {
            if (testCaseWrapper.getObjectValue().size() > 1) {
                throw new EPSCommonException("Too many nodes in testCase wrapper");
            }

            testCaseWrapper.getObjectValue().forEach((blockKey, commonBlock) -> {
                String commonBlockType = blockKey.getKey();
                AMLBlockBrace blockBrace = AMLBlockBrace.value(commonBlockType);

                Objects.requireNonNull(commonBlock, "'AML block' node must be presented");
                Objects.requireNonNull(blockBrace, "Unknown block type " + commonBlockType);

                int localRowCounter = tableRowCounter.getAndIncrement();
                table.put(localRowCounter, Column.Action.getName(), new SimpleCell(commonBlockType,blockKey.getLine()));

                commonBlock.getObjectValue().forEach((actionKey, actionNode) -> {
                    String reference = actionKey.getKey();

                    logger.debug("reading {}", reference);

                    if (actionNode.isObject()) {

                        int nestedCount = countNestedReferences(actionNode);
                        int target = tableRowCounter.get() + nestedCount;
                        table.put(target, Column.Reference.getName(), new SimpleCell(reference, actionKey.getLine()));
                        consumeNode(actionNode, table, target,  actionKey.getLine());
                        //FIXME will add additional empty row at last action
                        tableRowCounter.getAndIncrement();
                    } else if (actionNode.isSimple()) {
                        table.put(localRowCounter, reference,  new SimpleCell(actionNode.getSimpleValue().toString(), actionKey.getLine()));
                    } else if (actionNode.isArray()) {
                        throw new IllegalStateException(String.format("Invalid value type array %s found in block %s, number line %s", reference, commonBlockType, actionKey.getLine()));
                    } else {
                        logger.debug("{} - actionNode is NullValue. Line - {}, column - {}",
                                reference, actionNode.getLine(), actionNode.getColumn());
                    }
                });

                table.put(tableRowCounter.getAndIncrement(), Column.Action.getName(), new SimpleCell(blockBrace.getInversed().getName(),blockKey.getLine()));
            });
        }

        Set columns = ImmutableSet.builder().addAll(table.columnKeySet()).add(Column.Id.getName()).build();
        columns.forEach(column -> table.put(0, column, new SimpleCell(column)));

        Iterator rowIterator = dbg.DEBUG_SORT ? table.rowKeySet().iterator() : new TreeSet<>(table.rowKeySet()).iterator();

        Supplier supplier = SUPPLIER;
        //preserve header
        if (rowIterator.hasNext()) {

            supplier = () -> {
                int currentRow = rowIterator.next();
                Map row = new HashMap<>(table.row(currentRow));

                return columns.stream()
                        .map(key -> row.getOrDefault(key, new SimpleCell("")))
                        .toArray(SimpleCell[]::new);
            };
        }

        return new Pair<>(rowIterator, supplier);
    }

    @Override
    public SimpleCell[] readCells() throws IOException {
        return rowsSupplier.get();
    }

    @Override
    public String[] read() throws IOException {
        return Stream.of(rowsSupplier.get())
                .map(SimpleCell::getValue)
                .toArray(String[]::new);
    }

    @Override
    public boolean hasNext() {
        return rowIterator.hasNext();
    }

    @Override
    public void close() throws Exception {
        //TODO
    }

    private  Stream wrapIter(Iterator src) {
        Spliterator spliterator = Spliterators.spliteratorUnknownSize(src, Spliterator.SIZED);
        return StreamSupport.stream(spliterator, false);
    }

    /***
     * consumes json node desribing action or reference
     * @param node - jsom node
     * @param toTable - table to write rows
     * @param target - position of node (current pos + deps count)
     * @return generated or specified ref of action/explicit ref
     */
    private String consumeNode(CustomValue node, Table toTable, int target, int nodeNumberLine) {

        node.getObjectValue().forEach((actionKey, actionFieldNode) -> {
            String actionFieldKey = actionKey.getKey();

            Consumer actionFieldPut = value -> toTable.put(target, actionFieldKey, value);

            logger.debug("{} consuming with {}", dbg.openTabs(),  actionFieldKey);

            Function processObj = jsonNode -> {
                int nestedLvls = countNestedReferences(jsonNode);
                String ref = consumeNode(jsonNode, toTable, tableRowCounter.get() + nestedLvls, actionKey.getLine());
                tableRowCounter.incrementAndGet();
                return ref;
            };

            if (actionFieldNode.isObject()) {
                actionFieldPut.accept(new SimpleCell("[" + processObj.apply(actionFieldNode) + "]", actionKey.getLine()));
            } else if (actionFieldNode.isArray()) {
                String ref = wrapIter(actionFieldNode.getArrayValue().iterator())
                        .filter(elNode -> elNode.isObject() || elNode.isSimple())
                        //FIXME need unwrap ref syntax? or write only ref name in json
                        .map(elNode -> elNode.isObject() ? processObj.apply(elNode) : elNode.getSimpleValue().toString())
                        .collect(Collectors.joining(","));

                actionFieldPut.accept(new SimpleCell("[" + ref + "]", actionKey.getLine()));
            } else if (actionFieldNode.isSimple()) {
                actionFieldPut.accept(new SimpleCell(actionFieldNode.getSimpleValue().toString(), actionKey.getLine()));
            }

            logger.debug("{} consuming with {} done -> {}", dbg.closeTabs(), actionFieldKey, toTable.get(target, Column.Reference.getName()));
        });

        if (!toTable.contains(target, Column.Reference.getName())) {
            toTable.put(target, Column.Reference.getName(), new SimpleCell("implicit_ref" + target, nodeNumberLine));
        }

        return toTable.get(target, Column.Reference.getName()).getValue();
    }

    /***
     * Compute nested inline object nodes for reserve place for it
      * @param node - json node
     * @return count of rows to reserve
     */
    private int countNestedReferences(CustomValue node) {

        int counter = 0;

        if(node.getObjectValue()!=null) {
            for (Map.Entry el : node.getObjectValue().entrySet()) {
                if (el.getValue().isObject()) {
                    counter += 1 + countNestedReferences(el.getValue());
                } else if (el.getValue().isArray()) {
                    counter += wrapIter(el.getValue().getArrayValue().iterator())
                            .mapToInt(n -> !n.isSimple() ? 1 + countNestedReferences(n) : 0)
                            .sum();
                }
            }
        }
        return counter;
    }

    /***
     * Ivocation Handler for checking that rows content no mixes while generating inplace rows/arrays
     */
    private class SafetyCheckInvocationHandler implements InvocationHandler {

        private final Table table = HashBasedTable.create();

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            Object result = method.invoke(table, args);

            if ("put".equals(method.getName())) {

                if (logger.isDebugEnabled()) {
                    logger.debug("{}{} -> write ['{}':'{}']", dbg.currentTabs(), args[0], args[1], args[2]);
                }

                SimpleCell old = (SimpleCell) result;

                if (old != null && old.getValue() != null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("\uD83D\uDCA9{}{} overrides value {} at {} column with {} position", dbg.currentTabs(), args[2], old, args[1], args[0]);
                    }
                    throw new EPSCommonException(String.format("%s overrides value %s at %s column with %s position", args[2], old, args[1], args[0]));
                }

                return old;
            }

            return result;
        }
    }

    private static final class DEBUG {

        static final boolean DEBUG_SORT = false;
        private final AtomicInteger debug_tabs = new AtomicInteger();

        private String openTabs() {
            return StringUtils.repeat("  ", debug_tabs.getAndIncrement());
        }
        private String currentTabs() {
            return StringUtils.repeat("  ",  debug_tabs.get());
        }
        private String closeTabs() {
            return StringUtils.repeat("  ", debug_tabs.decrementAndGet());
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy