001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2017 tools4j.org (Marco Terzer)
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package org.tools4j.spockito;
025
026import org.junit.runners.model.FrameworkField;
027
028import java.lang.reflect.Executable;
029import java.lang.reflect.Field;
030import java.lang.reflect.Parameter;
031import java.lang.reflect.Type;
032import java.util.*;
033import java.util.regex.Pattern;
034
035/**
036 * Represents a single row of a {@link Table} defined via {@link org.tools4j.spockito.Spockito.Unroll} annotaiton.
037 */
038public class TableRow {
039
040    public static final String REF_ROW = "row";
041    public static final String REF_ALL = "*";
042
043    private static final Pattern UNESCAPED_PIPE = Pattern.compile("(?<=[^\\\\])\\|");
044
045    private final Table table;
046    private final List<String> values = new ArrayList<>();
047
048    public TableRow(final Table table) {
049        this.table = Objects.requireNonNull(table);
050    }
051
052    public static TableRow empty(final Table table) {
053        return new TableRow(table);
054    }
055
056    public static TableRow parse(final Table table, final String rowString) {
057        final String noBars = Strings.removeSurroundingPipes(rowString);
058        final String[] parts = UNESCAPED_PIPE.split(noBars);
059        final TableRow tableRow = new TableRow(table);
060        for (final String part : parts) {
061            tableRow.values.add(Converters.STRING_CONVERTER.apply(Strings.unescape(part.trim())));
062        }
063        for (int i = parts.length; i < table.getColumnCount(); i++) {
064            tableRow.values.add(null);
065        }
066        return tableRow;
067    }
068
069    public Table getTable() {
070        return table;
071    }
072
073    public boolean isSeparatorRow() {
074        return values.stream().anyMatch(s -> s.contains("-") || s.contains("=")) &&
075                values.stream().allMatch(s -> Strings.allCharsMatchingAnyOf(s, '-', '='));
076    }
077
078    public boolean isValidRefName(final String refName) {
079        return REF_ROW.equals(refName) || REF_ALL.equals(refName) || table.hasColumn(refName);
080    }
081
082    public Object[] convertValues(final Executable executable, final ValueConverter valueConverter) {
083        final Object[] converted = new Object[executable.getParameterCount()];
084        final Parameter[] parameters = executable.getParameters();
085        for (int i = 0; i < converted.length; i++) {
086            final String refName = Spockito.parameterRefNameOrNull(parameters[i]);
087            converted[i] = convertValue(refName, i, parameters[i].getType(), parameters[i].getParameterizedType(), valueConverter);
088        }
089        return converted;
090    }
091
092    public Object[] convertValues(final List<FrameworkField> fields, final ValueConverter valueConverter) {
093        final Object[] converted = new Object[fields.size()];
094        for (int i = 0; i < converted.length; i++) {
095            final Field field = fields.get(i).getField();
096            final String refValue = fields.get(i).getAnnotation(Spockito.Ref.class).value();
097            final String refName = refValue.isEmpty() ? field.getName() : refValue;
098            converted[i] = convertValue(refName, -1, field.getType(), field.getGenericType(), valueConverter);
099        }
100        return converted;
101    }
102
103    private Object convertValue(final String refNameOrNull, final int defaultColumnIndex,
104                                final Class<?> type, final Type genericType, final ValueConverter valueConverter) {
105        final String value;
106        if (REF_ROW.equals(refNameOrNull)) {
107            value = String.valueOf(getRowIndex());
108        } else if (REF_ALL.equals(refNameOrNull)) {
109            value = asMap().toString();
110        } else {
111            try {
112                final int columnIndex = refNameOrNull == null ? defaultColumnIndex : table.getColumnIndexByName(refNameOrNull);
113                value = get(columnIndex);
114            } catch (final Exception e) {
115                throw new IllegalArgumentException("Could not access column value " + refName(refNameOrNull, defaultColumnIndex), e);
116            }
117        }
118        try {
119            return valueConverter.convert(type, genericType, value);
120        } catch (final Exception e) {
121            throw new IllegalArgumentException("Conversion to " + genericType + " failed for column '" +
122                    refName(refNameOrNull, defaultColumnIndex) + "' value: " + value, e);
123        }
124    }
125
126    final Object refName(final String refNameOrNull, final int defaultColumnIndex) {
127        return refNameOrNull != null ? refNameOrNull : defaultColumnIndex;
128    }
129
130    public int size() {
131        return values.size();
132    }
133
134    public int distinctCount() {
135        return (int) values.stream().distinct().count();
136    }
137
138    public String get(final int index) {
139        return values.get(index);
140    }
141
142    public int indexOf(final String value) {
143        return values.indexOf(value);
144    }
145
146    public int getRowIndex() {
147        return table.getRowIndex(this);
148    }
149
150    public Map<String, String> asMap() {
151        final Map<String, String> map = new LinkedHashMap<>();
152        for (int i = 0; i < size(); i++) {
153            map.put(table.getColumnName(i), get(i));
154        }
155        return map;
156    }
157
158    @Override
159    public String toString() {
160        return "TableRow" + values;
161    }
162}