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}