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

nl.hsac.fitnesse.fixture.fit.MapColumnFixture Maven / Gradle / Ivy

There is a newer version: 5.3.17
Show newest version
package nl.hsac.fitnesse.fixture.fit;

import fit.Binding;
import fit.Fixture;
import fit.Parse;
import fit.exception.FitFailureException;
import nl.hsac.fitnesse.fixture.util.HttpResponse;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Base class for Fixtures that use a Map to store the set column values,
 * instead of fields.
 */
public class MapColumnFixture extends OurColumnFixture {
    private final Map currentRowValues = new HashMap();
    public static final String DEFAULT_ARRAY_SEPARATOR = ",";
    private static final String REGEX_STRING_WITH_SYMBOL = "(.*?)(\\$\\[)(.*?)(\\])(.*)"; // (Lazy) match randomText$[symbol]...
    private static final Pattern STRING_WITH_SYMBOL_PATTERN = Pattern.compile(REGEX_STRING_WITH_SYMBOL);

    @Override
    public void reset() {
        currentRowValues.clear();
        setDefaults(getCurrentRowValues());
    }

    /**
     * Adds new key/value pairs to currentRowValues based on table supplied. If value in first column is present,
     * and there is no value for key of second column, the value in second column is added, and vice versa.
     * The first row describes the key for each column's values.
     * @param translationTableWithHeader table describing equivalent key/value pairs.
     */
    public void translateFromTable(String[][] translationTableWithHeader) {
        translateFromTable(getCurrentRowValues(), translationTableWithHeader);
    }

    /**
     * Adds new key/value pairs to rowValues based on table supplied. If value in first column is present,
     * and there is no value for key of second column, the value in second column is added, and vice versa.
     * The first row describes the key for each column's values.
     * @param rowValues row to add values to.
     * @param translationTableWithHeader table describing equivalent key/value pairs.
     */
    public static void translateFromTable(Map rowValues, String[][] translationTableWithHeader) {
        translateFromTable(rowValues, translationTableWithHeader, 0, 1);
        translateFromTable(rowValues, translationTableWithHeader, 1, 0);
    }

    private static void translateFromTable(Map rowValues, String[][] translationTableWithHeader,
                                           int fromColumn, int toColumn) {
        String fromKey = translationTableWithHeader[0][fromColumn];
        String toKey = translationTableWithHeader[0][toColumn];
        if (rowValues.get(toKey) == null) {
            Object valuePresent = rowValues.get(fromKey);
            String toValue = findToValue(translationTableWithHeader, fromColumn, toColumn, valuePresent);
            if (toValue != null) {
                rowValues.put(toKey, toValue);
            }
        }
    }

    private static String findToValue(String[][] translationTableWithHeader, int fromColumn, int toColumn, Object fromValue) {
        String toValue = null;
        for (int i = 1; i < translationTableWithHeader.length; i++) { // row at i==0 contains headers
            String[] row = translationTableWithHeader[i];
            if (row[fromColumn].equals(fromValue)) {
                toValue = row[toColumn];
                break;
            }
        }
        return toValue;
    }

    /**
     * Replaces values found in the currentRowValues based on the supplied table.
     * Cell 0,1 defines the key to work with.
     * Other rows define a replacement value: if value in first column is found
     * it is replaced with the one from the second.
     * @param leftAcceptedForRight table describing replacements.
     */
    public void acceptAlternativeInputValuesFromTable(String[][] leftAcceptedForRight) {
        acceptAlternativeInputValuesFromTable(getCurrentRowValues(), leftAcceptedForRight);
    }

    /**
     * Replaces values found in the rowValues based on the supplied table.
     * Cell 0,1 defines the key to work with.
     * Other rows define a replacement value: if value in first column is found
     * it is replaced with the one from the second.
     * @param rowValues values to replace in.
     * @param leftAcceptedForRight table describing replacements.
     */
    public static void acceptAlternativeInputValuesFromTable(Map rowValues, String[][] leftAcceptedForRight) {
        String key = leftAcceptedForRight[0][1];
        Object valuePresent = rowValues.get(key);
        String translation = findToValue(leftAcceptedForRight, 0, 1, valuePresent);
        if (translation != null) {
            rowValues.put(key, translation);
        }
    }

    /**
     * Places default values (if any) before each row.
     *
     * @param values
     *            map to put defaults in.
     */
    protected void setDefaults(Map values) {
    }

    /**
     * @param columnName
     *            column to get current row's value of
     * @return value for specified column.
     */
    public Object get(String columnName) {
        return getCurrentRowValues().get(columnName);
    }

    /**
     * @param columnName
     *            column to get current row's value of
     * @return value for specified column.
     */
    @SuppressWarnings("unchecked")
    public Map getNested(String columnName) {
        Object value = get(columnName);
        if (value == null) {
            throw new IllegalArgumentException("No value for: " + columnName);
        }
        if (!(value instanceof Map)) {
            throw new IllegalArgumentException("Value for: " + columnName + " is not a Map, but a: " + value.getClass());
        }
        return (Map) value;
    }

    /**
     * @param columnName
     *            column to get current row's value of
     * @return value for specified column split by its spaces and optional whitespace.
     */
    protected List getList(String columnName) {
        Object value = get(columnName);
        if (value == null) {
            throw new IllegalArgumentException("No value for: " + columnName);
        }
        return Arrays.asList(value.toString().split("\\s*,\\s*"));
    }

    @Override
    protected Binding createBinding(int column, Parse heads) throws Throwable {
        Binding result;
        String header = heads.text();
        if (header.endsWith("=")) {
            result = new ParameterBinding(header.substring(0, header.length() - 1));
        } else if (header.endsWith("?")) {
            // use default Fixture behavior
            result = originalCreateBinding(column, heads);
            if (result instanceof Binding.SaveBinding) {
                // we want our own SaveBinding that makes anchors
                Binding saveBinding = new SaveBinding();
                saveBinding.adapter = result.adapter;
                result = saveBinding;
            }
        } else if (header.endsWith("?$")) { // compare return with value in symbol
            String originalBody = heads.body; // Save originalBody so we can show it in testresult page
            heads.body = header.substring(0, header.length()-1); // Remove the $ so originalCreateBinding will create a Binding.QueryBinding
            result = originalCreateBinding(column, heads); // use originalCreateBinding so we can set the adapter
            if (result instanceof Binding.QueryBinding) {
                // We want to compare value in symbol with return value and made our own QueryBinding to do so.
                Binding queryBinding = new QueryBinding();
                queryBinding.adapter = result.adapter; // I think adapter is necessary so it can see if values matches or not
                result = queryBinding;
            }
            heads.body = originalBody;

        } else {
            return new MapBinding(header);
        }
        return result;
    }

    protected final Binding originalCreateBinding(int column, Parse heads) throws Throwable {
        return super.createBinding(column, heads);
    }

    protected class SaveBinding extends Binding {
        @Override
        public void doCell(Fixture fixture, Parse cell) {
            try {
                executeIfNeeded();

                Object valueObj = getValue();
                String symbolValue = String.valueOf(valueObj);
                String symbolName = cell.text();

                if (valueObj instanceof Object[]) {
                    //  Store return value as array.
                    Fixture.setSymbol(symbolName, valueObj);
                    symbolValue = Arrays.toString((Object[]) valueObj);
                } else {
                    Fixture.setSymbol(symbolName, symbolValue);
                }
                cell.addToBody(Fixture.gray(" = " + symbolValue + ""));
            } catch (Exception e) {
                handleException(fixture, cell, e);
            }
        }

        /**
         * @return value to store.
         * @throws Exception
         *             if value could not be retrieved
         */
        protected Object getValue() throws Exception {
            return adapter.get(); // ...might be validly null
        }
    }

    public class ParameterBinding extends Binding {
        private final String header;

        public ParameterBinding(String headerName) {
            header = headerName;
        }

        @Override
        public void doCell(Fixture fixture, Parse cell) throws Throwable {
            String symbolName = cell.text();
            if (!"".equals(symbolName)) {
                String[] path = symbolName.split("\\.");
                String[] items = symbolName.split("\\s*,\\s*");
                if (items.length > 1 || !Fixture.hasSymbol(path[0])) {
                     String result = null;
                     for (String name : items) {
                         String[] nestedPath = name.split("\\.");
                         if (Fixture.hasSymbol(nestedPath[0])) {
                             String symbolValue = (String) Fixture.getSymbol(nestedPath[0]);
                             if (nestedPath.length > 1) {
                                 symbolValue = unmarshallParamValue(nestedPath, symbolValue).toString();
                             }
                             if (result == null) {
                                 result = symbolValue;
                                 cell.addToBody(" = ");
                             } else {
                                 result = result + DEFAULT_ARRAY_SEPARATOR + symbolValue;
                                cell.addToBody(", ");
                             }
                             cell.addToBody(paramHRef(nestedPath[0], symbolValue));
                         } else { //symbol not found
                             fixture.exception(cell, new FitFailureException("No such symbol: " + symbolName));
                         }
                     }
                     getCurrentRowValues().put(header, result);
                } else {
                    Object symbol = Fixture.getSymbol(path[0]);
                    String valueString = "";
                    Object value = null;
                    if (symbol instanceof String) {
                        valueString = (String) Fixture.getSymbol(path[0]);
                        if (!"null".equals(valueString)) {
                            value = unmarshallParamValue(path, valueString);
                        }
                    } else if (symbol instanceof Object[]) {
                        //  Stored value is array.
                        if (path.length > 1) {
                            int index = getIndexFromSymbolArray(path);
                            value = getSymbolArrayValue(symbol, index);
                            valueString = (String) value;
                        } else {
                            value = (symbol);
                            valueString = Arrays.toString((Object[]) value);
                        }
                    }
                    getCurrentRowValues().put(header, value);
                    cell.addToBody(" = " + paramHRef(symbolName, valueString));
                }
            }
        }

        private String paramHRef(String symbolName, String value) {
            return Fixture.gray("" + value + "");
        }

        private Object unmarshallParamValue(String[] paramNamePath, String valueString) {
            Object value;
            MapParameter mapValue = MapParameter.parse(valueString);
            if (mapValue != null) {
                if (paramNamePath.length == 1) {
                    value = mapValue;
                } else {
                    String nestedKey = paramNamePath[1];
                    value = mapValue.get(nestedKey);
                }
            } else {
                HttpResponse resp = HttpResponse.parse(valueString);
                if (resp != null) {
                    value = resp;
                } else {
                    value = valueString;
                }
            }
            return value;
        }
    }

    private class MapBinding extends Binding {
        private final String header;

        public MapBinding(String headerName) {
            header = headerName;
        }

        @Override
        public void doCell(Fixture aFixture, Parse aCell) throws Throwable {
            String text = aCell.text();
            if ("null".equals(text) || "".equals(text)) {
                text = null;
            } else if ("blank".equals(text)) {
                text = "";
            }
            currentRowValues.put(header, text);
        }
    }

    private class QueryBinding extends Binding.QueryBinding {

        /**
         * Special behavior: assumes the celltext is the name of a symbol and the return value of the column must be compared with the symbol value.
         * The special fitnesse values 'blank', 'null', '' are handled in the standard way though.
         */
        @Override
        public void doCell(Fixture aFixture, Parse aCell) {
            String originalCellText = aCell.text();
            String newText = null;
            String extraCellText =  "";
            try {
                if (!isSpecialBlankValueForFitnesse(originalCellText)) { // don't interfere with standard special behavior
                    // Assume the text is a symbol name of an array of those
                    if (originalCellText.startsWith("Array[") && originalCellText.endsWith("]")) {
                        // array of symbols and/of strings
                        newText = originalCellText.substring(6, originalCellText.length());
                        String separator = getArraySeperator();
                        // match string $variable1,variable2]    Note: ',' can be another array_seperator
                        Pattern pattern = Pattern.compile(String.format("\\$(.*?)(%s|])", separator));

                        Matcher matcher = pattern.matcher(newText);
                        while (matcher.find()) {
                            // replace all symbol entries by values
                            String symbolName = matcher.group();
                            int seperatorLength = matcher.group(2).length();
                            // replace the $variable name with value of getSymbolValue(variable_name)
                            newText = newText.replace(symbolName.substring(0, symbolName.length() - seperatorLength),
                                    getSymbolValue(symbolName.substring(1, symbolName.length() - seperatorLength)));
                        }
                        newText = newText.substring(0, newText.length() - 1);
                    } else {
                        newText = resolveStringWithSymbols(originalCellText);
                        if (newText == null){
                            // single element, we assume the text is a symbol
                            newText = getSymbolValue(originalCellText);
                        }
                    }
                    aCell.body = newText;
                    extraCellText = String.format(" (%s)", originalCellText);
                }
                super.doCell(aFixture, aCell);
                aCell.addToBody(extraCellText);

            } catch (NoSuchSymbolException e) {
                aCell.body = e.getMessage();
                aFixture.wrong(aCell);
            }
        }

    }

    /**
     * Functionality to compare returning cell value with a dynamic (with symbols) text.
     * See also HsacExamples.FitTests.ArraysAndSymbolsComparison#Compare result with string containing a symbol
     * @param originalCellText, ie randomText$[symbol1]MoreText where symbol1=ValueOfSymbol1
     * @return originalCellText with the symbols resolved to values, ie: randomTextValueOfSymbol1MoreText
     *      or null if the originalCellText doesn't contains text with symbols
     * @throws NoSuchSymbolException if the used symbol(s) don't exist
     */
    private String resolveStringWithSymbols(String originalCellText) throws NoSuchSymbolException {
        Matcher m = STRING_WITH_SYMBOL_PATTERN.matcher(originalCellText);
        String newText = null;
        while (m.matches()) {
            String symbolValue = getSymbolValue(m.group(3));
            newText = m.group(1) + symbolValue + m.group(5);
            m = STRING_WITH_SYMBOL_PATTERN.matcher(newText);
        }
        return newText;
    }

    private String getSymbolValue(String originalSymbolName) throws NoSuchSymbolException {
        String[] cellContent = null;
        String symbolName = null;
        String newText = null;
        if (originalSymbolName.contains(".")) {
            cellContent = StringUtils.split(originalSymbolName, '.');
            symbolName= cellContent[0];
        } else {
            symbolName = originalSymbolName;
        }
        Object symbolValue = Fixture.getSymbol(symbolName);
        if (symbolValue instanceof Object[]) {
            // cell text is element of array
            int arrayIndex = getIndexFromSymbolArray(cellContent);
            newText = (String) getSymbolArrayValue(symbolValue, arrayIndex);
        } else if (cellContent != null && cellContent.length == 2) {
            // cell text is nested object
            newText = (String) symbolValue;
            MapParameter mapValue = MapParameter.parse(newText);
            if (mapValue != null) {
                newText = mapValue.get(cellContent[1]).toString();
            }
        } else {
            newText = (String) (symbolValue);
        }
        if (newText == null) {
            throw new NoSuchSymbolException(String.format("No value for symbol '%s' found.", originalSymbolName));
        }
        return newText;
    }

    private void handleException(Fixture fixture, Parse cell, Exception e) {
        if (cell.text().isEmpty()) {
            cell.addToBody(gray("error"));
        } else {
            fixture.exception(cell, e);
        }
    }

    private class NoSuchSymbolException extends Exception {
        private static final long serialVersionUID = 1L;

        public NoSuchSymbolException(String message) {
            super(message);
        }
    }

    /**
     * Gets fixture parameter value by parameter key match.
     * Parameter should be defined in format =.
     * @param paramKey parameter key
     * @param defaultValue default parameter value
     * @return the fixture parameter value
     */
    private String getParameter(String paramKey, String defaultValue) {
        String paramValue = defaultValue;
        if (StringUtils.isNotBlank(paramKey) && args != null && args.length > 0) {
            for (String arg : args) {
                String[] parameter = arg.split("=");
                if (parameter != null && parameter.length == 2 && paramKey.equals(parameter[0])) {
                    paramValue = parameter[1];
                    break;
                }
            }
        }
        return paramValue;
    }

    /**
     * Fetch the array separator, which has a default value if not configured.
     * @return the array seperator
     */
    public String getArraySeperator() {
        return getParameter("ARRAY_SEPARATOR", DEFAULT_ARRAY_SEPARATOR);
    }

    /**
     * When cellContent contains a symbol that is an array, the specific element can be addressed
     * in the cell. However the index doesn't start at 0 but on 1 to make it more readable.
     * @param cellContent symbol of array with specification of element: ie arrayname.1 stands for arrayname[0]
     * @return the java index of the array (arrayname.1 returns 0, arrayname.2 return 1, etc)
     */
    private int getIndexFromSymbolArray(String[] cellContent) {
        int index = Integer.valueOf(cellContent[1]) - 1;
        return index;
    }

    /**
     * Fetch the value from arraySymbol on specified index.
     * @param arraySymbol symbol from Fixture that is array
     * @param index to find element from array
     * @return the element from the symbol array
     */
    private Object getSymbolArrayValue(Object arraySymbol, int index) {
        Object result = null;
        if (index > -1 && index < ((Object[]) arraySymbol).length) {
            result = ((Object[]) arraySymbol)[index];
        }
        return result;
    }

    boolean isSpecialBlankValueForFitnesse(String text) {
        if ("".equals(text)) {
            return true;
        } else if ("blank".equalsIgnoreCase(text)) {
            return true;
        } else if ("null".equalsIgnoreCase(text)) {
            return true;
        }
        return false;
    }

    /**
     * @return the currentRowValues
     */
    public Map getCurrentRowValues() {
        return currentRowValues;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy