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