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