1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package complex.toolkit.awtgrid;
25 
26 import java.lang.reflect.Method;
27 import com.sun.star.awt.grid.GridDataEvent;
28 import com.sun.star.awt.grid.XMutableGridDataModel;
29 import com.sun.star.lang.IllegalArgumentException;
30 import com.sun.star.lang.IndexOutOfBoundsException;
31 import static org.junit.Assert.*;
32 import static complex.toolkit.Assert.*;
33 
34 /** test for the <code>css.awt.grid.XMutableGridData</code> interface
35  *
36  * @author frank.schoenheit@oracle.com
37  */
38 public class TMutableGridDataModel
39 {
TMutableGridDataModel( final XMutableGridDataModel i_dataModel )40     public TMutableGridDataModel( final XMutableGridDataModel i_dataModel )
41     {
42         m_dataModel = i_dataModel;
43 
44         m_listener = new GridDataListener();
45         m_dataModel.addGridDataListener( m_listener );
46     }
47 
48     /*
49      * tests the XMutableGridDataModel.addRow method
50      */
testAddRow()51     public void testAddRow() throws IndexOutOfBoundsException
52     {
53         m_dataModel.addRow( m_rowHeadings[0], m_rowValues[0] );
54         GridDataEvent event = m_listener.assertSingleRowInsertionEvent();
55         m_listener.reset();
56         assertEquals( "row insertion: wrong FirstRow (1)", 0, event.FirstRow );
57         assertEquals( "row insertion: wrong LastRow (1)", 0, event.LastRow );
58         impl_assertRowData( 0 );
59 
60         m_dataModel.addRow( m_rowHeadings[1], m_rowValues[1] );
61         event = m_listener.assertSingleRowInsertionEvent();
62         m_listener.reset();
63         assertEquals( "row insertion: wrong FirstRow (2)", 1, event.FirstRow );
64         assertEquals( "row insertion: wrong LastRow (2)", 1, event.LastRow );
65         impl_assertRowData( 1 );
66     }
67 
68     /**
69      * tests the XMutableGridDataModel.addRows method
70      */
testAddRows()71     public void testAddRows() throws IndexOutOfBoundsException, IllegalArgumentException
72     {
73         assertEquals( "precondition not met: call this directly after testAddRow, please!", 2, m_dataModel.getRowCount() );
74 
75         m_dataModel.addRows(
76                 new Object[] { m_rowHeadings[2], m_rowHeadings[3], m_rowHeadings[4] },
77                 new Object[][] { m_rowValues[2], m_rowValues[3], m_rowValues[4] } );
78         GridDataEvent event = m_listener.assertSingleRowInsertionEvent();
79         assertEquals( "row insertion: wrong FirstRow (1)", 2, event.FirstRow );
80         assertEquals( "row insertion: wrong LastRow (1)", 4, event.LastRow );
81         m_listener.reset();
82 
83         assertEquals( "data model's row count is not adjusted when adding rows", m_rowValues.length, m_dataModel.getRowCount() );
84         assertEquals( "data model's column count is not adjusted when adding rows", m_rowValues[0].length, m_dataModel.getColumnCount() );
85         for ( int row=0; row<m_rowValues.length; ++row )
86         {
87             for ( int col=0; col<m_rowValues[row].length; ++col )
88             {
89                 assertEquals( "added row values are not preserved",
90                     m_rowValues[row][col], m_dataModel.getCellData( col, row ) );
91             }
92         }
93 
94         assertException( "addRows is expected to throw when invoked with different-sized arrays",
95             m_dataModel, "addRows", new Object[] { new Object[0], new Object[1][2] }, IllegalArgumentException.class );
96     }
97 
98     /**
99      * tests the XMutableGridDataModel.insertRow method
100      */
testInsertRow()101     public void testInsertRow() throws IndexOutOfBoundsException
102     {
103         int expectedRowCount = m_rowValues.length;
104         assertEquals( "precondition not met: call this directly after testAddRows, please!", expectedRowCount, m_dataModel.getRowCount() );
105 
106         // inserting some row somewhere between the other rows
107         final Object heading = "inbetweenRow";
108         final Object[] inbetweenRow = new Object[] { "foo", "bar", 3, 4, 5 };
109         final int insertionPos = 2;
110         m_dataModel.insertRow( insertionPos, heading, inbetweenRow );
111         ++expectedRowCount;
112         assertEquals( "inserting a row is expected to increment the row count",
113                 expectedRowCount, m_dataModel.getRowCount() );
114 
115         final GridDataEvent event = m_listener.assertSingleRowInsertionEvent();
116         assertEquals( "inserting a row results in wrong FirstRow being notified", insertionPos, event.FirstRow );
117         assertEquals( "inserting a row results in wrong LastRow being notified", insertionPos, event.LastRow );
118         m_listener.reset();
119 
120         for ( int row=0; row<expectedRowCount; ++row )
121         {
122             final Object[] actualRowData = m_dataModel.getRowData( row );
123             final Object[] expectedRowData =
124                 ( row < insertionPos )
125                 ?   m_rowValues[ row ]
126                 :   ( row == insertionPos )
127                     ?   inbetweenRow
128                     :   m_rowValues[ row - 1 ];
129             assertArrayEquals( "row number " + row + " has wrong content content after inserting a row",
130                     expectedRowData, actualRowData );
131 
132             final Object actualHeading = m_dataModel.getRowHeading(row);
133             final Object expectedHeading =
134                 ( row < insertionPos )
135                 ?   m_rowHeadings[ row ]
136                 :   ( row == insertionPos )
137                     ?   heading
138                     :   m_rowHeadings[ row - 1 ];
139             assertEquals( "row " + row + " has a wrong heading after invoking insertRow",
140                     expectedHeading, actualHeading );
141         }
142 
143         // exceptions
144         assertException( "inserting a row at a position > rowCount is expected to throw",
145                 m_dataModel, "insertRow",
146                 new Class[] { Integer.class, Object.class, Object[].class },
147                 new Object[] { expectedRowCount + 1, "", new Object[] { "1", 2, 3 } },
148                 IndexOutOfBoundsException.class );
149         assertException( "inserting a row at a position < 0 is expected to throw",
150                 m_dataModel, "insertRow",
151                 new Class[] { Integer.class, Object.class, Object[].class },
152                 new Object[] { -1, "", new Object[] { "1", 2, 3 } },
153                 IndexOutOfBoundsException.class );
154 
155         // remove the row, to create the situation expected by the next test
156         m_dataModel.removeRow( insertionPos );
157         m_listener.reset();
158     }
159 
160     /**
161      * tests the XMutableGridDataModel.insertRows method
162      */
testInsertRows()163     public void testInsertRows() throws IndexOutOfBoundsException, IllegalArgumentException
164     {
165         int expectedRowCount = m_rowValues.length;
166         assertEquals( "precondition not met: call this directly after testInsertRow, please!", expectedRowCount, m_dataModel.getRowCount() );
167 
168         // inserting some rows somewhere between the other rows
169         final int insertionPos = 3;
170         final Object[] rowHeadings = new Object[] { "A", "B", "C" };
171         final Object[][] rowData = new Object[][] {
172             new Object[] { "A", "B", "C", "D", "E" },
173             new Object[] { "J", "I", "H", "G", "F" },
174             new Object[] { "K", "L", "M", "N", "O" }
175         };
176         final int insertedRowCount = rowData.length;
177         assertEquals( "invalid test data", rowHeadings.length, insertedRowCount );
178 
179         m_dataModel.insertRows( insertionPos, rowHeadings, rowData );
180         expectedRowCount += insertedRowCount;
181 
182         final GridDataEvent event = m_listener.assertSingleRowInsertionEvent();
183         assertEquals( "inserting multiple rows results in wrong FirstRow being notified",
184                 insertionPos, event.FirstRow );
185         assertEquals( "inserting multiple rows results in wrong LastRow being notified",
186                 insertionPos + insertedRowCount - 1, event.LastRow );
187         m_listener.reset();
188 
189         for ( int row=0; row<expectedRowCount; ++row )
190         {
191             final Object[] actualRowData = m_dataModel.getRowData( row );
192             final Object[] expectedRowData =
193                 ( row < insertionPos )
194                 ?   m_rowValues[ row ]
195                 :   ( row >= insertionPos ) && ( row < insertionPos + insertedRowCount )
196                     ?   rowData[ row - insertionPos ]
197                     :   m_rowValues[ row - insertedRowCount ];
198             assertArrayEquals( "row number " + row + " has wrong content content after inserting multiple rows",
199                     expectedRowData, actualRowData );
200 
201             final Object actualHeading = m_dataModel.getRowHeading(row);
202             final Object expectedHeading =
203                 ( row < insertionPos )
204                 ?   m_rowHeadings[ row ]
205                 :   ( row >= insertionPos ) && ( row < insertionPos + insertedRowCount )
206                     ?   rowHeadings[ row - insertionPos ]
207                     :   m_rowHeadings[ row - insertedRowCount ];
208             assertEquals( "row " + row + " has a wrong heading after invoking insertRows",
209                     expectedHeading, actualHeading );
210         }
211 
212         // exceptions
213         assertException( "inserting multiple rows at a position > rowCount is expected to throw an IndexOutOfBoundsException",
214                 m_dataModel, "insertRows",
215                 new Class[] { Integer.class, Object[].class, Object[][].class },
216                 new Object[] { expectedRowCount + 1, new Object[0], new Object[][] { } },
217                 IndexOutOfBoundsException.class );
218         assertException( "inserting multiple rows at a position < 0 is expected to throw an IndexOutOfBoundsException",
219                 m_dataModel, "insertRows",
220                 new Class[] { Integer.class, Object[].class, Object[][].class },
221                 new Object[] { -1, new Object[0], new Object[][] { } },
222                 IndexOutOfBoundsException.class );
223         assertException( "inserting multiple rows with inconsistent array lengths is expected to throw an IllegalArgumentException",
224                 m_dataModel, "insertRows",
225                 new Class[] { Integer.class, Object[].class, Object[][].class },
226                 new Object[] { 0, new Object[0], new Object[][] { new Object[0] } },
227                 IllegalArgumentException.class );
228 
229         // remove the row, to create the situation expected by the next test
230         for ( int i=0; i<insertedRowCount; ++i )
231         {
232             m_dataModel.removeRow( insertionPos );
233             m_listener.reset();
234         }
235     }
236 
237     /**
238      * tests the XMutableGridDataModel.removeRow method
239      */
testRemoveRow()240     public void testRemoveRow() throws IndexOutOfBoundsException
241     {
242         assertEquals( "precondition not met: call this directly after testAddRows, please!", m_rowValues.length, m_dataModel.getRowCount() );
243 
244         final int rowToRemove = 2;
245         m_dataModel.removeRow( rowToRemove );
246         GridDataEvent event = m_listener.assertSingleRowRemovalEvent();
247         assertEquals( "incorrect notification of row removal (FirstRow)", rowToRemove, event.FirstRow );
248         assertEquals( "incorrect notification of row removal (LastRow)", rowToRemove, event.LastRow );
249         m_listener.reset();
250 
251         assertEquals( "data model's row count does not reflect the removed row", m_rowValues.length - 1, m_dataModel.getRowCount() );
252         for ( int row = rowToRemove; row<m_rowValues.length-1; ++row )
253         {
254             for ( int col=0; col<m_rowValues[row].length; ++col )
255             {
256                 assertEquals( "unexpected row values after removing a row (col: " + col + ", row: " + row + ")",
257                     m_rowValues[row+1][col], m_dataModel.getCellData( col, row ) );
258             }
259         }
260 
261         assertException( "removeRow silently ignores an invalid index (1)",
262             m_dataModel, "removeRow", new Object[] { -1 }, IndexOutOfBoundsException.class );
263         assertException( "removeRow silently ignores an invalid index (2)",
264             m_dataModel, "removeRow", new Object[] { m_dataModel.getRowCount() }, IndexOutOfBoundsException.class );
265     }
266 
267     /**
268      * tests the XMutableGridDataModel.removeAllRows method
269      */
testRemoveAllRows()270     public void testRemoveAllRows()
271     {
272         assertEquals( "precondition not met: call this directly after testRemoveRow, please!", m_rowValues.length - 1, m_dataModel.getRowCount() );
273 
274         m_dataModel.removeAllRows();
275         final GridDataEvent event = m_listener.assertSingleRowRemovalEvent();
276         if ( event.FirstRow != -1 )
277         {   // notifying "-1" is allowed, this means "all rows affected", by definition
278             assertEquals( "removeAllRows is not notifying properly (1)", 0, event.FirstRow );
279             assertEquals( "removeAllRows is not notifying properly (2)", m_rowValues.length - 1, event.LastRow );
280         }
281         m_listener.reset();
282     }
283 
284     /**
285      * tests the XMutableGridDataModel.updateCellData method
286      */
testUpdateCellData()287     public void testUpdateCellData() throws IndexOutOfBoundsException, IllegalArgumentException
288     {
289         assertEquals( "precondition not met: call this directly after testRemoveAllRows, please!", 0, m_dataModel.getRowCount() );
290 
291         m_dataModel.addRows( m_rowHeadings, m_rowValues );
292         m_listener.assertSingleRowInsertionEvent();
293         m_listener.reset();
294 
295         final Object[][] modifyValues = new Object[][] {
296             new Object[] { 2, 1, "text" },
297             new Object[] { 3, 0, null },
298             new Object[] { 0, 4, new Double( 33.0 ) }
299         };
300         for ( int i = 0; i < modifyValues.length; ++i )
301         {
302             final int row = ((Integer)modifyValues[i][0]).intValue();
303             final int col = ((Integer)modifyValues[i][1]).intValue();
304             final Object value = modifyValues[i][2];
305             m_dataModel.updateCellData( col, row, value );
306 
307             final GridDataEvent event = m_listener.assertSingleDataChangeEvent();
308             assertEquals( "data change notification: FirstRow is invalid", row, event.FirstRow );
309             assertEquals( "data change notification: LastRow is invalid", row, event.LastRow );
310             assertEquals( "data change notification: FirstColumn is invalid", col, event.FirstColumn );
311             assertEquals( "data change notification: LastColumn is invalid", col, event.LastColumn );
312             m_listener.reset();
313 
314             assertEquals( "data change at (" + col + ", " + row + ") not successful", value, m_dataModel.getCellData( col, row ) );
315         }
316 
317         assertException( "updateCellData silently ignores an invalid index (1)",
318             m_dataModel, "updateCellData", new Class[] { int.class, int.class, Object.class },
319             new Object[] { -1, -1, "text" }, IndexOutOfBoundsException.class );
320         assertException( "updateCellData silently ignores an invalid index (2)",
321             m_dataModel, "updateCellData", new Class[] { int.class, int.class, Object.class },
322             new Object[] { 0, m_dataModel.getRowCount(), "text" }, IndexOutOfBoundsException.class );
323         assertException( "updateCellData silently ignores an invalid index (3)",
324             m_dataModel, "updateCellData", new Class[] { int.class, int.class, Object.class },
325             new Object[] { m_dataModel.getColumnCount(), 0, "text" }, IndexOutOfBoundsException.class );
326     }
327 
328     /**
329      * tests the XMutableGridDataModel.updateRowData method
330      */
testUpdateRowData()331     public void testUpdateRowData() throws IndexOutOfBoundsException, IllegalArgumentException
332     {
333         assertEquals( "precondition not met: call this directly after testRemoveAllRows, please!", m_rowValues.length, m_dataModel.getRowCount() );
334 
335         // get data from before the update
336         final Object[][] preUpdateValues = impl_getCurrentData();
337 
338         // do the update
339         final int[] colIndexes = new int[] {
340             0, 3, 4
341         };
342         final Object[] values = new Object[] {
343             13, null, 42.0
344         };
345         final int rowToUpdate = 2;
346         m_dataModel.updateRowData( colIndexes, rowToUpdate, values );
347         final GridDataEvent event = m_listener.assertSingleDataChangeEvent();
348         assertEquals( "row update notification: FirstRow is invalid", rowToUpdate, event.FirstRow );
349         assertEquals( "row update notification: LastRow is invalid", rowToUpdate, event.LastRow );
350         assertEquals( "row update notification: FirstColumn is invalid", 0, event.FirstColumn );
351         assertEquals( "row update notification: LastColumn is invalid", 4, event.LastColumn );
352         m_listener.reset();
353 
354         // reflect the changes made in the pre-update data
355         for ( int i=0; i<colIndexes.length; ++i )
356         {
357             preUpdateValues[rowToUpdate][colIndexes[i]] = values[i];
358         }
359 
360         // get data from after the update
361         final Object[][] postUpdateValues = impl_getCurrentData();
362 
363         // ensure both the manually updated pre-update data and the post-update data are identical
364         assertArrayEquals( preUpdateValues, postUpdateValues );
365 
366 
367         assertException( "updateRowData silently ignores an invalid index (1)",
368             m_dataModel, "updateRowData", new Class[] { int[].class, int.class, Object[].class },
369             new Object[] { new int[] { -1 }, 0, new Object[] { "text" } }, IndexOutOfBoundsException.class );
370         assertException( "updateRowData silently ignores an invalid index (2)",
371             m_dataModel, "updateRowData", new Class[] { int[].class, int.class, Object[].class },
372             new Object[] { new int[] { 0 }, -1, new Object[] { "" } }, IndexOutOfBoundsException.class );
373         assertException( "updateRowData silently ignores different-sized arrays",
374             m_dataModel, "updateRowData", new Class[] { int[].class, int.class, Object[].class },
375             new Object[] { new int[] { 0, 0 }, 0, new Object[] { "" } }, IllegalArgumentException.class );
376     }
377 
378     /**
379      * tests the XMutableGridDataModel.updateRowHeading method
380      */
testUpdateRowHeading()381     public void testUpdateRowHeading() throws IndexOutOfBoundsException
382     {
383         assertEquals( "precondition not met: call this directly after testUpdateRowData, please!", m_rowValues.length, m_dataModel.getRowCount() );
384 
385         final Object[] preUpdateHeadings = impl_getCurrentRowHeadings();
386 
387         final int rowToUpdate = 2;
388         final String valueToUpdate = "some text";
389         m_dataModel.updateRowHeading( rowToUpdate, valueToUpdate );
390         final GridDataEvent event = m_listener.assertSingleRowHeadingChangeEvent();
391         assertEquals( "row heading update notification: FirstRow is invalid", rowToUpdate, event.FirstRow );
392         assertEquals( "row heading update notification: FirstRow is invalid", rowToUpdate, event.LastRow );
393         m_listener.reset();
394 
395         preUpdateHeadings[rowToUpdate] = valueToUpdate;
396 
397         final Object[] postUpdateHeadings = impl_getCurrentRowHeadings();
398         assertArrayEquals( preUpdateHeadings, postUpdateHeadings );
399 
400         assertException( "updateRowHeading silently ignores an invalid index",
401             m_dataModel, "updateRowHeading", new Class[] { int.class, Object.class },
402             new Object[] { -1, "" }, IndexOutOfBoundsException.class );
403     }
404 
cleanup()405     public void cleanup()
406     {
407         m_dataModel.removeGridDataListener( m_listener );
408     }
409 
impl_getCurrentData()410     private Object[][] impl_getCurrentData() throws IndexOutOfBoundsException
411     {
412         final int rowCount = m_dataModel.getRowCount();
413         final int colCount = m_dataModel.getColumnCount();
414         final Object[][] data = new Object[rowCount][colCount];
415         for ( int row=0; row<rowCount; ++row )
416         {
417             for ( int col=0; col<colCount; ++col )
418             {
419                 data[row][col] = m_dataModel.getCellData( col, row );
420             }
421         }
422         return data;
423     }
424 
impl_getCurrentRowHeadings()425     private Object[] impl_getCurrentRowHeadings() throws IndexOutOfBoundsException
426     {
427         final int rowCount = m_dataModel.getRowCount();
428         final Object[] headings = new Object[rowCount];
429         for ( int row=0; row<rowCount; ++row )
430             headings[row] = m_dataModel.getRowHeading( row );
431         return headings;
432     }
433 
impl_assertRowData( final int i_rowIndex )434     private void impl_assertRowData( final int i_rowIndex ) throws IndexOutOfBoundsException
435     {
436         for ( int i=0; i<m_rowValues[i_rowIndex].length; ++i )
437         {
438             assertEquals( m_rowValues[i_rowIndex][i], m_dataModel.getCellData( i, i_rowIndex ) );
439         }
440     }
441 
442     private final XMutableGridDataModel m_dataModel;
443     private final GridDataListener      m_listener;
444 
445     private final static Object[][] m_rowValues = new Object[][] {
446             new Object[] { 1, 2, "3", 4, 5 },
447             new Object[] { 2, 3, 4, "5", 6 },
448             new Object[] { "3", 4, 5, 6, 7 },
449             new Object[] { 4, 5, 6, 7, "8" },
450             new Object[] { 5, "6", 7, 8, 9 },
451         };
452 
453     private final static Object[] m_rowHeadings = new Object[] {
454         "1", 2, 3.0, "4", (float)5.0
455     };
456 }
457