001 package com.mockrunner.jdbc; 002 003 import java.util.ArrayList; 004 import java.util.Arrays; 005 import java.util.HashSet; 006 import java.util.List; 007 import java.util.Set; 008 009 /** 010 * A data structure providing tabular (row and column) access semantics to 011 * items within. While applicable to several usages, the primary purpose is 012 * (in conjunction with <code>ArrayResultSetFactory</code>) to provide for easy 013 * set up of unit test fixtures and assertion of outcomes with the same data 014 * structures, without any need for external sources of test data. 015 * 016 * @author Erick G. Reid 017 */ 018 public class StringValuesTable 019 { 020 private String name; // the table name 021 private List columnNames = new ArrayList(0); // the columns 022 private String[][] stringMatrix; // the table data 023 024 /** 025 * Creates a new <code>StringValuesTable</code> with the given name, 026 * columns and string matrix. 027 * 028 * @param name the table name. This argument cannot be <code>null</code> 029 * and must contain at least <code>1</code> non-blank character. 030 * @param stringMatrix the string matrix. This argument cannot be <code>null</code>, 031 * must not contain any null values, and each array in the matrix 032 * must contain the same number of elements as the first (<code>stringMatrix[0].length == stringMatrix[n].length</code> 033 * for any given valid row number, <code>n</code>). Further, 034 * this matrix must, at a minimum represent <code>1</code> row 035 * and <code>1</code> column of items (<code>stringMatrix.length >= 1</code>, 036 * and <code>stringMatrix[0].length >= 1</code>). 037 */ 038 public StringValuesTable(String name, String[][] stringMatrix) 039 { 040 this(name, null, stringMatrix); 041 } 042 043 /** 044 * Creates a new <code>StringValuesTable</code> with the given name, 045 * columns and string matrix. 046 * 047 * @param name the table name. This argument cannot be <code>null</code> 048 * and must contain at least <code>1</code> non-blank character. 049 * @param columnNames the names for the columns in this <code> 050 * StringValuesTable</code>. This argument may be <code>null</code> if no column names 051 * are desired, but if a non-<code>null</code> array reference 052 * is given, the array cannot contain any <code>null</code> nor 053 * duplicate elements, and must have the same number of elements 054 * as there are columns in the given string matrix (<code>stringMatrix[n]</code> 055 * for any given valid row number, <code>n</code>). 056 * @param stringMatrix the string matrix. This argument cannot be <code>null</code>, 057 * must not contain any null values, and each array in the matrix 058 * must contain the same number of elements as the first (<code>stringMatrix[0].length == stringMatrix[n].length</code> 059 * for any given valid row number, <code>n</code>). Further, 060 * this matrix must, at a minimum represent <code>1</code> row 061 * and <code>1</code> column of items (<code>stringMatrix.length >= 1</code>, 062 * and <code>stringMatrix[0].length >= 1</code>). 063 */ 064 public StringValuesTable(String name, String[] columnNames, String[][] stringMatrix) 065 { 066 067 if (name != null) 068 { 069 if (name.trim().length() > 0) 070 { 071 this.name = name; 072 this.stringMatrix = verifyStringMatrix(stringMatrix); 073 if (columnNames != null) 074 { 075 this.columnNames = Arrays.asList(verifyColumnNames(columnNames, stringMatrix)); 076 } 077 return; 078 } 079 throw new IllegalArgumentException("invalid table name given"); 080 } 081 throw new IllegalArgumentException("the table name cannot be null"); 082 } 083 084 /** 085 * Returns the contents of the given column. 086 * 087 * @param columnName the name of the desired column. This argument cannot be 088 * <code>null</code> and must be a valid column for this 089 * <code>StringValuesTable</code>. 090 * @return the contents of the given column. 091 */ 092 public String[] getColumn(String columnName) 093 { 094 if (columnName != null) 095 { 096 int index = this.columnNames.indexOf(columnName); 097 if (index >= 0) 098 { 099 return doGetColumn(index); 100 } 101 throw new IllegalArgumentException(columnName 102 + " is not a valid column name"); 103 } 104 throw new IllegalArgumentException("the column name cannot be null"); 105 } 106 107 /** 108 * Returns the contents of the given column. 109 * 110 * @param columnNumber the index of the desired column (<code>1</code>-based). 111 * This argument must be a valid column index for this 112 * <code>StringValuesTable</code>. 113 * @return the contents of the given column. 114 */ 115 public String[] getColumn(int columnNumber) 116 { 117 return doGetColumn(--columnNumber); 118 } 119 120 /** 121 * Returns the column names. This array may be empty if column names are not 122 * being used. 123 * 124 * @return the column names. 125 */ 126 public String[] getColumnNames() 127 { 128 return (String[]) this.columnNames.toArray(new String[this.columnNames.size()]); 129 } 130 131 /** 132 * Returns the item found in the string matrix at the given coordinate. 133 * 134 * @param rowNumber the number of the desired row (<code>1</code>-based). This 135 * argument must be a valid row number for this 136 * <code>StringValuesTable</code>. 137 * @param columnName the name of the desired column. This argument cannot be 138 * <code>null</code> and must be a valid column for this 139 * <code>StringValuesTable</code>. 140 * @return the item found in the string matrix at the given coordinate. 141 */ 142 public String getItem(int rowNumber, String columnName) 143 { 144 if (columnName != null) 145 { 146 int index = this.columnNames.indexOf(columnName); 147 if (index >= 0) 148 { 149 return doGetRow(rowNumber)[index]; 150 } 151 throw new IllegalArgumentException(columnName + " is not a valid column index"); 152 } 153 throw new IllegalArgumentException("the column name cannot be null"); 154 } 155 156 /** 157 * Returns the item found in the string matrix at the given coordinate. 158 * 159 * @param rowNumber the number of the desired row (<code>1</code>-based). This 160 * argument must be a valid row number for this 161 * <code>StringValuesTable</code>. 162 * @param columnNumber the index of the desired column (<code>1</code>-based). 163 * This argument must be a valid column index for this 164 * <code>StringValuesTable</code>. 165 * @return the item found in the string matrix at the given coordinate. 166 */ 167 public String getItem(int rowNumber, int columnNumber) 168 { 169 if (isColumnNumberValid(columnNumber)) 170 { 171 return doGetRow(rowNumber)[--columnNumber]; 172 } 173 throw new IllegalArgumentException(columnNumber + " is not a valid column index"); 174 } 175 176 /** 177 * Returns the table name. 178 * 179 * @return the table name. 180 */ 181 public String getName() 182 { 183 return this.name; 184 } 185 186 /** 187 * Returns the number of columns found in the string matrix for this 188 * <code>StringValuesTable</code>. 189 * 190 * @return the number of columns found in the string matrix for this 191 * <code>StringValuesTable</code>. 192 */ 193 public int getNumberOfColumns() 194 { 195 return this.stringMatrix[0].length; 196 } 197 198 /** 199 * Returns the number of rows found in the string matrix for this 200 * <code>StringValuesTable</code>. 201 * 202 * @return the number of rows found in the string matrix for this 203 * <code>StringValuesTable</code>. 204 */ 205 public int getNumberOfRows() 206 { 207 return this.stringMatrix.length; 208 } 209 210 /** 211 * Returns the elements of the given row. 212 * 213 * @param rowNumber the number of the desired row (<code>1</code>-based). This 214 * argument must be a valid row number for this 215 * <code>StringValuesTable</code>. 216 * @return the elements of the given row. 217 */ 218 public String[] getRow(int rowNumber) 219 { 220 return doGetRow(rowNumber); 221 } 222 223 /** 224 * Returns <code>true</code> if the given column name is valid for this 225 * <code>StringValuesTable</code>; returns <code>false</code> 226 * otherwise. 227 * 228 * @param columnName the column name to verify. 229 * @return <code>true</code> if the given column name is valid for this 230 * <code>StringValuesTable</code>. 231 */ 232 public boolean isValidColumnName(String columnName) 233 { 234 return columnName == null ? false : isColumnNumberValid(this.columnNames.indexOf(columnName) + 1); 235 } 236 237 /** 238 * Returns <code>true</code> if the given column number is valid for this 239 * <code>StringValuesTable</code>; returns <code>false</code> 240 * otherwise. 241 * 242 * @param columnNumber the column number to verify. 243 * @return <code>true</code> if the given column number is valid for this 244 * <code>StringValuesTable</code>. 245 */ 246 public boolean isValidColumnNumber(int columnNumber) 247 { 248 return isColumnNumberValid(columnNumber); 249 } 250 251 /** 252 * Returns <code>true</code> if the given row number is valid for this 253 * <code>StringValuesTable</code>; returns <code>false</code> 254 * otherwise. 255 * 256 * @param row the row number to verify. 257 * @return <code>true</code> if the given index is valid for this 258 * <code>StringValuesTable</code>. 259 */ 260 public boolean isValidRowNumber(int row) 261 { 262 return --row >= 0 && row < this.stringMatrix.length; 263 } 264 265 /** 266 * Returns the tabular data for this <code>StringValuesTable</code>. 267 * 268 * @return the tabular data for this <code>StringValuesTable</code>. 269 */ 270 public String[][] getStringMatrix() 271 { 272 return this.stringMatrix; 273 } 274 275 /** 276 * Returns the given array if it is found to indeed be valid according to 277 * the published contract. 278 */ 279 public synchronized static String[] verifyColumnNames(final String[] columnNames, final String[][] stringMatrix) 280 { 281 // note: the string matrix must already have been verified at this 282 // point... 283 284 if (columnNames != null) 285 { 286 if (columnNames.length == stringMatrix[0].length) 287 { 288 String name = null; 289 Set names = new HashSet(); 290 for (int i = 0; i < columnNames.length; i++) 291 { 292 name = columnNames[i]; 293 if (name == null) 294 { 295 throw new IllegalArgumentException("the column names array must not contain null elements"); 296 } 297 else 298 { 299 if (names.contains(name)) 300 { 301 throw new IllegalArgumentException("the column names array must not contain duplicate elements"); 302 } 303 names.add(name); 304 } 305 } 306 return columnNames; 307 } 308 throw new IllegalArgumentException(columnNames.length + " columns were given where " + stringMatrix[0].length 309 + (stringMatrix[0].length == 1 ? " is" : " are") + " required"); 310 } 311 throw new IllegalArgumentException("the column names array cannot be null"); 312 } 313 314 /** 315 * Returns the given matrix if it is found to indeed be valid according to 316 * the published contract. 317 */ 318 public synchronized static String[][] verifyStringMatrix(final String[][] stringMatrix) 319 { 320 if (stringMatrix != null) 321 { 322 if (stringMatrix.length > 0) 323 { 324 if (stringMatrix[0] != null && stringMatrix[0].length > 0) 325 { 326 for (int ii = 0; ii < stringMatrix.length; ii++) 327 { 328 if (stringMatrix[ii] == null) 329 { 330 throw new IllegalArgumentException("the string matrix cannot contain any null arrays"); 331 } 332 if (stringMatrix[ii].length != stringMatrix[0].length) 333 { 334 throw new IllegalArgumentException("arrays in the string matrix must all contain " 335 + stringMatrix[0].length + " elements"); 336 } 337 for (int jj = 0; jj < stringMatrix[ii].length; jj++) 338 { 339 if (stringMatrix[ii][jj] == null) 340 { 341 throw new IllegalArgumentException("arrays in the string matrix must not contain null elements"); 342 } 343 } 344 } 345 return stringMatrix; 346 } 347 throw new IllegalArgumentException("the string matrix must contain at least 1 column of items"); 348 } 349 throw new IllegalArgumentException("the string matrix must contain at least 1 row of items"); 350 } 351 throw new IllegalArgumentException("the string matrix cannot be null"); 352 } 353 354 /** 355 * Returns an array of items from each row of the given column. 356 */ 357 private String[] doGetColumn(int index) 358 { 359 if (index >= 0 && index < this.stringMatrix[0].length) 360 { 361 String[] data = new String[this.stringMatrix.length]; 362 for (int row = 0; row < this.stringMatrix.length; row++) 363 { 364 data[row] = this.stringMatrix[row][index]; 365 } 366 return data; 367 } 368 throw new IllegalArgumentException(++index + " is not a valid column index"); 369 } 370 371 /** 372 * Returns the elements of the given row. 373 */ 374 private String[] doGetRow(int rowNumber) 375 { 376 if (--rowNumber >= 0 && rowNumber < this.stringMatrix.length) 377 { 378 return this.stringMatrix[rowNumber]; 379 } 380 throw new IllegalArgumentException(++rowNumber + " is not a valid row number"); 381 } 382 383 /** 384 * Returns <code>true</code> if the given column number is valid 385 */ 386 private boolean isColumnNumberValid(int columnNumber) 387 { 388 return --columnNumber >= 0 && columnNumber < this.stringMatrix[0].length; 389 } 390 } 391