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 java.lang.reflect.Array; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Iterator; 030import java.util.List; 031 032/** 033 * Prepresents a table with header row and data rows as defined via the {@link org.tools4j.spockito.Spockito.Unroll} 034 * annotation. 035 */ 036public class Table implements Iterable<TableRow> { 037 038 private static final Table EMPTY = new Table(); 039 040 private final TableRow headers; 041 private final List<TableRow> data = new ArrayList<>(); 042 043 private Table() { 044 this.headers = TableRow.empty(this); 045 } 046 047 private Table(final String headerString) { 048 this.headers = parseRow(this, 0, headerString); 049 if (headers.distinctCount() < headers.size()) { 050 throw new IllegalArgumentException("Duplicate column headers: " + headers); 051 } 052 } 053 054 public int getColumnCount() { 055 //null in constructor when parsing header row 056 return headers == null ? 0 : headers.size(); 057 } 058 059 public int getColumnIndexByName(final String columnName) { 060 int columnIndex = headers.indexOf(columnName); 061 if (columnIndex < 0) { 062 if (columnName.length() > 0 && Character.isLowerCase(columnName.charAt(0))) { 063 columnIndex = headers.indexOf(Strings.firstCharToUpperCase(columnName)); 064 } 065 } 066 if (columnIndex < 0) { 067 throw new IllegalArgumentException("No such column: " + columnName); 068 } 069 return columnIndex; 070 } 071 072 public boolean hasColumn(final String columnName) { 073 return 0 <= headers.indexOf(columnName); 074 } 075 076 public int getRowCount() { 077 return data.size(); 078 } 079 080 public String getColumnName(final int index) { 081 return headers.get(index); 082 } 083 084 public TableRow getRow(final int rowIndex) { 085 return data.get(rowIndex); 086 } 087 088 public int getRowIndex(final TableRow row) { 089 return data.indexOf(row); 090 } 091 092 public String getValue(final int rowIndex, final int columnIndex) { 093 return data.get(rowIndex).get(columnIndex); 094 } 095 096 public String getValue(final int rowIndex, final String columnName) { 097 final int columnIndex = getColumnIndexByName(columnName); 098 return data.get(rowIndex).get(columnIndex); 099 } 100 101 @Override 102 public Iterator<TableRow> iterator() { 103 return Collections.unmodifiableList(data).iterator(); 104 } 105 106 public static <T> T[] parse(final Class<T> rowType, final String[] headerAndRows) { 107 return parse(rowType, headerAndRows, new SpockitoValueConverter()); 108 } 109 110 public static <T> T[] parse(final Class<T> rowType, final String[] headerAndRows, final ValueConverter valueConverter) { 111 final Table table = parse(headerAndRows); 112 final int rows = table.getRowCount(); 113 final T[] result = (T[])Array.newInstance(rowType, rows); 114 for (int row = 0; row < rows; row++) { 115 result[row] = valueConverter.convert(rowType, rowType, table.getRow(row).asMap().toString()); 116 } 117 return result; 118 } 119 120 public static Table parse(final String[] headerAndRows) { 121 if (headerAndRows.length > 0) { 122 final Table table = new Table(headerAndRows[0]); 123 for (int i = 1; i < headerAndRows.length; i++) { 124 final TableRow tableRow = parseRow(table, i, headerAndRows[i]); 125 if (!tableRow.isSeparatorRow()) { 126 table.data.add(tableRow); 127 } 128 } 129 return table; 130 } 131 return Table.EMPTY; 132 } 133 134 private static TableRow parseRow(final Table table, final int row, final String rowString) { 135 final String trimmed = rowString.trim(); 136 if (trimmed.length() < 2 || trimmed.charAt(0) != '|' || trimmed.charAt(trimmed.length() - 1) != '|') { 137 throw new IllegalArgumentException("Invalid table data: row " + row + " must start and end with '|'"); 138 } 139 final TableRow tableRow = TableRow.parse(table, trimmed); 140 if (row != 0) { 141 if (tableRow.size() > table.getColumnCount()) { 142 throw new IllegalArgumentException("Invalid table data: row " + row + " has more columns than header row: " + tableRow.size() + " > " + table.getColumnCount()); 143 } 144 } 145 return tableRow; 146 } 147 148}