diff --git a/src/main/java/com/googlecode/lanterna/gui2/table/PojoTableModel.java b/src/main/java/com/googlecode/lanterna/gui2/table/PojoTableModel.java
new file mode 100644
index 000000000..acda23d0a
--- /dev/null
+++ b/src/main/java/com/googlecode/lanterna/gui2/table/PojoTableModel.java
@@ -0,0 +1,293 @@
+/*
+ * This file is part of lanterna (https://github.com/mabe02/lanterna).
+ *
+ * lanterna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ * Copyright (C) 2010-2024 Martin Berglund
+ */
+package com.googlecode.lanterna.gui2.table;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A {@code PojoTableModel} is an extension of {@link TableModel} that allows rows to be represented
+ * as Plain Old Java Objects (POJOs), following the Java Swing MVC (Model-View-Controller) pattern
+ * more closely.
+ *
+ *
Instead of managing individual column values separately, you can work with your own domain
+ * objects directly. A {@link Function} (row mapper) is provided at construction time to extract
+ * the individual column values from each POJO when the table needs to render them.
+ *
+ * All existing {@link TableModel} methods (e.g. {@code addRow(V... values)},
+ * {@code getRow(int index)}) remain fully functional for backward compatibility. The POJO-based
+ * rows added via {@link #addRow(Object)} are tracked separately and can be retrieved via
+ * {@link #getPojoRow(int)} or {@link #getPojoRows()}.
+ *
+ * Example usage:
+ * {@code
+ * PojoTableModel model = new PojoTableModel<>(
+ * person -> Arrays.asList(person.getName(), person.getSurname(), String.valueOf(person.getAge())),
+ * "Name", "Surname", "Age"
+ * );
+ *
+ * model.addRow(new Person("Tunar", "Bayramov", 25));
+ * model.addRow(new Person("Martin", "Berglund", 40));
+ *
+ * Person first = model.getPojoRow(0); // returns Person("Tunar", ...)
+ * List all = model.getPojoRows(); // returns all POJOs
+ * }
+ *
+ * @param The type of the POJO (row object) stored in this model
+ * @param The type of the individual cell values used for rendering (e.g. {@code String})
+ * @author Contribution for Issue #658
+ */
+public class PojoTableModel extends TableModel {
+
+ /**
+ * Keeps a POJO reference (or {@code null} for legacy rows) for every row stored in the
+ * parent {@link TableModel}. The two lists are always kept in sync:
+ * {@code pojoRows.size() == getRowCount()} at all times.
+ */
+ private final List pojoRows;
+
+ /**
+ * Converts a POJO of type {@code R} into a list of cell values of type {@code V}.
+ * The list must have the same number of elements as there are columns in this model.
+ */
+ private final Function> rowMapper;
+
+ /**
+ * Flag set to {@code true} for the duration of a POJO-initiated {@code insertRow} call so
+ * that the overridden {@link #insertRow(int, java.util.Collection)} knows NOT to add an
+ * extra {@code null} into {@link #pojoRows} (the POJO-path methods handle that themselves).
+ */
+ private boolean insertingPojo = false;
+
+ /**
+ * Creates a new {@code PojoTableModel} with the specified column labels and a row mapper
+ * function that knows how to extract cell values from a POJO.
+ *
+ * @param rowMapper A function that maps a POJO of type {@code R} to a {@link List} of
+ * cell values. The list size must match the number of column labels
+ * provided. Must not be {@code null}.
+ * @param columnLabels Variable-length array of column header labels. At least one label
+ * must be provided.
+ * @throws NullPointerException if {@code rowMapper} is {@code null}
+ * @throws IllegalArgumentException if no column labels are provided
+ */
+ public PojoTableModel(Function> rowMapper, String... columnLabels) {
+ this(rowMapper, Arrays.asList(columnLabels));
+ }
+
+ /**
+ * Creates a new {@code PojoTableModel} with the specified column labels and a row mapper
+ * function that knows how to extract cell values from a POJO.
+ *
+ * @param rowMapper A function that maps a POJO of type {@code R} to a {@link List} of
+ * cell values. The list size must match the number of column labels
+ * provided. Must not be {@code null}.
+ * @param columnLabels List of column header labels. At least one label must be provided.
+ * @throws NullPointerException if {@code rowMapper} is {@code null}
+ * @throws IllegalArgumentException if no column labels are provided
+ */
+ public PojoTableModel(Function> rowMapper, List columnLabels) {
+ super(columnLabels);
+ this.rowMapper = Objects.requireNonNull(rowMapper, "rowMapper must not be null");
+ this.pojoRows = new ArrayList<>();
+ }
+
+ /**
+ * Adds a new row to the table model using a POJO as the data source. The row mapper
+ * provided at construction time is used to extract the individual column values from
+ * the POJO.
+ *
+ * The POJO itself is stored internally and can be retrieved later via
+ * {@link #getPojoRow(int)} or {@link #getPojoRows()}.
+ *
+ * @param pojo The POJO to add as a new row. May be {@code null} if your domain requires it,
+ * but note that the row mapper will be called with {@code null} in that case.
+ * @return Itself, for method chaining
+ */
+ public synchronized PojoTableModel addRow(R pojo) {
+ List columnValues = rowMapper.apply(pojo);
+ insertingPojo = true;
+ try {
+ super.addRow(columnValues);
+ } finally {
+ insertingPojo = false;
+ }
+ pojoRows.add(pojo);
+ return this;
+ }
+
+ /**
+ * Inserts a new POJO-backed row into the table model at a specific index. The row mapper
+ * provided at construction time is used to extract the individual column values.
+ *
+ * @param index Index at which to insert the new row. {@code 0} means first, and
+ * {@link #getRowCount()} appends at the end.
+ * @param pojo The POJO to insert as a new row.
+ * @return Itself, for method chaining
+ */
+ public synchronized PojoTableModel insertRow(int index, R pojo) {
+ List columnValues = rowMapper.apply(pojo);
+ insertingPojo = true;
+ try {
+ super.insertRow(index, columnValues);
+ } finally {
+ insertingPojo = false;
+ }
+ pojoRows.add(index, pojo);
+ return this;
+ }
+
+ /**
+ * Override the Collection-based {@code insertRow} — the single choke-point for ALL
+ * {@link TableModel#addRow} variants — so that legacy (non-POJO) calls also keep
+ * {@link #pojoRows} in sync by inserting a {@code null} placeholder at the same index.
+ *
+ * When a POJO-path method is executing it sets {@link #insertingPojo} to {@code true}
+ * before delegating here, so we know NOT to add a second entry.
+ *
+ * @param index Index at which to insert the new row
+ * @param values Cell values for the new row
+ * @return Itself
+ */
+ @Override
+ public synchronized TableModel insertRow(int index, java.util.Collection values) {
+ super.insertRow(index, values);
+ if (!insertingPojo) {
+ // Legacy call — insert a null placeholder so pojoRows stays in sync
+ pojoRows.add(index, null);
+ }
+ return this;
+ }
+
+ /**
+ * Removes the row at the specified index from the model. If the row was added via
+ * {@link #addRow(Object)}, the corresponding POJO is also removed from the internal
+ * POJO list.
+ *
+ * @param index Index of the row to remove
+ * @return Itself, for method chaining
+ */
+ @Override
+ public synchronized PojoTableModel removeRow(int index) {
+ super.removeRow(index);
+ // pojoRows is always the same size as the parent row list, so the index is always valid
+ pojoRows.remove(index);
+ return this;
+ }
+
+ /**
+ * Removes all rows from the model, including any stored POJO references.
+ *
+ * @return Itself, for method chaining
+ */
+ @Override
+ public synchronized PojoTableModel clear() {
+ super.clear();
+ pojoRows.clear();
+ return this;
+ }
+
+ /**
+ * Returns the POJO stored at the given row index, or {@code null} if the row at that index
+ * was added via the legacy {@link TableModel#addRow(Object[])} / {@link TableModel#addRow(java.util.Collection)}
+ * API (i.e. not POJO-backed).
+ *
+ * @param index The row index to retrieve
+ * @return The POJO at the given index, or {@code null} for non-POJO rows
+ * @throws IndexOutOfBoundsException if {@code index} is out of range
+ */
+ public synchronized R getPojoRow(int index) {
+ if (index < 0 || index >= getRowCount()) {
+ throw new IndexOutOfBoundsException(
+ "Row index " + index + " is out of bounds (row count: " + getRowCount() + ")");
+ }
+ if (index < pojoRows.size()) {
+ return pojoRows.get(index);
+ }
+ return null;
+ }
+
+ /**
+ * Returns an unmodifiable view of all POJOs stored in this model, in insertion order.
+ * Rows that were added via the legacy API are represented as {@code null} entries in the
+ * returned list.
+ *
+ * @return An unmodifiable list of POJOs (may contain {@code null} for non-POJO rows)
+ */
+ public synchronized List getPojoRows() {
+ return Collections.unmodifiableList(new ArrayList<>(pojoRows));
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given POJO in this model, or {@code -1}
+ * if it is not found. Uses {@link Object#equals(Object)} for comparison.
+ *
+ * @param pojo The POJO to search for
+ * @return The row index of the first matching POJO, or {@code -1} if not found
+ */
+ public synchronized int indexOfPojo(R pojo) {
+ return pojoRows.indexOf(pojo);
+ }
+
+ /**
+ * Removes the first row whose backing POJO equals the given object (as determined by
+ * {@link Object#equals(Object)}). Does nothing if the POJO is not found.
+ *
+ * @param pojo The POJO whose row should be removed
+ * @return Itself, for method chaining
+ */
+ public synchronized PojoTableModel removePojoRow(R pojo) {
+ int index = indexOfPojo(pojo);
+ if (index >= 0) {
+ removeRow(index);
+ }
+ return this;
+ }
+
+ /**
+ * Updates the row at the given index by replacing its backing POJO and re-applying the row
+ * mapper to refresh the cell values. This is the POJO equivalent of calling
+ * {@link TableModel#setCell(int, int, Object)} for every column.
+ *
+ * @param index The row index to update
+ * @param pojo The new POJO to store at this row
+ * @return Itself, for method chaining
+ * @throws IndexOutOfBoundsException if {@code index} is out of range
+ */
+ public synchronized PojoTableModel setPojoRow(int index, R pojo) {
+ if (index < 0 || index >= getRowCount()) {
+ throw new IndexOutOfBoundsException(
+ "Row index " + index + " is out of bounds (row count: " + getRowCount() + ")");
+ }
+ List columnValues = rowMapper.apply(pojo);
+ for (int col = 0; col < columnValues.size() && col < getColumnCount(); col++) {
+ setCell(col, index, columnValues.get(col));
+ }
+ // Extend pojoRows list with nulls if needed (for rows added via legacy API)
+ while (pojoRows.size() <= index) {
+ pojoRows.add(null);
+ }
+ pojoRows.set(index, pojo);
+ return this;
+ }
+}
diff --git a/src/test/java/com/googlecode/lanterna/gui2/table/PojoTableModelTest.java b/src/test/java/com/googlecode/lanterna/gui2/table/PojoTableModelTest.java
new file mode 100644
index 000000000..40173fdf2
--- /dev/null
+++ b/src/test/java/com/googlecode/lanterna/gui2/table/PojoTableModelTest.java
@@ -0,0 +1,405 @@
+/*
+ * This file is part of lanterna (https://github.com/mabe02/lanterna).
+ *
+ * lanterna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ * Copyright (C) 2010-2024 Martin Berglund
+ */
+package com.googlecode.lanterna.gui2.table;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link PojoTableModel}, verifying that POJO-based row management works
+ * correctly alongside the existing {@link TableModel} API (backward compatibility).
+ */
+public class PojoTableModelTest {
+
+ // -------------------------------------------------------------------
+ // Simple POJO used across tests
+ // -------------------------------------------------------------------
+
+ /**
+ * A minimal domain object representing a person, used as the row type in these tests.
+ */
+ static class Person {
+ final String name;
+ final String surname;
+ final int age;
+
+ Person(String name, String surname, int age) {
+ this.name = name;
+ this.surname = surname;
+ this.age = age;
+ }
+
+ /** Column values returned by the row mapper: Name, Surname, Age */
+ List toColumns() {
+ return Arrays.asList(name, surname, String.valueOf(age));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Person)) return false;
+ Person p = (Person) o;
+ return age == p.age && Objects.equals(name, p.name) && Objects.equals(surname, p.surname);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, surname, age);
+ }
+
+ @Override
+ public String toString() {
+ return name + " " + surname + " (" + age + ")";
+ }
+ }
+
+ // -------------------------------------------------------------------
+ // Test setup
+ // -------------------------------------------------------------------
+
+ private PojoTableModel model;
+ private final Person tunar = new Person("Tunar", "Bayramov", 25);
+ private final Person martin = new Person("Martin", "Berglund", 40);
+ private final Person alice = new Person("Alice", "Wonderland", 30);
+
+ @Before
+ public void setUp() {
+ model = new PojoTableModel<>(Person::toColumns, "Name", "Surname", "Age");
+ }
+
+ // -------------------------------------------------------------------
+ // Construction
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testConstructorWithVarargs() {
+ PojoTableModel m =
+ new PojoTableModel<>(Person::toColumns, "Name", "Surname", "Age");
+ assertEquals(3, m.getColumnCount());
+ assertEquals(0, m.getRowCount());
+ }
+
+ @Test
+ public void testConstructorWithList() {
+ List labels = Arrays.asList("Name", "Surname", "Age");
+ PojoTableModel m = new PojoTableModel<>(Person::toColumns, labels);
+ assertEquals(3, m.getColumnCount());
+ assertEquals(0, m.getRowCount());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testConstructorNullRowMapperThrows() {
+ new PojoTableModel<>(null, "Name");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructorNoColumnsThrows() {
+ new PojoTableModel<>(Person::toColumns);
+ }
+
+ // -------------------------------------------------------------------
+ // addRow(R pojo)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testAddRowIncrementsRowCount() {
+ assertEquals(0, model.getRowCount());
+ model.addRow(tunar);
+ assertEquals(1, model.getRowCount());
+ model.addRow(martin);
+ assertEquals(2, model.getRowCount());
+ }
+
+ @Test
+ public void testAddRowPopulatesCellValues() {
+ model.addRow(tunar);
+ // Verify that the parent TableModel cells reflect the POJO's fields
+ assertEquals("Tunar", model.getCell(0, 0));
+ assertEquals("Bayramov", model.getCell(1, 0));
+ assertEquals("25", model.getCell(2, 0));
+ }
+
+ @Test
+ public void testAddMultipleRowsPreservesOrder() {
+ model.addRow(tunar);
+ model.addRow(martin);
+ model.addRow(alice);
+
+ assertEquals(tunar, model.getPojoRow(0));
+ assertEquals(martin, model.getPojoRow(1));
+ assertEquals(alice, model.getPojoRow(2));
+ }
+
+ // -------------------------------------------------------------------
+ // getPojoRow(int)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testGetPojoRowReturnsSamePojo() {
+ model.addRow(tunar);
+ assertSame(tunar, model.getPojoRow(0));
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testGetPojoRowOutOfBoundsThrows() {
+ model.addRow(tunar);
+ model.getPojoRow(1); // only row 0 exists
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testGetPojoRowNegativeIndexThrows() {
+ model.getPojoRow(-1);
+ }
+
+ // -------------------------------------------------------------------
+ // getPojoRows()
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testGetPojoRowsReturnsAllInOrder() {
+ model.addRow(tunar);
+ model.addRow(martin);
+ List all = model.getPojoRows();
+ assertEquals(2, all.size());
+ assertEquals(tunar, all.get(0));
+ assertEquals(martin, all.get(1));
+ }
+
+ @Test
+ public void testGetPojoRowsReturnsUnmodifiableCopy() {
+ model.addRow(tunar);
+ List snapshot = model.getPojoRows();
+ try {
+ snapshot.add(martin);
+ fail("Should have thrown UnsupportedOperationException");
+ } catch (UnsupportedOperationException expected) {
+ // correct
+ }
+ // Adding to snapshot must not affect model
+ assertEquals(1, model.getRowCount());
+ }
+
+ @Test
+ public void testGetPojoRowsIsEmptyInitially() {
+ assertTrue(model.getPojoRows().isEmpty());
+ }
+
+ // -------------------------------------------------------------------
+ // insertRow(int, R)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testInsertRowAtBeginning() {
+ model.addRow(martin);
+ model.insertRow(0, tunar);
+
+ assertEquals(2, model.getRowCount());
+ assertEquals(tunar, model.getPojoRow(0));
+ assertEquals(martin, model.getPojoRow(1));
+ assertEquals("Tunar", model.getCell(0, 0));
+ }
+
+ @Test
+ public void testInsertRowInMiddle() {
+ model.addRow(tunar);
+ model.addRow(alice);
+ model.insertRow(1, martin);
+
+ assertEquals(3, model.getRowCount());
+ assertEquals(tunar, model.getPojoRow(0));
+ assertEquals(martin, model.getPojoRow(1));
+ assertEquals(alice, model.getPojoRow(2));
+ }
+
+ // -------------------------------------------------------------------
+ // removeRow(int)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testRemoveRowDecrementsRowCount() {
+ model.addRow(tunar);
+ model.addRow(martin);
+ model.removeRow(0);
+ assertEquals(1, model.getRowCount());
+ assertEquals(martin, model.getPojoRow(0));
+ }
+
+ @Test
+ public void testRemoveRowUpdatesPojoList() {
+ model.addRow(tunar);
+ model.addRow(martin);
+ model.addRow(alice);
+ model.removeRow(1); // remove martin
+
+ assertEquals(2, model.getRowCount());
+ assertEquals(tunar, model.getPojoRow(0));
+ assertEquals(alice, model.getPojoRow(1));
+ }
+
+ // -------------------------------------------------------------------
+ // removePojoRow(R)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testRemovePojoRowByObject() {
+ model.addRow(tunar);
+ model.addRow(martin);
+ model.removePojoRow(tunar);
+
+ assertEquals(1, model.getRowCount());
+ assertEquals(martin, model.getPojoRow(0));
+ }
+
+ @Test
+ public void testRemovePojoRowNotFoundDoesNothing() {
+ model.addRow(tunar);
+ model.removePojoRow(martin); // martin not in model
+ assertEquals(1, model.getRowCount());
+ }
+
+ // -------------------------------------------------------------------
+ // indexOfPojo(R)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testIndexOfPojoFound() {
+ model.addRow(tunar);
+ model.addRow(martin);
+ assertEquals(1, model.indexOfPojo(martin));
+ }
+
+ @Test
+ public void testIndexOfPojoNotFound() {
+ model.addRow(tunar);
+ assertEquals(-1, model.indexOfPojo(alice));
+ }
+
+ // -------------------------------------------------------------------
+ // setPojoRow(int, R)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testSetPojoRowUpdatesCellsAndPojo() {
+ model.addRow(tunar);
+ model.setPojoRow(0, martin);
+
+ assertEquals(martin, model.getPojoRow(0));
+ assertEquals("Martin", model.getCell(0, 0));
+ assertEquals("Berglund", model.getCell(1, 0));
+ assertEquals("40", model.getCell(2, 0));
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testSetPojoRowOutOfBoundsThrows() {
+ model.setPojoRow(0, tunar); // model is empty
+ }
+
+ // -------------------------------------------------------------------
+ // clear()
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testClearRemovesAllRowsAndPojos() {
+ model.addRow(tunar);
+ model.addRow(martin);
+ model.clear();
+
+ assertEquals(0, model.getRowCount());
+ assertTrue(model.getPojoRows().isEmpty());
+ }
+
+ // -------------------------------------------------------------------
+ // Backward compatibility — legacy TableModel API still works
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testLegacyAddRowVarargs() {
+ // Adding rows via the inherited TableModel API should still work
+ model.addRow("Legacy", "User", "99");
+ assertEquals(1, model.getRowCount());
+ assertEquals("Legacy", model.getCell(0, 0));
+ // getPojoRow returns null for non-POJO rows
+ assertNull(model.getPojoRow(0));
+ }
+
+ @Test
+ public void testMixedPojoAndLegacyRows() {
+ model.addRow(tunar); // POJO row at index 0
+ model.addRow("Legacy", "User", "99"); // legacy row at index 1
+ model.addRow(alice); // POJO row at index 2
+
+ assertEquals(3, model.getRowCount());
+ assertEquals(tunar, model.getPojoRow(0));
+ assertNull(model.getPojoRow(1)); // legacy row
+ assertEquals(alice, model.getPojoRow(2));
+
+ // Cell values of the POJO rows are correct
+ assertEquals("Tunar", model.getCell(0, 0));
+ assertEquals("Legacy", model.getCell(0, 1));
+ assertEquals("Alice", model.getCell(0, 2));
+ }
+
+ @Test
+ public void testGetRowStillReturnsListForPojoRow() {
+ model.addRow(tunar);
+ List row = model.getRow(0);
+ assertEquals(Arrays.asList("Tunar", "Bayramov", "25"), row);
+ }
+
+ // -------------------------------------------------------------------
+ // Listener integration (inherited from TableModel)
+ // -------------------------------------------------------------------
+
+ @Test
+ public void testListenerFiredOnAddRow() {
+ final int[] addedIndex = {-1};
+ model.addListener(new TableModel.Listener() {
+ @Override public void onRowAdded(TableModel m, int index) { addedIndex[0] = index; }
+ @Override public void onRowRemoved(TableModel m, int index, List r) {}
+ @Override public void onColumnAdded(TableModel m, int index) {}
+ @Override public void onColumnRemoved(TableModel m, int index, String h, List c) {}
+ @Override public void onCellChanged(TableModel m, int row, int col, String o, String n) {}
+ });
+
+ model.addRow(tunar);
+ assertEquals(0, addedIndex[0]);
+ }
+
+ @Test
+ public void testListenerFiredOnRemoveRow() {
+ model.addRow(tunar);
+ final int[] removedIndex = {-1};
+ model.addListener(new TableModel.Listener() {
+ @Override public void onRowAdded(TableModel m, int index) {}
+ @Override public void onRowRemoved(TableModel m, int index, List r) { removedIndex[0] = index; }
+ @Override public void onColumnAdded(TableModel m, int index) {}
+ @Override public void onColumnRemoved(TableModel m, int index, String h, List c) {}
+ @Override public void onCellChanged(TableModel m, int row, int col, String o, String n) {}
+ });
+
+ model.removeRow(0);
+ assertEquals(0, removedIndex[0]);
+ }
+}