From dbbee1728e63397007bdba497c83fcc3f042765c Mon Sep 17 00:00:00 2001 From: Dirk Fauth Date: Mon, 18 Nov 2024 09:29:58 +0100 Subject: [PATCH] Fixes #127 - Avoid possible deadlock situations on updating GlazedLists Signed-off-by: Dirk Fauth --- .../_809_GroupBySummarySummaryRowExample.java | 30 ++-- ...tableGroupByWithComboBoxFilterExample.java | 65 +++++---- ...llFilterPerformanceColumnGroupExample.java | 91 ++++++------ .../glazedlists/GlazedListsLockHelper.java | 137 ++++++++++++++++++ .../GlazedListsRowDeleteCommandHandler.java | 22 +-- .../GlazedListsRowInsertCommandHandler.java | 42 +++--- ...zedListsRowObjectDeleteCommandHandler.java | 28 ++-- .../ComboBoxGlazedListsFilterStrategy.java | 19 +-- ...xGlazedListsWithExcludeFilterStrategy.java | 48 +++--- .../DefaultGlazedListsFilterStrategy.java | 81 +++++------ ...efaultGlazedListsStaticFilterStrategy.java | 57 +++----- .../glazedlists/groupBy/GroupByDataLayer.java | 50 ++++--- .../HierarchicalWrapperSortModel.java | 28 ++-- .../tree/GlazedListTreeRowModel.java | 137 ++++++++++-------- 14 files changed, 476 insertions(+), 359 deletions(-) create mode 100644 org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/GlazedListsLockHelper.java diff --git a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_809_GroupBySummarySummaryRowExample.java b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_809_GroupBySummarySummaryRowExample.java index 369762301..3c790bd60 100644 --- a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_809_GroupBySummarySummaryRowExample.java +++ b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_809_GroupBySummarySummaryRowExample.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013, 2020 Dirk Fauth and others. + * Copyright (c) 2013, 2024 Dirk Fauth and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -38,6 +38,7 @@ import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.DarkGroupByThemeExtension; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.DefaultGroupByThemeExtension; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.GroupByConfigAttributes; @@ -538,19 +539,20 @@ public void widgetSelected(SelectionEvent e) { change.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - bodyLayerStack.getSortedList().getReadWriteLock().writeLock().lock(); - try { - // deactivate - bodyLayerStack.getGlazedListsEventLayer().deactivate(); - // clear - bodyLayerStack.getSortedList().clear(); - // addall - bodyLayerStack.getSortedList().addAll(PersonService.getExtendedPersonsWithAddress(1000)); - } finally { - bodyLayerStack.getSortedList().getReadWriteLock().writeLock().unlock(); - // activate - bodyLayerStack.getGlazedListsEventLayer().activate(); - } + GlazedListsLockHelper.performWriteOperation( + bodyLayerStack.getSortedList().getReadWriteLock(), + () -> { + // deactivate + bodyLayerStack.getGlazedListsEventLayer().deactivate(); + // clear + bodyLayerStack.getSortedList().clear(); + // addall + bodyLayerStack.getSortedList().addAll(PersonService.getExtendedPersonsWithAddress(1000)); + }, + () -> { + // activate + bodyLayerStack.getGlazedListsEventLayer().activate(); + }); } }); diff --git a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_813_SortableGroupByWithComboBoxFilterExample.java b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_813_SortableGroupByWithComboBoxFilterExample.java index 845712cff..9c89092ac 100644 --- a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_813_SortableGroupByWithComboBoxFilterExample.java +++ b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_813_SortableGroupByWithComboBoxFilterExample.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2023 Dirk Fauth and others. + * Copyright (c) 2016, 2024 Dirk Fauth and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -52,6 +52,7 @@ import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.data.command.GlazedListsRowInsertCommandHandler; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.data.command.GlazedListsRowObjectDeleteCommandHandler; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxFilterRowHeaderComposite; @@ -576,36 +577,38 @@ public void widgetSelected(SelectionEvent e) { address.setPostalCode(12345); address.setCity("In the clouds"); - bodyLayerStack.getEventList().getReadWriteLock().writeLock().lock(); - try { - Person person = new Person(42, "Ralph", "Wiggum", Gender.MALE, false, new Date()); - ExtendedPersonWithAddress entry = new ExtendedPersonWithAddress(person, address, - "0000", "The little Ralphy", 0, - new ArrayList(), new ArrayList()); - bodyLayerStack.getEventList().add(entry); - - person = new Person(42, "Clancy", "Wiggum", Gender.MALE, true, new Date()); - entry = new ExtendedPersonWithAddress(person, address, - "XXXL", "It is Chief Wiggum", 0, new ArrayList(), new ArrayList()); - bodyLayerStack.getEventList().add(entry); - - person = new Person(42, "Sarah", "Wiggum", Gender.FEMALE, true, new Date()); - entry = new ExtendedPersonWithAddress(person, address, - "mommy", "Little Ralphy's mother", 0, - new ArrayList(), new ArrayList()); - bodyLayerStack.getEventList().add(entry); - } finally { - bodyLayerStack.getEventList().getReadWriteLock().writeLock().unlock(); - // Inserting new objects could cause the creation of new - // GroupByObjects dependent on the GroupBy state. If - // additionally to the GroupBy state a sorting is applied on - // a column that contain a GroupBy summary value, the - // comparison and therefore the sorting is incorrect as the - // GroupBy summary value cannot be calculated. Therefore the - // sorting is re-applied after the insert operation to have - // a reliable sorting. - sortModel.refresh(); - } + GlazedListsLockHelper.performWriteOperation( + bodyLayerStack.getEventList().getReadWriteLock(), + () -> { + Person person = new Person(42, "Ralph", "Wiggum", Gender.MALE, false, new Date()); + ExtendedPersonWithAddress entry = new ExtendedPersonWithAddress(person, address, + "0000", "The little Ralphy", 0, + new ArrayList(), new ArrayList()); + bodyLayerStack.getEventList().add(entry); + + person = new Person(42, "Clancy", "Wiggum", Gender.MALE, true, new Date()); + entry = new ExtendedPersonWithAddress(person, address, + "XXXL", "It is Chief Wiggum", 0, new ArrayList(), new ArrayList()); + bodyLayerStack.getEventList().add(entry); + + person = new Person(42, "Sarah", "Wiggum", Gender.FEMALE, true, new Date()); + entry = new ExtendedPersonWithAddress(person, address, + "mommy", "Little Ralphy's mother", 0, + new ArrayList(), new ArrayList()); + bodyLayerStack.getEventList().add(entry); + }, + () -> { + // Inserting new objects could cause the creation of + // new GroupByObjects dependent on the GroupBy + // state. If additionally to the GroupBy state a + // sorting is applied on a column that contain a + // GroupBy summary value, the comparison and + // therefore the sorting is incorrect as the GroupBy + // summary value cannot be calculated. Therefore the + // sorting is re-applied after the insert operation + // to have a reliable sorting. + sortModel.refresh(); + }); } }); diff --git a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_818_SortableAllFilterPerformanceColumnGroupExample.java b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_818_SortableAllFilterPerformanceColumnGroupExample.java index 2e6c83f54..ddbcd25b8 100644 --- a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_818_SortableAllFilterPerformanceColumnGroupExample.java +++ b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_800_Integration/_818_SortableAllFilterPerformanceColumnGroupExample.java @@ -67,6 +67,7 @@ import org.eclipse.nebula.widgets.nattable.export.image.config.DefaultImageExportBindings; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxFilterRowHeaderComposite; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxGlazedListsWithExcludeFilterStrategy; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.FilterRowUtils; @@ -643,29 +644,30 @@ public void widgetSelected(SelectionEvent e) { replaceContentButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - bodyLayerStack.getSortedList().getReadWriteLock().writeLock().lock(); - try { - // deactivate - bodyLayerStack.getGlazedListsEventLayer().deactivate(); - columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate(); - - // clear - bodyLayerStack.getSortedList().clear(); - - // addall - if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.compareAndSet(true, false)) { - bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons); - } else { - _818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.set(true); - bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons); - // bodyLayerStack.getSortedList().addAll(PersonService.getPersonsWithAddress(200)); - } - } finally { - bodyLayerStack.getSortedList().getReadWriteLock().writeLock().unlock(); - // activate - bodyLayerStack.getGlazedListsEventLayer().activate(); - columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate(); - } + GlazedListsLockHelper.performWriteOperation( + bodyLayerStack.getSortedList().getReadWriteLock(), + () -> { + // deactivate + bodyLayerStack.getGlazedListsEventLayer().deactivate(); + columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate(); + + // clear + bodyLayerStack.getSortedList().clear(); + + // addall + if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.compareAndSet(true, false)) { + bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons); + } else { + _818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.set(true); + bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons); + // bodyLayerStack.getSortedList().addAll(PersonService.getPersonsWithAddress(200)); + } + }, + () -> { + // activate + bodyLayerStack.getGlazedListsEventLayer().activate(); + columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate(); + }); } }); @@ -674,27 +676,28 @@ public void widgetSelected(SelectionEvent e) { reapplyContentButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - bodyLayerStack.getSortedList().getReadWriteLock().writeLock().lock(); - try { - // deactivate - bodyLayerStack.getGlazedListsEventLayer().deactivate(); - columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate(); - - // clear - bodyLayerStack.getSortedList().clear(); - - // addall - if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.get()) { - bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons); - } else { - bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons); - } - } finally { - bodyLayerStack.getSortedList().getReadWriteLock().writeLock().unlock(); - // activate - bodyLayerStack.getGlazedListsEventLayer().activate(); - columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate(); - } + GlazedListsLockHelper.performWriteOperation( + bodyLayerStack.getSortedList().getReadWriteLock(), + () -> { + // deactivate + bodyLayerStack.getGlazedListsEventLayer().deactivate(); + columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate(); + + // clear + bodyLayerStack.getSortedList().clear(); + + // addall + if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.get()) { + bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons); + } else { + bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons); + } + }, + () -> { + // activate + bodyLayerStack.getGlazedListsEventLayer().activate(); + columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate(); + }); } }); diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/GlazedListsLockHelper.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/GlazedListsLockHelper.java new file mode 100644 index 000000000..f369f4148 --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/GlazedListsLockHelper.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2024 Dirk Fauth and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Fauth - initial API and implementation + *******************************************************************************/ +package org.eclipse.nebula.widgets.nattable.extension.glazedlists; + +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.odell.glazedlists.util.concurrent.Lock; +import ca.odell.glazedlists.util.concurrent.ReadWriteLock; + +/** + * Helper class to perform write operations on a GlazedLists that uses + * {@link Lock#tryLock()} with a waiting time of 1 minute to reduce the risk of + * deadlocks or endless waiting. + * + * @since 2.5 + */ +public final class GlazedListsLockHelper { + + private static final Logger LOG = LoggerFactory.getLogger(GlazedListsLockHelper.class); + + private GlazedListsLockHelper() { + // do nothing + } + + /** + * Try to acquire the write lock for the given {@link Lock} using + * {@link Lock#tryLock()}. Waits for 1 minute to acquire the write lock, if + * not possible fire an {@link IllegalStateException}. + * + * @param writeLock + * The write {@link Lock}. + * @throws IllegalStateException + * if the write lock can not be acquired in 1 minute. + */ + public static void acquireWriteLock(Lock writeLock) { + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis(); + while ((end - start) < 60_000) { + try { + boolean success = writeLock.tryLock(); + + if (success) { + return; + } + + Thread.sleep(50); + } catch (InterruptedException e) { + LOG.debug("thread interrupted while waiting for writeLock", e); //$NON-NLS-1$ + } + + end = System.currentTimeMillis(); + } + + throw new IllegalStateException("Failed to acquire the write lock!"); //$NON-NLS-1$ + } + + /** + * Performs a write operation on a GlazedLists that is wrapped by acquiring + * and releasing the write lock. + * + * @param lock + * The GlazedLists {@link Lock} object to get the write lock + * from. + * @param writeRunnable + * The {@link Runnable} that should be executed between obtaining + * the write lock and releasing it. + */ + public static void performWriteOperation(ReadWriteLock lock, Runnable writeRunnable) { + Lock writeLock = lock.writeLock(); + try { + acquireWriteLock(writeLock); + writeRunnable.run(); + } finally { + writeLock.unlock(); + } + } + + /** + * Performs a write operation on a GlazedLists that is wrapped by acquiring + * and releasing the write lock. + * + * @param lock + * The GlazedLists {@link Lock} object to get the write lock + * from. + * @param writeRunnable + * The {@link Runnable} that should be executed between obtaining + * the write lock and releasing it. + * @param finallyRunnable + * The {@link Runnable} that should be executed after the write + * lock is released. + */ + public static void performWriteOperation(ReadWriteLock lock, Runnable writeRunnable, Runnable finallyRunnable) { + Lock writeLock = lock.writeLock(); + try { + acquireWriteLock(writeLock); + writeRunnable.run(); + } finally { + writeLock.unlock(); + finallyRunnable.run(); + } + } + + /** + * Performs a write operation on a GlazedLists that is wrapped by acquiring + * and releasing the write lock. + * + * @param lock + * The GlazedLists {@link Lock} object to get the write lock + * from. + * @param writeSupplier + * The {@link Supplier} that should be executed between obtaining + * the write lock and releasing it. + * @return The result of the {@link Supplier#get()} method. + */ + public static T performWriteOperation(ReadWriteLock lock, Supplier writeSupplier) { + Lock writeLock = lock.writeLock(); + try { + acquireWriteLock(writeLock); + return writeSupplier.get(); + } finally { + writeLock.unlock(); + } + } +} diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowDeleteCommandHandler.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowDeleteCommandHandler.java index 48e6c1d44..d04896252 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowDeleteCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowDeleteCommandHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Dirk Fauth. + * Copyright (c) 2019, 2024 Dirk Fauth. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -18,6 +18,7 @@ import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler; import org.eclipse.nebula.widgets.nattable.data.command.RowDeleteCommand; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.layer.ILayer; import org.eclipse.nebula.widgets.nattable.layer.event.RowObjectDeleteEvent; @@ -60,16 +61,15 @@ public boolean doCommand(ILayer targetLayer, RowDeleteCommand command) { int[] positions = command.getRowPositionsArray(); Map deleted = new HashMap<>(); - this.bodyData.getReadWriteLock().writeLock().lock(); - try { - for (int i = positions.length - 1; i >= 0; i--) { - // remove the element - int pos = positions[i]; - deleted.put(pos, this.bodyData.remove(pos)); - } - } finally { - this.bodyData.getReadWriteLock().writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.bodyData.getReadWriteLock(), + () -> { + for (int i = positions.length - 1; i >= 0; i--) { + // remove the element + int pos = positions[i]; + deleted.put(pos, this.bodyData.remove(pos)); + } + }); // fire the event to refresh targetLayer.fireLayerEvent(new RowObjectDeleteEvent(targetLayer, deleted)); diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowInsertCommandHandler.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowInsertCommandHandler.java index 2a32dc1af..39c2b6333 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowInsertCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowInsertCommandHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Dirk Fauth. + * Copyright (c) 2019, 2024 Dirk Fauth. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -17,6 +17,7 @@ import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler; import org.eclipse.nebula.widgets.nattable.coordinate.Range; import org.eclipse.nebula.widgets.nattable.data.command.RowInsertCommand; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.layer.ILayer; import org.eclipse.nebula.widgets.nattable.layer.event.RowInsertEvent; @@ -52,28 +53,25 @@ public GlazedListsRowInsertCommandHandler(EventList bodyData) { public boolean doCommand(ILayer targetLayer, RowInsertCommand command) { // convert the transported position to the target layer if (command.convertToTargetLayer(targetLayer)) { - RowInsertEvent event = null; - this.bodyData.getReadWriteLock().writeLock().lock(); - try { - // add the elements - if (command.getRowIndex() < 0 || command.getRowIndex() >= this.bodyData.size()) { - this.bodyData.addAll(command.getObjects()); - // fire the event to refresh - event = new RowInsertEvent( - targetLayer, - new Range(this.bodyData.size() - command.getObjects().size(), this.bodyData.size())); - } else { - this.bodyData.addAll(command.getRowIndex(), command.getObjects()); - event = new RowInsertEvent( - targetLayer, - new Range(command.getRowIndex(), command.getRowIndex() + command.getObjects().size())); - } - } finally { - this.bodyData.getReadWriteLock().writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.bodyData.getReadWriteLock(), + () -> { - // fire the event to refresh - targetLayer.fireLayerEvent(event); + // add the elements + if (command.getRowIndex() < 0 || command.getRowIndex() >= this.bodyData.size()) { + this.bodyData.addAll(command.getObjects()); + // fire the event to refresh + targetLayer.fireLayerEvent(new RowInsertEvent( + targetLayer, + new Range(this.bodyData.size() - command.getObjects().size(), this.bodyData.size()))); + } else { + this.bodyData.addAll(command.getRowIndex(), command.getObjects()); + // fire the event to refresh + targetLayer.fireLayerEvent(new RowInsertEvent( + targetLayer, + new Range(command.getRowIndex(), command.getRowIndex() + command.getObjects().size()))); + } + }); return true; } return false; diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowObjectDeleteCommandHandler.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowObjectDeleteCommandHandler.java index 0e06847d4..3804d09d8 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowObjectDeleteCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/data/command/GlazedListsRowObjectDeleteCommandHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Dirk Fauth. + * Copyright (c) 2019, 2024 Dirk Fauth. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -18,6 +18,7 @@ import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler; import org.eclipse.nebula.widgets.nattable.data.command.RowObjectDeleteCommand; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.layer.ILayer; import org.eclipse.nebula.widgets.nattable.layer.event.RowObjectDeleteEvent; @@ -61,21 +62,20 @@ public boolean doCommand(ILayer targetLayer, RowObjectDeleteCommand command) { // first we need to determine the indexes so we are able to revert the // changes via DataChangeLayer in the correct order again int[] indexes = new int[command.getObjectsToDelete().size()]; - int idx = 0; Map deleted = new TreeMap<>(); - this.bodyData.getReadWriteLock().writeLock().lock(); - try { - for (Object rowObject : command.getObjectsToDelete()) { - int index = this.bodyData.indexOf(rowObject); - deleted.put(index, (T) rowObject); - indexes[idx] = index; - idx++; - } - this.bodyData.removeAll(command.getObjectsToDelete()); - } finally { - this.bodyData.getReadWriteLock().writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.bodyData.getReadWriteLock(), + () -> { + int idx = 0; + for (Object rowObject : command.getObjectsToDelete()) { + int index = this.bodyData.indexOf(rowObject); + deleted.put(index, (T) rowObject); + indexes[idx] = index; + idx++; + } + this.bodyData.removeAll(command.getObjectsToDelete()); + }); // fire the event to refresh targetLayer.fireLayerEvent(new RowObjectDeleteEvent(targetLayer, deleted)); diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java index 577e5f7d5..f19645711 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsFilterStrategy.java @@ -30,6 +30,7 @@ import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter; import org.eclipse.nebula.widgets.nattable.edit.EditConstants; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.filterrow.combobox.ComboBoxFilterUtils; import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider; import org.eclipse.nebula.widgets.nattable.layer.cell.LayerCell; @@ -126,12 +127,9 @@ public ComboBoxGlazedListsFilterStrategy( @Override public void applyFilter(Map filterIndexToObjectMap) { if (filterIndexToObjectMap.isEmpty() && hasComboBoxFilterEditorRegistered()) { - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().add(this.matchNone); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().add(this.matchNone)); return; } @@ -161,12 +159,9 @@ public void applyFilter(Map filterIndexToObjectMap) { if (filterCollection == null || filterCollection.isEmpty()) { // for one column there are no items selected in the combo, // therefore nothing matches - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().add(this.matchNone); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().add(this.matchNone)); return; } else if (filterCollectionsEqual(filterCollection, dataProviderList)) { it.remove(); diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategy.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategy.java index 43198b233..c068ad557 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategy.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/ComboBoxGlazedListsWithExcludeFilterStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Dirk Fauth and others. + * Copyright (c) 2023, 2024 Dirk Fauth and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -18,6 +18,7 @@ import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.filterrow.combobox.FilterRowComboBoxDataProvider; import ca.odell.glazedlists.FilterList; @@ -106,12 +107,9 @@ public void addExcludeFilter(final Matcher matcher) { public void addExcludeFilter(final MatcherEditor matcherEditor) { // add the new MatcherEditor to the CompositeMatcherEditor if (isActive()) { - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().add(matcherEditor); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.compositeMatcherEditor.getMatcherEditors().add(matcherEditor)); } this.excludeMatcherEditor.put(matcherEditor.getMatcher(), matcherEditor); @@ -126,12 +124,9 @@ public void addExcludeFilter(final MatcherEditor matcherEditor) { public void removeExcludeFilter(final Matcher matcher) { MatcherEditor removed = this.excludeMatcherEditor.remove(matcher); if (removed != null && isActive()) { - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().remove(removed); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.compositeMatcherEditor.getMatcherEditors().remove(removed)); } } @@ -151,12 +146,9 @@ public void removeExcludeFilter(final MatcherEditor matcherEditor) { public void clearExcludeFilter() { Collection> excludeMatcher = this.excludeMatcherEditor.values(); if (!excludeMatcher.isEmpty() && isActive()) { - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().removeAll(excludeMatcher); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.compositeMatcherEditor.getMatcherEditors().removeAll(excludeMatcher)); } this.excludeMatcherEditor.clear(); } @@ -166,12 +158,9 @@ public void activateFilterStrategy() { if (!isActive()) { Collection> excludeMatcher = this.excludeMatcherEditor.values(); if (!excludeMatcher.isEmpty()) { - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().addAll(excludeMatcher); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.compositeMatcherEditor.getMatcherEditors().addAll(excludeMatcher)); } } super.activateFilterStrategy(); @@ -182,12 +171,9 @@ public void deactivateFilterStrategy() { if (isActive()) { Collection> excludeMatcher = this.excludeMatcherEditor.values(); if (!excludeMatcher.isEmpty()) { - this.filterLock.writeLock().lock(); - try { - this.compositeMatcherEditor.getMatcherEditors().removeAll(excludeMatcher); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.compositeMatcherEditor.getMatcherEditors().removeAll(excludeMatcher)); } } super.deactivateFilterStrategy(); diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java index 09cfaa58c..e1132016f 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsFilterStrategy.java @@ -28,6 +28,7 @@ import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.filterrow.FilterRowDataLayer; import org.eclipse.nebula.widgets.nattable.filterrow.IFilterStrategy; import org.eclipse.nebula.widgets.nattable.filterrow.ParseResult; @@ -155,12 +156,9 @@ public void applyFilter(Map filterIndexToObjectMap) { if (filterIndexToObjectMap.isEmpty()) { // wait until all listeners had the chance to handle the clear event - try { - this.filterLock.writeLock().lock(); - this.matcherEditor.getMatcherEditors().clear(); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.matcherEditor.getMatcherEditors().clear()); return; } @@ -259,44 +257,43 @@ public void applyFilter(Map filterIndexToObjectMap) { } // wait until all listeners had the chance to handle the clear event - try { - this.filterLock.writeLock().lock(); - - boolean changed = false; - - // Remove the existing matchers that are removed from - // 'filterIndexToObjectMap' - final Iterator> existingMatcherEditors = - this.matcherEditor.getMatcherEditors().iterator(); - while (existingMatcherEditors.hasNext()) { - final MatcherEditor existingMatcherEditor = existingMatcherEditors.next(); - if (!containsMatcherEditor(matcherEditors, existingMatcherEditor)) { - existingMatcherEditors.remove(); - changed = true; - } - } - - // Add the new matchers that are added from - // 'filterIndexToObjectMap' - for (final MatcherEditor me : matcherEditors) { - if (!containsMatcherEditor(this.matcherEditor.getMatcherEditors(), me)) { - this.matcherEditor.getMatcherEditors().add(me); - changed = true; - } - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> { + boolean changed = false; + + // Remove the existing matchers that are removed from + // 'filterIndexToObjectMap' + final Iterator> existingMatcherEditors = + this.matcherEditor.getMatcherEditors().iterator(); + while (existingMatcherEditors.hasNext()) { + final MatcherEditor existingMatcherEditor = existingMatcherEditors.next(); + if (!containsMatcherEditor(matcherEditors, existingMatcherEditor)) { + existingMatcherEditors.remove(); + changed = true; + } + } - // If there was no change to the MatcherEditors but - // applyFilter() was called, probably the re-evaluation of the - // filter was requested. To trigger the re-evaluation we need to - // add a MatcherEditor that matches all. - if (!changed) { - this.matcherEditor.getMatcherEditors().add(this.matchAll); - this.matcherEditor.getMatcherEditors().remove(this.matchAll); - } + // Add the new matchers that are added from + // 'filterIndexToObjectMap' + for (final MatcherEditor me : matcherEditors) { + if (!containsMatcherEditor(this.matcherEditor.getMatcherEditors(), me)) { + this.matcherEditor.getMatcherEditors().add(me); + changed = true; + } + } - } finally { - this.filterLock.writeLock().unlock(); - } + // If there was no change to the MatcherEditors but + // applyFilter() was called, probably the re-evaluation + // of the + // filter was requested. To trigger the re-evaluation we + // need to + // add a MatcherEditor that matches all. + if (!changed) { + this.matcherEditor.getMatcherEditors().add(this.matchAll); + this.matcherEditor.getMatcherEditors().remove(this.matchAll); + } + }); } catch (Exception e) { LOG.error("Error on applying a filter", e); //$NON-NLS-1$ diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsStaticFilterStrategy.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsStaticFilterStrategy.java index b45a85130..ccf3c67cc 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsStaticFilterStrategy.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/filterrow/DefaultGlazedListsStaticFilterStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2023 Original authors and others. + * Copyright (c) 2012, 2024 Original authors and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -18,6 +18,7 @@ import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.filterrow.IActivatableFilterStrategy; import org.eclipse.nebula.widgets.nattable.filterrow.IFilterStrategy; @@ -103,12 +104,9 @@ public DefaultGlazedListsStaticFilterStrategy( public void applyFilter(Map filterIndexToObjectMap) { super.applyFilter(filterIndexToObjectMap); if (this.active) { - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().addAll(this.staticMatcherEditor.values()); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().addAll(this.staticMatcherEditor.values())); } } @@ -135,12 +133,9 @@ public void addStaticFilter(final Matcher matcher) { public void addStaticFilter(final MatcherEditor matcherEditor) { // add the new MatcherEditor to the CompositeMatcherEditor if (isActive()) { - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().add(matcherEditor); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().add(matcherEditor)); } // remember the MatcherEditor so it can be restored after new @@ -157,12 +152,9 @@ public void addStaticFilter(final MatcherEditor matcherEditor) { public void removeStaticFilter(final Matcher matcher) { MatcherEditor removed = this.staticMatcherEditor.remove(matcher); if (removed != null && isActive()) { - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().remove(removed); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().remove(removed)); } } @@ -184,12 +176,9 @@ public void removeStaticFilter(final MatcherEditor matcherEditor) { public void clearStaticFilter() { Collection> staticMatcher = this.staticMatcherEditor.values(); if (!staticMatcher.isEmpty() && isActive()) { - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().removeAll(staticMatcher); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().removeAll(staticMatcher)); } this.staticMatcherEditor.clear(); } @@ -201,12 +190,9 @@ public void activateFilterStrategy() { Collection> staticMatcher = this.staticMatcherEditor.values(); if (!staticMatcher.isEmpty()) { - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().addAll(staticMatcher); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().addAll(staticMatcher)); } } } @@ -218,12 +204,9 @@ public void deactivateFilterStrategy() { Collection> staticMatcher = this.staticMatcherEditor.values(); if (!staticMatcher.isEmpty()) { - this.filterLock.writeLock().lock(); - try { - this.getMatcherEditor().getMatcherEditors().removeAll(staticMatcher); - } finally { - this.filterLock.writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.filterLock, + () -> this.getMatcherEditor().getMatcherEditors().removeAll(staticMatcher)); } } } diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupBy/GroupByDataLayer.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupBy/GroupByDataLayer.java index 6c3bce441..90a6339c9 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupBy/GroupByDataLayer.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupBy/GroupByDataLayer.java @@ -37,6 +37,7 @@ import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.summary.IGroupBySummaryProvider; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeRowModel; @@ -606,30 +607,31 @@ protected void updateTree() { // groupby structure costs time. This is related to dynamically building // a tree structure with additional objects BusyIndicator.showWhile(Display.getDefault(), () -> { - GroupByDataLayer.this.eventList.getReadWriteLock().writeLock().lock(); - try { - /* - * The workaround for the update issue suggested on the mailing - * list iterates over the whole list. This causes a lot of list - * change events, which also cost processing time. Instead we - * are performing a clear()-addAll() which is slightly faster. - */ - EventList temp = GlazedLists.eventList(GroupByDataLayer.this.eventList); - GroupByDataLayer.this.eventList.clear(); - GroupByDataLayer.this.eventList.addAll(temp); - - /* - * Collect the created GroupByObjects and cleanup local caches - */ - if (GroupByDataLayer.this.groupByExpansionModel != null) { - FilterList groupByObjects = new FilterList<>( - GroupByDataLayer.this.treeList, - GroupByDataLayer.this.groupByMatcher); - GroupByDataLayer.this.groupByExpansionModel.cleanupCollapsed(groupByObjects); - } - } finally { - GroupByDataLayer.this.eventList.getReadWriteLock().writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + GroupByDataLayer.this.eventList.getReadWriteLock(), + () -> { + /* + * The workaround for the update issue suggested on the + * mailing list iterates over the whole list. This + * causes a lot of list change events, which also cost + * processing time. Instead we are performing a + * clear()-addAll() which is slightly faster. + */ + EventList temp = GlazedLists.eventList(GroupByDataLayer.this.eventList); + GroupByDataLayer.this.eventList.clear(); + GroupByDataLayer.this.eventList.addAll(temp); + + /* + * Collect the created GroupByObjects and cleanup local + * caches + */ + if (GroupByDataLayer.this.groupByExpansionModel != null) { + FilterList groupByObjects = new FilterList<>( + GroupByDataLayer.this.treeList, + GroupByDataLayer.this.groupByMatcher); + GroupByDataLayer.this.groupByExpansionModel.cleanupCollapsed(groupByObjects); + } + }); }); } diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/hierarchical/HierarchicalWrapperSortModel.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/hierarchical/HierarchicalWrapperSortModel.java index d506780e4..66ac975bc 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/hierarchical/HierarchicalWrapperSortModel.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/hierarchical/HierarchicalWrapperSortModel.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2018, 2020 Dirk Fauth. + * Copyright (c) 2018, 2024 Dirk Fauth. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -24,6 +24,7 @@ import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry; import org.eclipse.nebula.widgets.nattable.config.NullComparator; import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.hierarchical.HierarchicalWrapper; import org.eclipse.nebula.widgets.nattable.hierarchical.HierarchicalWrapperComparator; import org.eclipse.nebula.widgets.nattable.layer.DataLayer; @@ -149,18 +150,19 @@ public void sort(int columnIndex, SortDirectionEnum sortDirection, boolean accum } // perform the sorting - this.sortedList.getReadWriteLock().writeLock().lock(); - try { - if (this.sortingState.isEmpty()) { - // if we do not have a sorting state, we disable sorting - this.sortedList.setComparator(null); - } else { - // we have some sorting state, so we trigger a re-sort - this.sortedList.setComparator(new HierarchicalWrapperComparator(this.columnAccessor, this.levelIndexMapping, this)); - } - } finally { - this.sortedList.getReadWriteLock().writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + this.sortedList.getReadWriteLock(), + () -> { + if (this.sortingState.isEmpty()) { + // if we do not have a sorting state, we disable + // sorting + this.sortedList.setComparator(null); + } else { + // we have some sorting state, so we trigger a + // re-sort + this.sortedList.setComparator(new HierarchicalWrapperComparator(this.columnAccessor, this.levelIndexMapping, this)); + } + }); } } diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/tree/GlazedListTreeRowModel.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/tree/GlazedListTreeRowModel.java index bedd267a6..c0413e1b3 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/tree/GlazedListTreeRowModel.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/tree/GlazedListTreeRowModel.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 Original authors and others. + * Copyright (c) 2012, 2024 Original authors and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -17,6 +17,7 @@ import java.util.Collections; import java.util.List; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper; import org.eclipse.nebula.widgets.nattable.tree.AbstractTreeRowModel; import ca.odell.glazedlists.TreeList; @@ -71,27 +72,28 @@ public List collapse(int index) { @Override public List collapseAll() { TreeList treeList = this.getTreeData().getTreeList(); - treeList.getReadWriteLock().writeLock().lock(); - try { - // iterating directly over the TreeList is a lot faster than - // checking the nodes - // which is related that on collapsing we only need to iterate once - // from bottom to top - for (int i = (treeList.size() - 1); i >= 0; i--) { - /* - * Checks if the node at the given visible index has children - * and is collapsible. If it is it will be collapsed otherwise - * skipped. This backwards searching and collapsing mechanism is - * necessary to ensure to really get every collapsible node in - * the whole tree structure. - */ - if (hasChildren(i) && !isCollapsed(i)) { - treeList.setExpanded(i, false); - } - } - } finally { - treeList.getReadWriteLock().writeLock().unlock(); - } + GlazedListsLockHelper.performWriteOperation( + treeList.getReadWriteLock(), + () -> { + // iterating directly over the TreeList is a lot faster than + // checking the nodes + // which is related that on collapsing we only need to + // iterate once + // from bottom to top + for (int i = (treeList.size() - 1); i >= 0; i--) { + /* + * Checks if the node at the given visible index has + * children and is collapsible. If it is it will be + * collapsed otherwise skipped. This backwards searching + * and collapsing mechanism is necessary to ensure to + * really get every collapsible node in the whole tree + * structure. + */ + if (hasChildren(i) && !isCollapsed(i)) { + treeList.setExpanded(i, false); + } + } + }); notifyListeners(); return new ArrayList<>(); @@ -174,27 +176,30 @@ public List expandAll() { protected void internalExpandAll() { TreeList treeList = this.getTreeData().getTreeList(); - boolean expandPerformed = false; - treeList.getReadWriteLock().writeLock().lock(); - try { - // iterating directly over the TreeList is a lot faster than - // checking the nodes - for (int i = (treeList.size() - 1); i >= 0; i--) { - /* - * Checks if the node at the given visible index has children - * and is expandable. If it is it will be expanded otherwise - * skipped. This backwards searching and expanding mechanism is - * necessary to ensure to really get every expandable node in - * the whole tree structure. - */ - if (hasChildren(i) && isCollapsed(i)) { - treeList.setExpanded(i, true); - expandPerformed = true; - } - } - } finally { - treeList.getReadWriteLock().writeLock().unlock(); - } + boolean expandPerformed = + GlazedListsLockHelper.performWriteOperation( + treeList.getReadWriteLock(), + () -> { + // iterating directly over the TreeList is a lot + // faster than + // checking the nodes + boolean performed = false; + for (int i = (treeList.size() - 1); i >= 0; i--) { + /* + * Checks if the node at the given visible index + * has children and is expandable. If it is it + * will be expanded otherwise skipped. This + * backwards searching and expanding mechanism + * is necessary to ensure to really get every + * expandable node in the whole tree structure. + */ + if (hasChildren(i) && isCollapsed(i)) { + treeList.setExpanded(i, true); + performed = true; + } + } + return performed; + }); // if at least one element was expanded we need to perform the step // again as we are only able to retrieve the visible nodes @@ -226,27 +231,31 @@ public List expandToLevel(int level) { protected void internalExpandToLevel(int level) { TreeList treeList = this.getTreeData().getTreeList(); - boolean expandPerformed = false; - treeList.getReadWriteLock().writeLock().lock(); - try { - // iterating directly over the TreeList is a lot faster than - // checking the nodes - for (int i = (treeList.size() - 1); i >= 0; i--) { - /* - * Checks if the node at the given visible index has children, - * is expandable and is on a level below the given level. If it - * is it will be expanded otherwise skipped. This backwards - * searching and expanding mechanism is necessary to ensure to - * really get every expandable node in the whole tree structure. - */ - if (hasChildren(i) && isCollapsed(i) && treeList.getTreeNode(i).path().size() <= level) { - treeList.setExpanded(i, true); - expandPerformed = true; - } - } - } finally { - treeList.getReadWriteLock().writeLock().unlock(); - } + boolean expandPerformed = + GlazedListsLockHelper.performWriteOperation( + treeList.getReadWriteLock(), + () -> { + // iterating directly over the TreeList is a lot + // faster than + // checking the nodes + boolean performed = false; + for (int i = (treeList.size() - 1); i >= 0; i--) { + /* + * Checks if the node at the given visible index + * has children, is expandable and is on a level + * below the given level. If it is it will be + * expanded otherwise skipped. This backwards + * searching and expanding mechanism is + * necessary to ensure to really get every + * expandable node in the whole tree structure. + */ + if (hasChildren(i) && isCollapsed(i) && treeList.getTreeNode(i).path().size() <= level) { + treeList.setExpanded(i, true); + performed = true; + } + } + return performed; + }); // if at least one element was expanded we need to perform the step // again as we are only able to retrieve the visible nodes