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