Bug 576615 - Implement Filter editing in JDT with a new custom component

 Refactor TypeFilterPreferencePage and JUnitPreferencePage to use this new component, and adjust the module versions

Change-Id: Ieb3a3e80cbb3ecc6b9eb9949b2aab6a1ec5bb178
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.ui/+/186460
Tested-by: JDT Bot <jdt-bot@eclipse.org>
Tested-by: Jeff Johnston <jjohnstn@redhat.com>
Reviewed-by: Jeff Johnston <jjohnstn@redhat.com>
diff --git a/org.eclipse.jdt.junit.core/META-INF/MANIFEST.MF b/org.eclipse.jdt.junit.core/META-INF/MANIFEST.MF
index 063390e..9e2082d 100644
--- a/org.eclipse.jdt.junit.core/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.junit.core/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jdt.junit.core;singleton:=true
-Bundle-Version: 3.11.100.qualifier
+Bundle-Version: 3.11.200.qualifier
 Bundle-Activator: org.eclipse.jdt.internal.junit.JUnitCorePlugin
 Bundle-ActivationPolicy: lazy
 Bundle-Vendor: %providerName
diff --git a/org.eclipse.jdt.junit.core/pom.xml b/org.eclipse.jdt.junit.core/pom.xml
index 08482db..ba1ab1f 100644
--- a/org.eclipse.jdt.junit.core/pom.xml
+++ b/org.eclipse.jdt.junit.core/pom.xml
@@ -18,6 +18,6 @@
   </parent>
   <groupId>org.eclipse.jdt</groupId>
   <artifactId>org.eclipse.jdt.junit.core</artifactId>
-  <version>3.11.100-SNAPSHOT</version>
+  <version>3.11.200-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitPreferencesConstants.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitPreferencesConstants.java
index 37ec38c..18cffe1 100644
--- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitPreferencesConstants.java
+++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitPreferencesConstants.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2021 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -15,7 +15,6 @@
 package org.eclipse.jdt.internal.junit;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.StringTokenizer;
 
@@ -174,8 +173,8 @@
 	 *
 	 * @return list
 	 */
-	public static List<String> createDefaultStackFiltersList() {
-		return Arrays.asList(fgDefaultFilterPatterns);
+	public static String[] createDefaultStackFiltersList() {
+		return fgDefaultFilterPatterns;
 	}
 
 	/**
diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JunitPreferenceInitializer.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JunitPreferenceInitializer.java
index 45a1f53..f8f1271 100644
--- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JunitPreferenceInitializer.java
+++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JunitPreferenceInitializer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2021 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,8 +14,6 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.junit;
 
-import java.util.List;
-
 import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
 import org.eclipse.core.runtime.preferences.DefaultScope;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -35,8 +33,7 @@
 		prefs.putBoolean(JUnitPreferencesConstants.SHOW_ON_ERROR_ONLY, false);
 		prefs.putBoolean(JUnitPreferencesConstants.ENABLE_ASSERTIONS, JUnitPreferencesConstants.ENABLE_ASSERTIONS_DEFAULT);
 
-		List<String> defaults= JUnitPreferencesConstants.createDefaultStackFiltersList();
-		String[] filters= defaults.toArray(new String[defaults.size()]);
+		String[] filters= JUnitPreferencesConstants.createDefaultStackFiltersList();
 		String active= JUnitPreferencesConstants.serializeList(filters);
 		prefs.put(JUnitPreferencesConstants.PREF_ACTIVE_FILTERS_LIST, active);
 		prefs.put(JUnitPreferencesConstants.PREF_INACTIVE_FILTERS_LIST, ""); //$NON-NLS-1$
diff --git a/org.eclipse.jdt.junit/META-INF/MANIFEST.MF b/org.eclipse.jdt.junit/META-INF/MANIFEST.MF
index e486afe..3572aea 100644
--- a/org.eclipse.jdt.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.junit/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jdt.junit;singleton:=true
-Bundle-Version: 3.13.0.qualifier
+Bundle-Version: 3.14.0.qualifier
 Bundle-Activator: org.eclipse.jdt.internal.junit.ui.JUnitPlugin
 Bundle-ActivationPolicy: lazy
 Bundle-Vendor: %providerName
@@ -28,7 +28,7 @@
  org.eclipse.debug.core;bundle-version="[3.5.0,4.0.0)",
  org.eclipse.debug.ui;bundle-version="[3.5.0,4.0.0)",
  org.eclipse.jdt.core;bundle-version="[3.18.0,4.0.0)",
- org.eclipse.jdt.ui;bundle-version="[3.17.100,4.0.0)",
+ org.eclipse.jdt.ui;bundle-version="[3.26.0,4.0.0)",
  org.eclipse.core.runtime;bundle-version="[3.11.0,4.0.0)",
  org.eclipse.jdt.launching;bundle-version="[3.5.0,4.0.0)",
  org.eclipse.jdt.debug.ui;bundle-version="[3.3.0,4.0.0)",
@@ -37,7 +37,7 @@
  org.eclipse.ltk.core.refactoring;bundle-version="[3.5.0,4.0.0)",
  org.eclipse.core.variables;bundle-version="[3.2.200,4.0.0)",
  org.eclipse.ltk.ui.refactoring;bundle-version="[3.5.0,4.0.0)",
- org.eclipse.jdt.junit.core;bundle-version="[3.6.0,4.0.0)";visibility:=reexport,
+ org.eclipse.jdt.junit.core;bundle-version="[3.11.200,4.0.0)";visibility:=reexport,
  com.ibm.icu;bundle-version="4.4.2",
  org.eclipse.jdt.core.manipulation;bundle-version="1.9.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/org.eclipse.jdt.junit/pom.xml b/org.eclipse.jdt.junit/pom.xml
index b7af641..b774c48 100644
--- a/org.eclipse.jdt.junit/pom.xml
+++ b/org.eclipse.jdt.junit/pom.xml
@@ -18,6 +18,6 @@
   </parent>
   <groupId>org.eclipse.jdt</groupId>
   <artifactId>org.eclipse.jdt.junit</artifactId>
-  <version>3.13.0-SNAPSHOT</version>
+  <version>3.14.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/JUnitPreferencePage.java b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/JUnitPreferencePage.java
index 1d9a8c8..d5ab5ce 100644
--- a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/JUnitPreferencePage.java
+++ b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/JUnitPreferencePage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,77 +14,37 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.junit.ui;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import org.eclipse.osgi.util.TextProcessor;
 
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.TableEditor;
-import org.eclipse.swt.events.FocusAdapter;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.KeyAdapter;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TableItem;
-import org.eclipse.swt.widgets.Text;
 
 import org.eclipse.core.runtime.preferences.DefaultScope;
 import org.eclipse.core.runtime.preferences.InstanceScope;
 
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.preference.PreferencePage;
-import org.eclipse.jface.resource.JFaceResources;
-import org.eclipse.jface.viewers.CheckboxTableViewer;
-import org.eclipse.jface.viewers.ColumnWeightData;
-import org.eclipse.jface.viewers.ContentViewer;
-import org.eclipse.jface.viewers.ILabelProvider;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.ITableLabelProvider;
-import org.eclipse.jface.viewers.LabelProvider;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerComparator;
-import org.eclipse.jface.window.Window;
 
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPreferencePage;
 import org.eclipse.ui.PlatformUI;
-import org.eclipse.ui.dialogs.SelectionDialog;
 import org.eclipse.ui.preferences.ScopedPreferenceStore;
-import org.eclipse.ui.progress.IProgressService;
-
-import org.eclipse.jdt.core.IJavaElement;
-import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jdt.core.search.IJavaSearchScope;
-import org.eclipse.jdt.core.search.SearchEngine;
 
 import org.eclipse.jdt.internal.junit.JUnitCorePlugin;
 import org.eclipse.jdt.internal.junit.JUnitPreferencesConstants;
 import org.eclipse.jdt.internal.junit.launcher.AssertionVMArg;
-import org.eclipse.jdt.internal.junit.util.ExceptionHandler;
-import org.eclipse.jdt.internal.junit.util.LayoutUtil;
 
-import org.eclipse.jdt.ui.IJavaElementSearchConstants;
-import org.eclipse.jdt.ui.ISharedImages;
-import org.eclipse.jdt.ui.JavaUI;
-
+import org.eclipse.jdt.internal.ui.filtertable.FilterManager;
+import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable;
+import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.ButtonLabel;
+import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.DialogLabels;
+import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.FilterTableConfig;
 import org.eclipse.jdt.internal.ui.util.SWTUtil;
-import org.eclipse.jdt.internal.ui.util.TableLayoutComposite;
 
 /**
  * Preference page for JUnit settings. Supports to define the failure
@@ -92,241 +52,38 @@
  */
 public class JUnitPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
 
-	private static final String DEFAULT_NEW_FILTER_TEXT= ""; //$NON-NLS-1$
-	private static final Image IMG_CUNIT= JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_CLASS);
-	private static final Image IMG_PKG= JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_PACKAGE);
+	final static FilterManager FILTER_MANAGER = new FilterManager(JUnitPreferencesConstants.PREF_ACTIVE_FILTERS_LIST, JUnitPreferencesConstants.PREF_INACTIVE_FILTERS_LIST) {
+		@Override
+		protected String[] getDefaultActiveFilters(IPreferenceStore store) {
+			return JUnitPreferencesConstants.createDefaultStackFiltersList();
+		}
 
+		@Override
+		protected String[] getDefaultInactiveFilters(IPreferenceStore store) {
+			return new String[0];
+		}
+	};
+
+	private final JavaFilterTable fJavaFilterTable;
 	private Button fEnableAssertionsCheckBox;
 	private Button fShowInAllViewsCheckBox;
 
-	// Step filter widgets
-	private Label fFilterViewerLabel;
-	private CheckboxTableViewer fFilterViewer;
-	private Table fFilterTable;
-
-	private Button fAddPackageButton;
-	private Button fAddTypeButton;
-	private Button fRemoveFilterButton;
-	private Button fAddFilterButton;
-
-	private Button fEnableAllButton;
-	private Button fDisableAllButton;
-
-	private Text fEditorText;
-	private String fInvalidEditorText= null;
-	private TableEditor fTableEditor;
-	private TableItem fNewTableItem;
-	private Filter fNewStackFilter;
-
-	private StackFilterContentProvider fStackFilterContentProvider;
-
-	/**
-	 * Model object that represents a single entry in the filter table.
-	 */
-	private static class Filter {
-
-		private String fName;
-		private boolean fChecked;
-
-		public Filter(String name, boolean checked) {
-			setName(name);
-			setChecked(checked);
-		}
-
-		public String getName() {
-			return fName;
-		}
-
-		public void setName(String name) {
-			fName= name;
-		}
-
-		public boolean isChecked() {
-			return fChecked;
-		}
-
-		public void setChecked(boolean checked) {
-			fChecked= checked;
-		}
-
-		@Override
-		public boolean equals(Object o) {
-			if (!(o instanceof Filter))
-				return false;
-
-			Filter other= (Filter) o;
-			return (getName().equals(other.getName()));
-		}
-
-		@Override
-		public int hashCode() {
-			return fName.hashCode();
-		}
-	}
-
-	/**
-	 * Sorter for the filter table; sorts alphabetically ascending.
-	 */
-	private static class FilterViewerSorter extends ViewerComparator {
-		@Override
-		public int compare(Viewer viewer, Object e1, Object e2) {
-			ILabelProvider lprov= (ILabelProvider) ((ContentViewer) viewer).getLabelProvider();
-			String name1= lprov.getText(e1);
-			String name2= lprov.getText(e2);
-			if (name1 == null)
-				name1= ""; //$NON-NLS-1$
-
-			if (name2 == null)
-				name2= ""; //$NON-NLS-1$
-
-			if (name1.length() > 0 && name2.length() > 0) {
-				boolean isPackage1= name1.indexOf('*') != -1;
-				boolean isPackage2= name2.indexOf('*') != -1;
-				if (isPackage1 && !isPackage2)
-					return -1;
-
-				if (isPackage2 && !isPackage1)
-					return 1;
-			}
-			return getComparator().compare(name1, name2);
-		}
-	}
-
-	/**
-	 * Label provider for Filter model objects
-	 */
-	private static class FilterLabelProvider extends LabelProvider implements ITableLabelProvider {
-
-		@Override
-		public String getColumnText(Object object, int column) {
-			return (column == 0) ? getText(object) : ""; //$NON-NLS-1$
-		}
-
-		@Override
-		public String getText(Object element) {
-			return TextProcessor.process(((Filter) element).getName());
-		}
-
-		@Override
-		public Image getColumnImage(Object object, int column) {
-			String name= ((Filter) object).getName();
-			if (name.contains(".*") || name.equals(JUnitMessages.JUnitMainTab_label_defaultpackage)) {  //$NON-NLS-1$
-				//package
-				return IMG_PKG;
-			} else if ("".equals(name)) { //$NON-NLS-1$
-				//needed for the in-place editor
-				return null;
-			} else if ((Character.isUpperCase(name.charAt(0))) && (name.indexOf('.') < 0)) {
-				//class in default package
-				return IMG_CUNIT;
-			} else {
-				//fully-qualified class or other filter
-				final int lastDotIndex= name.lastIndexOf('.');
-				if ((-1 != lastDotIndex) && ((name.length() - 1) != lastDotIndex) && Character.isUpperCase(name.charAt(lastDotIndex + 1)))
-					return IMG_CUNIT;
-			}
-			//other filter
-			return null;
-		}
-	}
-
-	/**
-	 * Content provider for the filter table.  Content consists of instances of
-	 * Filter.
-	 */
-	private class StackFilterContentProvider implements IStructuredContentProvider {
-
-		private List<Filter> fFilters;
-
-		public StackFilterContentProvider() {
-			List<String> active= createActiveStackFiltersList();
-			List<String> inactive= createInactiveStackFiltersList();
-			populateFilters(active, inactive);
-		}
-
-		public void setDefaults() {
-			fFilterViewer.remove(fFilters.toArray());
-			List<String> active= JUnitPreferencesConstants.createDefaultStackFiltersList();
-			List<String> inactive= new ArrayList<>();
-			populateFilters(active, inactive);
-		}
-
-		protected void populateFilters(List<String> activeList, List<String> inactiveList) {
-			fFilters= new ArrayList<>(activeList.size() + inactiveList.size());
-			populateList(activeList, true);
-			if (!inactiveList.isEmpty())
-				populateList(inactiveList, false);
-		}
-
-		protected void populateList(List<String> list, boolean checked) {
-			Iterator<String> iterator= list.iterator();
-
-			while (iterator.hasNext()) {
-				String name= iterator.next();
-				addFilter(name, checked);
-			}
-		}
-
-		public Filter addFilter(String name, boolean checked) {
-			Filter filter= new Filter(name, checked);
-			if (!fFilters.contains(filter)) {
-				fFilters.add(filter);
-				fFilterViewer.add(filter);
-				fFilterViewer.setChecked(filter, checked);
-			}
-			updateActions();
-			return filter;
-		}
-
-		public void saveFilters() {
-			List<String> active= new ArrayList<>(fFilters.size());
-			List<String> inactive= new ArrayList<>(fFilters.size());
-			Iterator<Filter> iterator= fFilters.iterator();
-			while (iterator.hasNext()) {
-				Filter filter= iterator.next();
-				String name= filter.getName();
-				if (filter.isChecked())
-					active.add(name);
-				else
-					inactive.add(name);
-			}
-			String pref= JUnitPreferencesConstants.serializeList(active.toArray(new String[active.size()]));
-			getPreferenceStore().setValue(JUnitPreferencesConstants.PREF_ACTIVE_FILTERS_LIST, pref);
-			pref= JUnitPreferencesConstants.serializeList(inactive.toArray(new String[inactive.size()]));
-			getPreferenceStore().setValue(JUnitPreferencesConstants.PREF_INACTIVE_FILTERS_LIST, pref);
-		}
-
-		public void removeFilters(Object[] filters) {
-			for (int i= (filters.length - 1); i >= 0; --i) {
-				Filter filter= (Filter) filters[i];
-				fFilters.remove(filter);
-			}
-			fFilterViewer.remove(filters);
-			updateActions();
-		}
-
-		public void toggleFilter(Filter filter) {
-			boolean newState= !filter.isChecked();
-			filter.setChecked(newState);
-			fFilterViewer.setChecked(filter, newState);
-		}
-
-		@Override
-		public Object[] getElements(Object inputElement) {
-			return fFilters.toArray();
-		}
-
-		@Override
-		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
-		@Override
-		public void dispose() {}
-
-	}
-
 	public JUnitPreferencePage() {
 		super();
 		setDescription(JUnitMessages.JUnitPreferencePage_description);
-		setPreferenceStore(new ScopedPreferenceStore(InstanceScope.INSTANCE,JUnitCorePlugin.CORE_PLUGIN_ID));
+		setPreferenceStore(new ScopedPreferenceStore(InstanceScope.INSTANCE, JUnitCorePlugin.CORE_PLUGIN_ID));
+		fJavaFilterTable = new JavaFilterTable(this, FILTER_MANAGER,
+				new FilterTableConfig()
+					.setLabelText(JUnitMessages.JUnitPreferencePage_filter_label)
+					.setAddFilter(new ButtonLabel(JUnitMessages.JUnitPreferencePage_addfilterbutton_label))
+					.setAddType(new ButtonLabel(JUnitMessages.JUnitPreferencePage_addtypebutton_label))
+					.setAddTypeDialog(new DialogLabels(JUnitMessages.JUnitPreferencePage_addtypedialog_title, JUnitMessages.JUnitPreferencePage_addtypedialog_message))
+					.setAddPackage(new ButtonLabel(JUnitMessages.JUnitPreferencePage_addpackagebutton_label))
+					.setAddPackageDialog(new DialogLabels(JUnitMessages.JUnitPreferencePage_addpackagedialog_title, JUnitMessages.JUnitPreferencePage_addpackagedialog_message))
+					.setRemove(new ButtonLabel(JUnitMessages.JUnitPreferencePage_removefilterbutton_label))
+					.setSelectAll(new ButtonLabel(JUnitMessages.JUnitPreferencePage_enableallbutton_label))
+					.setDeselectAll(new ButtonLabel(JUnitMessages.JUnitPreferencePage_disableallbutton_label))
+					.setHelpContextId(IJUnitHelpContextIds.JUNIT_PREFERENCE_PAGE));
 	}
 
 	@Override
@@ -346,11 +103,24 @@
 
 		createEnableAssertionsCheckbox(composite);
 		createShowInAllViewsCheckbox(composite);
-		createStackFilterPreferences(composite);
+		createJavaFilterTable(composite);
 		Dialog.applyDialogFont(composite);
 		return composite;
 	}
 
+	private void createJavaFilterTable(Composite parent) {
+		Composite container= new Composite(parent, SWT.NONE);
+		GridLayout layout= new GridLayout();
+		layout.numColumns= 2;
+		layout.marginHeight= 0;
+		layout.marginWidth= 0;
+		container.setLayout(layout);
+		GridData gd= new GridData(GridData.FILL_BOTH);
+		container.setLayoutData(gd);
+
+		fJavaFilterTable.createTable(container);
+	}
+
 	private void createEnableAssertionsCheckbox(Composite container) {
 		fEnableAssertionsCheckBox= new Button(container, SWT.CHECK | SWT.WRAP);
 		fEnableAssertionsCheckBox.setText(JUnitMessages.JUnitPreferencePage_enableassertionscheckbox_label);
@@ -389,108 +159,6 @@
 		fShowInAllViewsCheckBox.setSelection(selected);
 	}
 
-	/*
-	 * Create a group to contain the step filter related widgets
-	 */
-	private void createStackFilterPreferences(Composite composite) {
-		fFilterViewerLabel= new Label(composite, SWT.SINGLE | SWT.LEFT);
-		fFilterViewerLabel.setText(JUnitMessages.JUnitPreferencePage_filter_label);
-
-		Composite container= new Composite(composite, SWT.NONE);
-		GridLayout layout= new GridLayout();
-		layout.numColumns= 2;
-		layout.marginHeight= 0;
-		layout.marginWidth= 0;
-		container.setLayout(layout);
-		GridData gd= new GridData(GridData.FILL_BOTH);
-		container.setLayoutData(gd);
-
-		createFilterTable(container);
-		createStepFilterButtons(container);
-	}
-
-	private void createFilterTable(Composite container) {
-		TableLayoutComposite layouter= new TableLayoutComposite(container, SWT.NONE);
-		layouter.addColumnData(new ColumnWeightData(100));
-		layouter.setLayoutData(new GridData(GridData.FILL_BOTH));
-
-		fFilterTable= new Table(layouter, SWT.CHECK | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
-
-		@SuppressWarnings("unused")
-		TableColumn tableColumn= new TableColumn(fFilterTable, SWT.NONE);
-		fFilterViewer= new CheckboxTableViewer(fFilterTable);
-		fTableEditor= new TableEditor(fFilterTable);
-		fFilterViewer.setLabelProvider(new FilterLabelProvider());
-		fFilterViewer.setComparator(new FilterViewerSorter());
-		fStackFilterContentProvider= new StackFilterContentProvider();
-		fFilterViewer.setContentProvider(fStackFilterContentProvider);
-		// input just needs to be non-null
-		fFilterViewer.setInput(this);
-		fFilterViewer.addCheckStateListener(event -> {
-			Filter filter= (Filter) event.getElement();
-			fStackFilterContentProvider.toggleFilter(filter);
-		});
-		fFilterViewer.addSelectionChangedListener(event -> {
-			ISelection selection= event.getSelection();
-			fRemoveFilterButton.setEnabled(!selection.isEmpty());
-		});
-	}
-
-	private void createStepFilterButtons(Composite container) {
-		Composite buttonContainer= new Composite(container, SWT.NONE);
-		GridData gd= new GridData(GridData.FILL_VERTICAL);
-		buttonContainer.setLayoutData(gd);
-		GridLayout buttonLayout= new GridLayout();
-		buttonLayout.numColumns= 1;
-		buttonLayout.marginHeight= 0;
-		buttonLayout.marginWidth= 0;
-		buttonContainer.setLayout(buttonLayout);
-
-		fAddFilterButton= new Button(buttonContainer, SWT.PUSH);
-		fAddFilterButton.setText(JUnitMessages.JUnitPreferencePage_addfilterbutton_label);
-		gd= new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
-		fAddFilterButton.setLayoutData(gd);
-		LayoutUtil.setButtonDimensionHint(fAddFilterButton);
-		fAddFilterButton.addListener(SWT.Selection, event -> editFilter());
-
-		fAddTypeButton= new Button(buttonContainer, SWT.PUSH);
-		fAddTypeButton.setText(JUnitMessages.JUnitPreferencePage_addtypebutton_label);
-		gd= getButtonGridData(fAddTypeButton);
-		fAddTypeButton.setLayoutData(gd);
-		LayoutUtil.setButtonDimensionHint(fAddTypeButton);
-		fAddTypeButton.addListener(SWT.Selection, event -> addType());
-
-		fAddPackageButton= new Button(buttonContainer, SWT.PUSH);
-		fAddPackageButton.setText(JUnitMessages.JUnitPreferencePage_addpackagebutton_label);
-		gd= getButtonGridData(fAddPackageButton);
-		fAddPackageButton.setLayoutData(gd);
-		SWTUtil.setButtonDimensionHint(fAddPackageButton);
-		fAddPackageButton.addListener(SWT.Selection, event -> addPackage());
-
-		fRemoveFilterButton= new Button(buttonContainer, SWT.PUSH);
-		fRemoveFilterButton.setText(JUnitMessages.JUnitPreferencePage_removefilterbutton_label);
-		gd= getButtonGridData(fRemoveFilterButton);
-		fRemoveFilterButton.setLayoutData(gd);
-		SWTUtil.setButtonDimensionHint(fRemoveFilterButton);
-		fRemoveFilterButton.addListener(SWT.Selection, event -> removeFilters());
-		fRemoveFilterButton.setEnabled(false);
-
-		fEnableAllButton= new Button(buttonContainer, SWT.PUSH);
-		fEnableAllButton.setText(JUnitMessages.JUnitPreferencePage_enableallbutton_label);
-		gd= getButtonGridData(fEnableAllButton);
-		fEnableAllButton.setLayoutData(gd);
-		SWTUtil.setButtonDimensionHint(fEnableAllButton);
-		fEnableAllButton.addListener(SWT.Selection, event -> checkAllFilters(true));
-
-		fDisableAllButton= new Button(buttonContainer, SWT.PUSH);
-		fDisableAllButton.setText(JUnitMessages.JUnitPreferencePage_disableallbutton_label);
-		gd= getButtonGridData(fDisableAllButton);
-		fDisableAllButton.setLayoutData(gd);
-		SWTUtil.setButtonDimensionHint(fDisableAllButton);
-		fDisableAllButton.addListener(SWT.Selection, event -> checkAllFilters(false));
-
-	}
-
 	private GridData getButtonGridData(Button button) {
 		GridData gd= new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING);
 		int widthHint= convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
@@ -501,229 +169,11 @@
 	@Override
 	public void init(IWorkbench workbench) {}
 
-	/**
-	 * Create a new filter in the table (with the default 'new filter' value),
-	 * then open up an in-place editor on it.
-	 */
-	private void editFilter() {
-		// if a previous edit is still in progress, finish it
-		if (fEditorText != null)
-			validateChangeAndCleanup();
-
-		fNewStackFilter= fStackFilterContentProvider.addFilter(DEFAULT_NEW_FILTER_TEXT, true);
-		fNewTableItem= fFilterTable.getItem(0);
-		int textStyles= SWT.SINGLE | SWT.LEFT;
-
-		/*
-		 * Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=218193
-		 * which won't get fixed (advice is to not use border). We still use a
-		 * border on platforms where it looks OK and nicer with a border.
-		 */
-		String platform= SWT.getPlatform();
-		if ("win32".equals(platform) || "gtk".equals(platform)) //$NON-NLS-1$ //$NON-NLS-2$
-			textStyles|= SWT.BORDER;
-
-		fEditorText= new Text(fFilterTable, textStyles);
-		fEditorText.setFont(JFaceResources.getDialogFont());
-		GridData gd= new GridData(GridData.FILL_BOTH);
-		fEditorText.setLayoutData(gd);
-
-		// set the editor
-		fTableEditor.horizontalAlignment= SWT.LEFT;
-		fTableEditor.grabHorizontal= true;
-		fTableEditor.setEditor(fEditorText, fNewTableItem, 0);
-
-		// get the editor ready to use
-		fEditorText.setText(fNewStackFilter.getName());
-		fEditorText.selectAll();
-		setEditorListeners(fEditorText);
-		fEditorText.setFocus();
-	}
-
-	private void setEditorListeners(Text text) {
-		// CR means commit the changes, ESC means abort and don't commit
-		text.addKeyListener(new KeyAdapter() {
-			@Override
-			public void keyReleased(KeyEvent event) {
-				if (event.character == SWT.CR) {
-					if (fInvalidEditorText != null) {
-						fEditorText.setText(fInvalidEditorText);
-						fInvalidEditorText= null;
-					} else
-						validateChangeAndCleanup();
-				} else if (event.character == SWT.ESC) {
-					removeNewFilter();
-					cleanupEditor();
-				}
-			}
-		});
-		// Consider loss of focus on the editor to mean the same as CR
-		text.addFocusListener(new FocusAdapter() {
-			@Override
-			public void focusLost(FocusEvent event) {
-				if (fInvalidEditorText != null) {
-					fEditorText.setText(fInvalidEditorText);
-					fInvalidEditorText= null;
-				} else
-					validateChangeAndCleanup();
-			}
-		});
-		// Consume traversal events from the text widget so that CR doesn't
-		// traverse away to dialog's default button.  Without this, hitting
-		// CR in the text field closes the entire dialog.
-		text.addListener(SWT.Traverse, event -> event.doit= false);
-	}
-
-	private void validateChangeAndCleanup() {
-		String trimmedValue= fEditorText.getText().trim();
-		// if the new value is blank, remove the filter
-		if (trimmedValue.length() < 1)
-			removeNewFilter();
-
-		// if it's invalid, beep and leave sitting in the editor
-		else if (!validateEditorInput(trimmedValue)) {
-			fInvalidEditorText= trimmedValue;
-			fEditorText.setText(JUnitMessages.JUnitPreferencePage_invalidstepfilterreturnescape);
-			getShell().getDisplay().beep();
-			return;
-			// otherwise, commit the new value if not a duplicate
-		} else {
-			Object[] filters= fStackFilterContentProvider.getElements(null);
-			for (Object filter2 : filters) {
-				Filter filter= (Filter) filter2;
-				if (filter.getName().equals(trimmedValue)) {
-					removeNewFilter();
-					cleanupEditor();
-					return;
-				}
-			}
-			fNewTableItem.setText(trimmedValue);
-			fNewStackFilter.setName(trimmedValue);
-			fFilterViewer.refresh();
-		}
-		cleanupEditor();
-	}
-
-	/*
-	 * Cleanup all widgets & resources used by the in-place editing
-	 */
-	private void cleanupEditor() {
-		if (fEditorText == null)
-			return;
-
-		fNewStackFilter= null;
-		fNewTableItem= null;
-		fTableEditor.setEditor(null, null, 0);
-		fEditorText.dispose();
-		fEditorText= null;
-	}
-
-	private void removeNewFilter() {
-		fStackFilterContentProvider.removeFilters(new Object[] { fNewStackFilter });
-	}
-
-	/*
-	 * A valid step filter is simply one that is a valid Java identifier.
-	 * and, as defined in the JDI spec, the regular expressions used for
-	 * step filtering must be limited to exact matches or patterns that
-	 * begin with '*' or end with '*'. Beyond this, a string cannot be validated
-	 * as corresponding to an existing type or package (and this is probably not
-	 * even desirable).
-	 */
-	private boolean validateEditorInput(String trimmedValue) {
-		char firstChar= trimmedValue.charAt(0);
-		if ((!(Character.isJavaIdentifierStart(firstChar)) || (firstChar == '*')))
-			return false;
-
-		int length= trimmedValue.length();
-		for (int i= 1; i < length; i++) {
-			char c= trimmedValue.charAt(i);
-			if (!Character.isJavaIdentifierPart(c)) {
-				if (c == '.' && i != (length - 1))
-					continue;
-				if (c == '*' && i == (length - 1))
-					continue;
-
-				return false;
-			}
-		}
-		return true;
-	}
-
-	private void addType() {
-		Shell shell= getShell();
-		SelectionDialog dialog= null;
-		try {
-			dialog=
-				JavaUI.createTypeDialog(
-					shell,
-					PlatformUI.getWorkbench().getProgressService(),
-					SearchEngine.createWorkspaceScope(),
-					IJavaElementSearchConstants.CONSIDER_CLASSES,
-					false);
-		} catch (JavaModelException jme) {
-			String title= JUnitMessages.JUnitPreferencePage_addtypedialog_title;
-			String message= JUnitMessages.JUnitPreferencePage_addtypedialog_error_message;
-			ExceptionHandler.handle(jme, shell, title, message);
-			return;
-		}
-
-		dialog.setTitle(JUnitMessages.JUnitPreferencePage_addtypedialog_title);
-		dialog.setMessage(JUnitMessages.JUnitPreferencePage_addtypedialog_message);
-		if (dialog.open() == IDialogConstants.CANCEL_ID)
-			return;
-
-		Object[] types= dialog.getResult();
-		if (types != null && types.length > 0) {
-			IType type= (IType) types[0];
-			fStackFilterContentProvider.addFilter(type.getFullyQualifiedName('.'), true);
-		}
-	}
-
-	private void addPackage() {
-		Shell shell= getShell();
-		IProgressService context= PlatformUI.getWorkbench().getProgressService();
-		IJavaSearchScope createWorkspaceScope= SearchEngine.createWorkspaceScope();
-		SelectionDialog dialog= JavaUI.createPackageDialog(shell, context, createWorkspaceScope, true, true, ""); //$NON-NLS-1$
-		dialog.setTitle(JUnitMessages.JUnitPreferencePage_addpackagedialog_title);
-		dialog.setMessage(JUnitMessages.JUnitPreferencePage_addpackagedialog_message);
-		if (dialog.open() != Window.OK)
-			return;
-
-		Object[] packages= dialog.getResult();
-		if (packages == null)
-			return;
-
-		for (Object package1 : packages) {
-			IJavaElement pkg= (IJavaElement) package1;
-
-			String filter= pkg.getElementName();
-			if (filter.length() < 1)
-				filter= JUnitMessages.JUnitMainTab_label_defaultpackage;
-			else
-				filter += ".*"; //$NON-NLS-1$
-
-			fStackFilterContentProvider.addFilter(filter, true);
-		}
-	}
-	private void removeFilters() {
-		IStructuredSelection selection= (IStructuredSelection) fFilterViewer.getSelection();
-		fStackFilterContentProvider.removeFilters(selection.toArray());
-	}
-
-	private void checkAllFilters(boolean check) {
-		Object[] filters= fStackFilterContentProvider.getElements(null);
-		for (int i= (filters.length - 1); i >= 0; --i)
-			 ((Filter) filters[i]).setChecked(check);
-
-		fFilterViewer.setAllChecked(check);
-	}
-
 	@Override
 	public boolean performOk() {
 		AssertionVMArg.setEnableAssertionsPreference(getAssertionCheckBoxSelection());
 		JUnitUIPreferencesConstants.setShowInAllViews(getShowInAllViewsCheckBoxSelection());
-		fStackFilterContentProvider.saveFilters();
+		fJavaFilterTable.performOk(getPreferenceStore());
 		return true;
 	}
 
@@ -738,35 +188,7 @@
 				.getBoolean(JUnitPreferencesConstants.ENABLE_ASSERTIONS, JUnitPreferencesConstants.ENABLE_ASSERTIONS_DEFAULT));
 		fShowInAllViewsCheckBox.setSelection(DefaultScope.INSTANCE.getNode(JUnitPlugin.PLUGIN_ID)
 				.getBoolean(JUnitUIPreferencesConstants.SHOW_IN_ALL_VIEWS, JUnitUIPreferencesConstants.SHOW_IN_ALL_VIEWS_DEFAULT));
-		fStackFilterContentProvider.setDefaults();
+		fJavaFilterTable.performDefaults();
 	}
 
-	/**
-	 * Returns a list of active stack filters.
-	 *
-	 * @return list
-	 */
-	protected List<String> createActiveStackFiltersList() {
-		return Arrays.asList(JUnitPreferencesConstants.getFilterPatterns());
-	}
-
-	/**
-	 * Returns a list of active stack filters.
-	 *
-	 * @return list
-	 */
-	protected List<String> createInactiveStackFiltersList() {
-		String[] strings=
-			JUnitPreferencesConstants.parseList(getPreferenceStore().getString(JUnitPreferencesConstants.PREF_INACTIVE_FILTERS_LIST));
-		return Arrays.asList(strings);
-	}
-
-	protected void updateActions() {
-		if (fEnableAllButton == null)
-			return;
-
-		boolean enabled= fFilterViewer.getTable().getItemCount() > 0;
-		fEnableAllButton.setEnabled(enabled);
-		fDisableAllButton.setEnabled(enabled);
-	}
 }
diff --git a/org.eclipse.jdt.ui.unittest.junit.feature/feature.xml b/org.eclipse.jdt.ui.unittest.junit.feature/feature.xml
index f0b1976..76df38a 100644
--- a/org.eclipse.jdt.ui.unittest.junit.feature/feature.xml
+++ b/org.eclipse.jdt.ui.unittest.junit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jdt.ui.unittest.junit.feature"
       label="%featureName"
-      version="1.0.100.qualifier"
+      version="1.0.200.qualifier"
       provider-name="%provider"
       license-feature="org.eclipse.license"
       license-feature-version="0.0.0">
diff --git a/org.eclipse.jdt.ui.unittest.junit.feature/pom.xml b/org.eclipse.jdt.ui.unittest.junit.feature/pom.xml
index dab5bba..2c71a7c 100644
--- a/org.eclipse.jdt.ui.unittest.junit.feature/pom.xml
+++ b/org.eclipse.jdt.ui.unittest.junit.feature/pom.xml
@@ -20,7 +20,7 @@
   </parent>
   <groupId>org.eclipse.jdt.feature</groupId>
   <artifactId>org.eclipse.jdt.ui.unittest.junit.feature</artifactId>
-  <version>1.0.100-SNAPSHOT</version>
+  <version>1.0.200-SNAPSHOT</version>
   <packaging>eclipse-feature</packaging>
   <build>
      <plugins>
diff --git a/org.eclipse.jdt.ui.unittest.junit/META-INF/MANIFEST.MF b/org.eclipse.jdt.ui.unittest.junit/META-INF/MANIFEST.MF
index 11ec513..fe83769 100644
--- a/org.eclipse.jdt.ui.unittest.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.ui.unittest.junit/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jdt.ui.unittest.junit;singleton:=true
-Bundle-Version: 1.0.200.qualifier
+Bundle-Version: 1.0.300.qualifier
 Bundle-Activator: org.eclipse.jdt.ui.unittest.junit.JUnitTestPlugin
 Bundle-ActivationPolicy: lazy
 Bundle-Vendor: %providerName
@@ -22,9 +22,9 @@
  org.eclipse.core.runtime;bundle-version="[3.11.0,4.0.0)",
  org.eclipse.jdt.launching;bundle-version="[3.5.0,4.0.0)",
  org.eclipse.jdt.debug.ui;bundle-version="[3.3.0,4.0.0)",
- org.eclipse.jdt.junit.core;bundle-version="[3.10.800,4.0.0]",
+ org.eclipse.jdt.junit.core;bundle-version="[3.11.200,4.0.0]",
  org.eclipse.jdt.junit.runtime;bundle-version="[3.5.0,4.0.0)",
  org.eclipse.core.variables;bundle-version="[3.2.200,4.0.0)",
  org.eclipse.jdt.core.manipulation;bundle-version="1.9.0",
- org.eclipse.jdt.junit;bundle-version="3.11.0"
+ org.eclipse.jdt.junit;bundle-version="3.14.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/org.eclipse.jdt.ui.unittest.junit/pom.xml b/org.eclipse.jdt.ui.unittest.junit/pom.xml
index a2dad83..f0c4a57 100644
--- a/org.eclipse.jdt.ui.unittest.junit/pom.xml
+++ b/org.eclipse.jdt.ui.unittest.junit/pom.xml
@@ -18,7 +18,7 @@
   </parent>
   <groupId>org.eclipse.jdt</groupId>
   <artifactId>org.eclipse.jdt.ui.unittest.junit</artifactId>
-  <version>1.0.200-SNAPSHOT</version>
+  <version>1.0.300-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
   <properties>
       <skipAPIAnalysis>true</skipAPIAnalysis>
diff --git a/org.eclipse.jdt.ui/META-INF/MANIFEST.MF b/org.eclipse.jdt.ui/META-INF/MANIFEST.MF
index 6ffb6dd..375f409 100644
--- a/org.eclipse.jdt.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.ui/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jdt.ui; singleton:=true
-Bundle-Version: 3.25.100.qualifier
+Bundle-Version: 3.26.0.qualifier
 Bundle-Activator: org.eclipse.jdt.internal.ui.JavaPlugin
 Bundle-ActivationPolicy: lazy
 Bundle-Vendor: %providerName
@@ -45,6 +45,7 @@
  org.eclipse.jdt.internal.ui.dialogs;x-friends:="org.eclipse.jdt.apt.ui,org.eclipse.jdt.junit",
  org.eclipse.jdt.internal.ui.dnd;x-internal:=true,
  org.eclipse.jdt.internal.ui.filters;x-internal:=true,
+ org.eclipse.jdt.internal.ui.filtertable;x-friends:="org.eclipse.jdt.junit,org.eclipse.jdt.debug.ui",
  org.eclipse.jdt.internal.ui.fix;x-internal:=true,
  org.eclipse.jdt.internal.ui.infoviews;x-internal:=true,
  org.eclipse.jdt.internal.ui.jarimport;x-internal:=true,
@@ -133,7 +134,7 @@
  org.eclipse.ui.forms;bundle-version="[3.4.0,4.0.0)",
  org.eclipse.ui.navigator;bundle-version="[3.3.200,4.0.0)",
  org.eclipse.ui.navigator.resources;bundle-version="[3.4.0,4.0.0)",
- org.eclipse.jdt.core.manipulation;bundle-version="[1.15.100,2.0.0)",
+ org.eclipse.jdt.core.manipulation;bundle-version="[1.15.200,2.0.0)",
  com.ibm.icu;bundle-version="4.4.2",
  org.eclipse.equinox.bidi;bundle-version="[0.10.0,2.0.0)"
 Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/org.eclipse.jdt.ui/pom.xml b/org.eclipse.jdt.ui/pom.xml
index 15a3a7c..b301ba2 100644
--- a/org.eclipse.jdt.ui/pom.xml
+++ b/org.eclipse.jdt.ui/pom.xml
@@ -18,7 +18,7 @@
   </parent>
   <groupId>org.eclipse.jdt</groupId>
   <artifactId>org.eclipse.jdt.ui</artifactId>
-  <version>3.25.100-SNAPSHOT</version>
+  <version>3.26.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
   <properties>
     <code.ignoredWarnings>-warn:-deprecation,unavoidableGenericProblems</code.ignoredWarnings>
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/Filter.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/Filter.java
new file mode 100644
index 0000000..be8c4ef
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/Filter.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Note:
+ *     Moved from org.eclipse.jdt.internal.debug.ui from the eclipse.jdt.debug project
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.filtertable;
+
+
+/**
+ * Model object that represents a single entry in a filter table.
+ * @since 3.26
+ */
+public class Filter {
+
+	private String fName;
+	private boolean fChecked;
+
+	public Filter(String name, boolean checked) {
+		setName(name);
+		setChecked(checked);
+	}
+
+	public String getName() {
+		return fName;
+	}
+
+	public void setName(String name) {
+		fName = name;
+	}
+
+	public boolean isChecked() {
+		return fChecked;
+	}
+
+	public void setChecked(boolean checked) {
+		fChecked = checked;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof Filter) {
+			Filter other = (Filter) o;
+			if (getName().equals(other.getName())) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public int hashCode() {
+		return getName().hashCode();
+	}
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterLabelProvider.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterLabelProvider.java
new file mode 100644
index 0000000..59f93c1
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterLabelProvider.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Note:
+ *     The code moved from org.eclipse.jdt.internal.debug.ui from the eclipse.jdt.debug project
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.filtertable;
+
+
+import org.eclipse.swt.graphics.Image;
+
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+
+import org.eclipse.jdt.ui.ISharedImages;
+import org.eclipse.jdt.ui.JavaUI;
+
+/**
+ * Label provider for Filter model objects
+ * @since 3.26
+ */
+public class FilterLabelProvider extends LabelProvider implements ITableLabelProvider {
+
+	private static final Image IMG_CUNIT =
+		JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_CLASS);
+	private static final Image IMG_PKG =
+		JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_PACKAGE);
+
+	/**
+	 * @see ITableLabelProvider#getColumnText(Object, int)
+	 */
+	@Override
+	public String getColumnText(Object object, int column) {
+		if (column == 0) {
+			return ((Filter) object).getName();
+		}
+		return ""; //$NON-NLS-1$
+	}
+
+	/**
+	 * @see ILabelProvider#getText(Object)
+	 */
+	@Override
+	public String getText(Object element) {
+		return ((Filter) element).getName();
+	}
+
+	/**
+	 * @see ITableLabelProvider#getColumnImage(Object, int)
+	 */
+	@Override
+	public Image getColumnImage(Object object, int column) {
+		String name = ((Filter) object).getName();
+		if (name.endsWith("*") || name.equals("(default package)")) { //$NON-NLS-1$ //$NON-NLS-2$
+			return IMG_PKG;
+		}
+		return IMG_CUNIT;
+	}
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterManager.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterManager.java
new file mode 100644
index 0000000..65aae24
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterManager.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Zsombor Gegesy.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Zsombor Gegesy - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.filtertable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+
+/**
+ * Helper class to manage converting between active/inactive Filter lists into preference settings.
+ * @since 3.26
+ */
+public class FilterManager {
+
+	private final String activeListKey;
+	private final String inactiveListKey;
+	private final String separatorString;
+
+	public FilterManager(String activeListKey, String inactiveListKey, String separatorString) {
+		this.activeListKey = activeListKey;
+		this.inactiveListKey = inactiveListKey;
+		this.separatorString = separatorString;
+	}
+
+	public FilterManager(String activeListKey, String inactiveListKey) {
+		this(activeListKey, inactiveListKey, ","); //$NON-NLS-1$
+	}
+
+	public String[] getActiveList(IPreferenceStore store) {
+		return parseList(store.getString(activeListKey));
+	}
+
+	public String[] getInactiveList(IPreferenceStore store) {
+		return parseList(store.getString(inactiveListKey));
+	}
+
+	public Filter[] getAllStoredFilters(IPreferenceStore store, boolean defaults) {
+		Filter[] filters = null;
+		String[] activefilters, inactivefilters;
+		if (defaults) {
+			activefilters = parseList(store.getDefaultString(activeListKey));
+			inactivefilters = parseList(store.getDefaultString(inactiveListKey));
+		} else {
+			activefilters = getDefaultActiveFilters(store);
+			inactivefilters = getDefaultInactiveFilters(store);
+		}
+		filters = new Filter[activefilters.length + inactivefilters.length];
+		for (int i = 0; i < activefilters.length; i++) {
+			filters[i] = new Filter(activefilters[i], true);
+		}
+		for (int i = 0; i < inactivefilters.length; i++) {
+			filters[i + activefilters.length] = new Filter(inactivefilters[i], false);
+		}
+		return filters;
+	}
+
+	protected String[] getDefaultActiveFilters(IPreferenceStore store) {
+		return parseList(store.getDefaultString(activeListKey));
+	}
+
+	protected String[] getDefaultInactiveFilters(IPreferenceStore store) {
+		return parseList(store.getDefaultString(inactiveListKey));
+	}
+
+	public void save(IPreferenceStore store, Filter[] filters) {
+		ArrayList<String> active = new ArrayList<>();
+		ArrayList<String> inactive = new ArrayList<>();
+		String name = ""; //$NON-NLS-1$
+		for (int i = 0; i < filters.length; i++) {
+			name = filters[i].getName();
+			if (filters[i].isChecked()) {
+				active.add(name);
+			} else {
+				inactive.add(name);
+			}
+		}
+		String pref = serializeList(active.toArray(new String[active.size()]));
+		store.setValue(activeListKey, pref);
+		pref = serializeList(inactive.toArray(new String[inactive.size()]));
+		store.setValue(inactiveListKey, pref);
+	}
+
+
+	/**
+	 * Parses the comma separated string into an array of strings
+	 *
+	 * @param listString the comma separated string
+	 * @return list
+	 */
+	public String[] parseList(String listString) {
+		List<String> list = new ArrayList<>(10);
+		StringTokenizer tokenizer = new StringTokenizer(listString, separatorString);
+		while (tokenizer.hasMoreTokens()) {
+			String token = tokenizer.nextToken();
+			list.add(token);
+		}
+		return list.toArray(new String[list.size()]);
+	}
+
+	/**
+	 * Serializes the array of strings into one comma
+	 * separated string.
+	 *
+	 * @param list array of strings
+	 * @return a single string composed of the given list
+	 */
+	public String serializeList(String[] list) {
+		if (list == null) {
+			return ""; //$NON-NLS-1$
+		}
+		return String.join(separatorString, list);
+	}
+
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterViewerComparator.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterViewerComparator.java
new file mode 100644
index 0000000..a1d4acb
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/FilterViewerComparator.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Note:
+ *     Moved from org.eclipse.jdt.internal.debug.ui from the eclipse.jdt.debug project
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.filtertable;
+
+import org.eclipse.jface.viewers.ContentViewer;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+import org.eclipse.ui.model.WorkbenchViewerComparator;
+
+/**
+ * Comparator for {@link Filter} objects.
+ * @since 3.26
+ *
+ */
+public class FilterViewerComparator extends WorkbenchViewerComparator {
+	@Override
+	public int compare(Viewer viewer, Object e1, Object e2) {
+		ILabelProvider lprov =
+			(ILabelProvider) ((ContentViewer) viewer).getLabelProvider();
+		String name1 = lprov.getText(e1);
+		String name2 = lprov.getText(e2);
+		if (name1 == null) {
+			name1 = ""; //$NON-NLS-1$
+		}
+		if (name2 == null) {
+			name2 = ""; //$NON-NLS-1$
+		}
+		if (name1.length() > 0 && name2.length() > 0) {
+			char char1 = name1.charAt(name1.length() - 1);
+			char char2 = name2.charAt(name2.length() - 1);
+			if (char1 == '*' && char1 != char2) {
+				return -1;
+			}
+			if (char2 == '*' && char2 != char1) {
+				return 1;
+			}
+		}
+		return name1.compareTo(name2);
+	}
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/JavaFilterTable.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/JavaFilterTable.java
new file mode 100644
index 0000000..0acd6bc
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/JavaFilterTable.java
@@ -0,0 +1,630 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Jesper Steen Moller - Enhancement 254677 - filter getters/setters
+ *     Zsombor Gegesy - creating a standalone component.
+ * Note:
+ *     The code was extracted from org.eclipse.jdt.internal.debug.ui.JavaStepFilterPreferencePage
+ *     from the eclipse.jdt.debug project.
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.filtertable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.TableItem;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.SelectionDialog;
+
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchEngine;
+
+import org.eclipse.jdt.ui.IJavaElementSearchConstants;
+import org.eclipse.jdt.ui.JavaUI;
+
+import org.eclipse.jdt.internal.ui.dialogs.PackageSelectionDialog;
+import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
+
+/**
+ * Component to manage a list of Java filters, where a filter can be a Java class name, or a package
+ * name, and every filter can be active or inactive, and they are stored in the preference store.
+ *
+ * @since 3.26
+ */
+public class JavaFilterTable {
+	/**
+	 * Content provider for the table. Content consists of instances of Filter.
+	 *
+	 * @since 3.26
+	 */
+	class FilterContentProvider implements IStructuredContentProvider {
+		public FilterContentProvider() {
+			initTableState(false);
+		}
+
+		@Override
+		public Object[] getElements(Object inputElement) {
+			return getAllFiltersFromTable();
+		}
+	}
+
+	/**
+	 * Interface for encapsulating the storage of the {@link Filter}s
+	 *
+	 * @since 3.26
+	 *
+	 */
+	public interface FilterStorage {
+		/**
+		 * Returns all of the stored filters
+		 *
+		 * @param defaults if the defaults should be returned
+		 * @return an array of stored filters
+		 */
+		Filter[] getStoredFilters(boolean defaults);
+
+		/**
+		 * Saves the filters in a persistent storage.
+		 *
+		 * @param store the current preference store, or null if not applicable.
+		 * @param filters an array of filters, what the user is configured
+		 */
+		void setStoredFilters(IPreferenceStore store, Filter[] filters);
+
+	}
+
+	public static class ButtonLabel {
+		final String label;
+
+		final String tooltip;
+
+		public ButtonLabel(String label, String tooltip) {
+			this.label= label;
+			this.tooltip= tooltip;
+		}
+
+		public ButtonLabel(String label) {
+			this(label, null);
+		}
+	}
+
+	public static class DialogLabels {
+		final String title;
+
+		final String message;
+
+		public DialogLabels(String title, String message) {
+			this.title= title;
+			this.message= message;
+		}
+	}
+
+	public static class FilterTableConfig {
+		String labelText;
+
+		ButtonLabel addFilter;
+
+		ButtonLabel addType;
+
+		ButtonLabel addPackage;
+
+		ButtonLabel editFilter;
+
+		ButtonLabel remove;
+
+		ButtonLabel selectAll;
+
+		ButtonLabel deselectAll;
+
+		DialogLabels addTypeDialog;
+
+		DialogLabels errorAddTypeDialog;
+
+		DialogLabels addPackageDialog;
+
+		String helpContextId;
+
+		boolean showDefaultPackage;
+		boolean showParents;
+		boolean considerAllTypes;
+		boolean checkable = true;
+
+		public FilterTableConfig setLabelText(String labelText) {
+			this.labelText= labelText;
+			return this;
+		}
+
+		public FilterTableConfig setAddFilter(ButtonLabel buttonLabel) {
+			this.addFilter= buttonLabel;
+			return this;
+		}
+
+		public FilterTableConfig setAddType(ButtonLabel buttonLabel) {
+			this.addType= buttonLabel;
+			return this;
+		}
+
+		public FilterTableConfig setEditFilter(ButtonLabel buttonLabel) {
+			this.editFilter= buttonLabel;
+			return this;
+		}
+
+		public FilterTableConfig setAddPackage(ButtonLabel buttonLabel) {
+			this.addPackage= buttonLabel;
+			return this;
+		}
+
+		public FilterTableConfig setRemove(ButtonLabel buttonLabel) {
+			this.remove= buttonLabel;
+			return this;
+		}
+
+		public FilterTableConfig setSelectAll(ButtonLabel buttonLabel) {
+			this.selectAll= buttonLabel;
+			return this;
+		}
+
+		public FilterTableConfig setDeselectAll(ButtonLabel buttonLabel) {
+			this.deselectAll= buttonLabel;
+			return this;
+		}
+
+		public FilterTableConfig setAddTypeDialog(DialogLabels dialog) {
+			this.addTypeDialog= dialog;
+			return this;
+		}
+
+		public FilterTableConfig setErrorAddTypeDialog(DialogLabels dialog) {
+			this.errorAddTypeDialog= dialog;
+			return this;
+		}
+
+		public FilterTableConfig setAddPackageDialog(DialogLabels dialog) {
+			this.addPackageDialog= dialog;
+			return this;
+		}
+
+		public FilterTableConfig setHelpContextId(String helpContextId) {
+			this.helpContextId= helpContextId;
+			return this;
+		}
+
+		public FilterTableConfig setShowDefaultPackage(boolean showDefaultPackage) {
+			this.showDefaultPackage= showDefaultPackage;
+			return this;
+		}
+
+		public FilterTableConfig setShowParents(boolean showParents) {
+			this.showParents= showParents;
+			return this;
+		}
+
+		public FilterTableConfig setConsiderAllTypes(boolean considerAllTypes) {
+			this.considerAllTypes= considerAllTypes;
+			return this;
+		}
+
+		public FilterTableConfig setCheckable(boolean checkable) {
+			this.checkable= checkable;
+			return this;
+		}
+	}
+
+	private final FilterStorage fFilterStorage;
+
+	private final FilterTableConfig config;
+
+	private TableViewer fTableViewer;
+
+	private Button fAddPackageButton;
+
+	private Button fAddTypeButton;
+
+	private Button fRemoveFilterButton;
+
+	private Button fAddFilterButton;
+
+	private Button fEditFilterButton;
+
+	private Button fSelectAllButton;
+
+	private Button fDeselectAllButton;
+
+	public JavaFilterTable(PreferencePage preferencePage, FilterManager filterManager, FilterTableConfig config) {
+		this(new FilterStorage() {
+			@Override
+			public Filter[] getStoredFilters(boolean defaults) {
+				return filterManager.getAllStoredFilters(preferencePage.getPreferenceStore(), defaults);
+			}
+
+			@Override
+			public void setStoredFilters(IPreferenceStore store, Filter[] filters) {
+				filterManager.save(store, filters);
+			}
+		}, config);
+		Objects.requireNonNull(filterManager);
+	}
+
+	public JavaFilterTable(FilterStorage filterStorage, FilterTableConfig config) {
+		this.fFilterStorage= Objects.requireNonNull(filterStorage);
+		this.config= config != null ? config : new FilterTableConfig();
+	}
+
+	public void createTable(Composite container) {
+		if (config.labelText != null) {
+			SWTFactory.createLabel(container, config.labelText, 2);
+		}
+		createFilterTableViewer(container);
+		createFilterButtons(container);
+	}
+
+	private void createFilterTableViewer(Composite container) {
+		if (config.checkable) {
+			fTableViewer= CheckboxTableViewer.newCheckList(container, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
+		} else {
+			fTableViewer= new TableViewer(container, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
+		}
+		fTableViewer.getTable().setFont(container.getFont());
+		fTableViewer.setLabelProvider(new FilterLabelProvider());
+		fTableViewer.setComparator(new FilterViewerComparator());
+		fTableViewer.setContentProvider(new FilterContentProvider());
+		fTableViewer.setInput(getAllStoredFilters(false));
+		fTableViewer.getTable().setLayoutData(new GridData(GridData.FILL_BOTH));
+		if (fTableViewer instanceof CheckboxTableViewer) {
+			CheckboxTableViewer checkableViewer = (CheckboxTableViewer) fTableViewer;
+			checkableViewer.addCheckStateListener(new ICheckStateListener() {
+				@Override
+				public void checkStateChanged(CheckStateChangedEvent event) {
+					((Filter) event.getElement()).setChecked(event.getChecked());
+				}
+			});
+		}
+		fTableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+			@Override
+			public void selectionChanged(SelectionChangedEvent event) {
+				boolean hasSelection= !event.getSelection().isEmpty();
+				setEnabled(fRemoveFilterButton, hasSelection);
+				setEnabled(fEditFilterButton, hasSelection);
+			}
+		});
+		fTableViewer.getControl().addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent event) {
+				handleFilterViewerKeyPress(event);
+			}
+		});
+	}
+
+
+	/**
+	 * Creates the add/remove/etc buttons for the filters
+	 *
+	 * @param container the parent container
+	 */
+	private void createFilterButtons(Composite container) {
+		// button container
+		Composite buttonContainer= new Composite(container, SWT.NONE);
+		GridData gd= new GridData(GridData.FILL_VERTICAL);
+		buttonContainer.setLayoutData(gd);
+		GridLayout buttonLayout= new GridLayout();
+		buttonLayout.numColumns= 1;
+		buttonLayout.marginHeight= 0;
+		buttonLayout.marginWidth= 0;
+		buttonContainer.setLayout(buttonLayout);
+
+		//Add filter button
+		fAddFilterButton= createPushButton(buttonContainer, config.addFilter, event -> addFilter());
+		//Add type button
+		fAddTypeButton= createPushButton(buttonContainer, config.addType, event -> addType());
+		//Add package button
+		fAddPackageButton= createPushButton(buttonContainer, config.addPackage, event -> addPackage());
+		//Add edit button
+		fEditFilterButton= createPushButton(buttonContainer, config.editFilter, event -> editFilter(), false);
+		//Remove button
+		fRemoveFilterButton= createPushButton(buttonContainer, config.remove, event -> removeFilters(), false);
+
+		Label separator= new Label(buttonContainer, SWT.NONE);
+		separator.setVisible(false);
+		gd= new GridData();
+		gd.horizontalAlignment= GridData.FILL;
+		gd.verticalAlignment= GridData.BEGINNING;
+		gd.heightHint= 4;
+		separator.setLayoutData(gd);
+		if (fTableViewer instanceof CheckboxTableViewer) {
+			CheckboxTableViewer checkableViewer = (CheckboxTableViewer) fTableViewer;
+			//Select All button
+			fSelectAllButton= createPushButton(buttonContainer, config.selectAll, event -> checkableViewer.setAllChecked(true));
+
+			//De-Select All button
+			fDeselectAllButton= createPushButton(buttonContainer, config.deselectAll, event -> checkableViewer.setAllChecked(false));
+		}
+
+	}
+
+	private static Button createPushButton(Composite parent, ButtonLabel buttonLabels, Listener listener) {
+		return createPushButton(parent, buttonLabels, listener, true);
+	}
+
+	private static Button createPushButton(Composite parent, ButtonLabel buttonLabels, Listener listener, boolean enabled) {
+		if (buttonLabels != null && buttonLabels.label != null && !buttonLabels.label.isBlank()) {
+			Button button= SWTFactory.createPushButton(parent, buttonLabels.label, buttonLabels.tooltip, null);
+			button.addListener(SWT.Selection, listener);
+			button.setEnabled(enabled);
+			return button;
+		}
+		return null;
+	}
+
+	private void setEnabled(Button buttonToEnable, boolean enabled) {
+		if (buttonToEnable != null) {
+			buttonToEnable.setEnabled(enabled);
+		}
+	}
+
+	/**
+	 * handles the filter button being clicked
+	 *
+	 * @param event the clicked event
+	 */
+	private void handleFilterViewerKeyPress(KeyEvent event) {
+		if (event.character == SWT.DEL && event.stateMask == 0) {
+			removeFilters();
+		}
+	}
+
+	/**
+	 * Removes the currently selected filters.
+	 */
+	protected void removeFilters() {
+		fTableViewer.remove(((IStructuredSelection) fTableViewer.getSelection()).toArray());
+	}
+
+	/**
+	 * Allows a new filter to be added to the listing
+	 */
+	private void addFilter() {
+		List<String> existingEntries= getExistingFilters();
+		TypeFilterInputDialog dialog= new TypeFilterInputDialog(fAddFilterButton.getShell(), existingEntries, config.helpContextId);
+		if (dialog.open() == Window.OK) {
+			String res= (String) dialog.getResult();
+			addFilter(res, true);
+		}
+	}
+
+	private List<String> getExistingFilters() {
+		TableItem[] items= fTableViewer.getTable().getItems();
+		List<String> existingEntries= new ArrayList<>(items.length);
+		for (TableItem item : items) {
+			Filter data= (Filter) item.getData();
+			existingEntries.add(data.getName());
+		}
+		return existingEntries;
+	}
+
+	/**
+	 * Allows a filter to be edited
+	 */
+	private void editFilter() {
+		List<Filter> selected= ((IStructuredSelection) fTableViewer.getSelection()).toList();
+		if (selected.isEmpty()) {
+			return;
+		}
+		Filter editedEntry= selected.get(0);
+		List<String> existing= getExistingFilters();
+		existing.remove(editedEntry.getName());
+		TypeFilterInputDialog dialog= new TypeFilterInputDialog(fEditFilterButton.getShell(), existing, config.helpContextId);
+		dialog.setInitialString(editedEntry.getName());
+		if (dialog.open() == Window.OK) {
+			editedEntry.setName((String) dialog.getResult());
+			fTableViewer.refresh(editedEntry);
+		}
+	}
+
+	/**
+	 * add a new type to the listing of available filters
+	 */
+	private void addType() {
+		try {
+			int searchType = config.considerAllTypes ? IJavaElementSearchConstants.CONSIDER_ALL_TYPES : IJavaElementSearchConstants.CONSIDER_CLASSES;
+			SelectionDialog dialog= JavaUI.createTypeDialog(fAddTypeButton.getShell(),
+					PlatformUI.getWorkbench().getProgressService(),
+					getTypeSearchScope(),
+					searchType,
+					false);
+			apply(config.addTypeDialog, dialog);
+			if (dialog.open() == IDialogConstants.OK_ID) {
+				Object[] types= dialog.getResult();
+				if (types != null && types.length > 0) {
+					IType type= (IType) types[0];
+					addFilter(type.getFullyQualifiedName(), true);
+				}
+			}
+		} catch (JavaModelException jme) {
+			ExceptionHandler.handle(jme, config.errorAddTypeDialog.title, config.errorAddTypeDialog.message);
+		}
+	}
+
+	/**
+	 * add a new package to the list of all available package filters
+	 */
+	private void addPackage() {
+		int packageSelectionFlags = PackageSelectionDialog.F_REMOVE_DUPLICATES;
+		if (!config.showDefaultPackage) {
+			packageSelectionFlags |= PackageSelectionDialog.F_HIDE_DEFAULT_PACKAGE;
+		}
+		if (config.showParents) {
+			packageSelectionFlags |= PackageSelectionDialog.F_SHOW_PARENTS;
+		}
+		SelectionDialog dialog= JavaUI.createPackageDialog(
+				fAddPackageButton.getShell(),
+				true,
+				packageSelectionFlags,
+				""); //$NON-NLS-1$
+		apply(config.addPackageDialog, dialog);
+		if (dialog.open() == IDialogConstants.OK_ID) {
+			Object[] packages= dialog.getResult();
+			if (packages != null) {
+				IJavaElement pkg= null;
+				for (int i= 0; i < packages.length; i++) {
+					pkg= (IJavaElement) packages[i];
+					String filter= pkg.getElementName() + ".*"; //$NON-NLS-1$
+					addFilter(filter, true);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Enables or disables the buttons and the table control
+	 *
+	 * @param enabled the new enabled status of the widgets
+	 * @since 3.24
+	 */
+	public void setEnabled(boolean enabled) {
+		fAddFilterButton.setEnabled(enabled);
+		fAddPackageButton.setEnabled(enabled);
+		fAddTypeButton.setEnabled(enabled);
+		fDeselectAllButton.setEnabled(enabled);
+		fSelectAllButton.setEnabled(enabled);
+		fTableViewer.getTable().setEnabled(enabled);
+		fRemoveFilterButton.setEnabled(enabled & !fTableViewer.getSelection().isEmpty());
+	}
+
+	/**
+	 * initializes the checked state of the filters when the dialog opens
+	 *
+	 * @param defaults if the defaults should be returned
+	 * @since 3.24
+	 */
+	private void initTableState(boolean defaults) {
+		Filter[] filters= getAllStoredFilters(defaults);
+		for (int i= 0; i < filters.length; i++) {
+			fTableViewer.add(filters[i]);
+			setChecked(filters[i], filters[i].isChecked());
+		}
+	}
+
+	private void setChecked(Object element, boolean checked) {
+		if (fTableViewer instanceof CheckboxTableViewer) {
+			((CheckboxTableViewer) fTableViewer).setChecked(element, checked);
+		}
+	}
+
+	private void apply(DialogLabels dialogLabels, SelectionDialog dialog) {
+		if (dialogLabels != null) {
+			dialog.setTitle(dialogLabels.title);
+			dialog.setMessage(dialogLabels.message);
+		}
+	}
+
+	/**
+	 * Returns all of the committed filters
+	 *
+	 * @param defaults if the defaults should be returned
+	 * @return an array of committed filters
+	 * @since 3.24
+	 */
+	private Filter[] getAllStoredFilters(boolean defaults) {
+		return fFilterStorage.getStoredFilters(defaults);
+	}
+
+	/**
+	 * adds a single filter to the viewer
+	 *
+	 * @param filter the new filter to add
+	 * @param checked the checked state of the new filter
+	 * @since 3.24
+	 */
+	protected void addFilter(String filter, boolean checked) {
+		if (filter != null) {
+			Filter filterObj = new Filter(filter, checked);
+			for (var item : fTableViewer.getTable().getItems()) {
+				var current = (Filter) item.getData();
+				if (filterObj.equals(current)) {
+					item.setChecked(checked);
+					return;
+				}
+			}
+			fTableViewer.add(filterObj);
+			setChecked(filterObj, checked);
+		}
+	}
+
+	/**
+	 * returns all of the filters from the table, this includes ones that have not yet been saved
+	 *
+	 * @return a possibly empty lits of filters fron the table
+	 * @since 3.24
+	 */
+	protected Filter[] getAllFiltersFromTable() {
+		TableItem[] items= fTableViewer.getTable().getItems();
+		Filter[] filters= new Filter[items.length];
+		for (int i= 0; i < items.length; i++) {
+			filters[i]= (Filter) items[i].getData();
+			filters[i].setChecked(items[i].getChecked());
+		}
+		return filters;
+	}
+
+	/**
+	 * @return the search scope for the types, by default it search in the whole workspace.
+	 */
+	protected IJavaSearchScope getTypeSearchScope() {
+		return SearchEngine.createWorkspaceScope();
+	}
+
+	/**
+	 * Resets the component to it's default state.
+	 */
+	public void performDefaults() {
+		fTableViewer.getTable().removeAll();
+		initTableState(true);
+	}
+
+	/**
+	 * Saves the current state of the table into the underlying {@link FilterStorage}
+	 *
+	 * @param store the current {@link IPreferenceStore} if the component is inside a preference
+	 *            page.
+	 */
+	public void performOk(IPreferenceStore store) {
+		fFilterStorage.setStoredFilters(store, getAllFiltersFromTable());
+	}
+
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/SWTFactory.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/SWTFactory.java
new file mode 100644
index 0000000..f0f59a4
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/SWTFactory.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ *  Copyright (c) 2000, 2022 IBM Corporation and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Note:
+ *     The code extracted from org.eclipse.jdt.internal.debug.ui package from the
+ *     eclipse.jdt.debug project.
+ *******************************************************************************/
+
+package org.eclipse.jdt.internal.ui.filtertable;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+import org.eclipse.core.runtime.Assert;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.layout.PixelConverter;
+
+/**
+ * Factory class to create some SWT resources.
+ */
+class SWTFactory {
+
+	/**
+	 * Returns a width hint for a button control.
+	 */
+	private static int getButtonWidthHint(Button button) {
+		/*button.setFont(JFaceResources.getDialogFont());*/
+		PixelConverter converter= new PixelConverter(button);
+		int widthHint= converter.convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
+		return Math.max(widthHint, button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
+	}
+
+	/**
+	 * Sets width and height hint for the button control.
+	 * <b>Note:</b> This is a NOP if the button's layout data is not
+	 * an instance of <code>GridData</code>.
+	 *
+	 * @param button the button for which to set the dimension hint
+	 */
+	private static void setButtonDimensionHint(Button button) {
+		Assert.isNotNull(button);
+		Object gd= button.getLayoutData();
+		if (gd instanceof GridData) {
+			((GridData)gd).widthHint= getButtonWidthHint(button);
+			((GridData)gd).horizontalAlignment = GridData.FILL;
+		}
+	}
+
+	/**
+	 * Creates and returns a new push button with the given
+	 * label and/or image.
+	 *
+	 * @param parent parent control
+	 * @param label button label or <code>null</code>
+	 * @param image image of <code>null</code>
+	 *
+	 * @return a new push button
+	 */
+	private static Button createPushButton(Composite parent, String label, Image image) {
+		Button button = new Button(parent, SWT.PUSH);
+		button.setFont(parent.getFont());
+		if (image != null) {
+			button.setImage(image);
+		}
+		if (label != null) {
+			button.setText(label);
+		}
+		GridData gd = new GridData();
+		button.setLayoutData(gd);
+		setButtonDimensionHint(button);
+		return button;
+	}
+
+	/**
+	 * Creates and returns a new push button with the given
+	 * label, tooltip and/or image.
+	 *
+	 * @param parent parent control
+	 * @param label button label or <code>null</code>
+	 * @param tooltip the tooltip text for the button or <code>null</code>
+	 * @param image image of <code>null</code>
+	 *
+	 * @return a new push button
+	 * @since 3.25
+	 */
+	static Button createPushButton(Composite parent, String label, String tooltip, Image image) {
+		Button button = createPushButton(parent, label, image);
+		button.setToolTipText(tooltip);
+		return button;
+	}
+
+	/**
+	 * Creates a new label widget
+	 * @param parent the parent composite to add this label widget to
+	 * @param text the text for the label
+	 * @param hspan the horizontal span to take up in the parent composite
+	 * @return the new label
+	 * @since 3.25
+	 *
+	 */
+	static Label createLabel(Composite parent, String text, int hspan) {
+		Label l = new Label(parent, SWT.NONE);
+		l.setFont(parent.getFont());
+		l.setText(text);
+		GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+		gd.horizontalSpan = hspan;
+		gd.grabExcessHorizontalSpace = false;
+		l.setLayoutData(gd);
+		return l;
+	}
+
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/TypeFilterInputDialog.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/TypeFilterInputDialog.java
similarity index 90%
rename from org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/TypeFilterInputDialog.java
rename to org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/TypeFilterInputDialog.java
index 5f0a5cc..9b845a2 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/TypeFilterInputDialog.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/filtertable/TypeFilterInputDialog.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,8 +10,10 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ * Note:
+ *     The class moved from the org.eclipse.jdt.internal.ui.preferences package.
  *******************************************************************************/
-package org.eclipse.jdt.internal.ui.preferences;
+package org.eclipse.jdt.internal.ui.filtertable;
 
 import java.util.List;
 
@@ -40,10 +42,10 @@
 
 import org.eclipse.jdt.internal.corext.util.Messages;
 
-import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
 import org.eclipse.jdt.internal.ui.dialogs.PackageSelectionDialog;
 import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
 import org.eclipse.jdt.internal.ui.dialogs.TextFieldNavigationHandler;
+import org.eclipse.jdt.internal.ui.preferences.PreferencesMessages;
 import org.eclipse.jdt.internal.ui.util.BusyIndicatorRunnableContext;
 import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
 import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
@@ -54,7 +56,7 @@
 /**
  * Dialog to enter a new entry in the type filter preference page.
  */
-public class TypeFilterInputDialog extends StatusDialog {
+class TypeFilterInputDialog extends StatusDialog {
 
 	private class TypeFilterInputAdapter implements IDialogFieldListener, IStringButtonAdapter {
 		/*
@@ -75,9 +77,11 @@
 
 	private StringButtonDialogField fNameDialogField;
 	private List<String> fExistingEntries;
+	private final String helpContextId;
 
-	public TypeFilterInputDialog(Shell parent, List<String> existingEntries) {
+	public TypeFilterInputDialog(Shell parent, List<String> existingEntries, String helpContextId) {
 		super(parent);
+		this.helpContextId = helpContextId;
 
 		fExistingEntries= existingEntries;
 
@@ -133,7 +137,7 @@
 		dialog.setFilter(fNameDialogField.getText());
 		if (dialog.open() == IDialogConstants.OK_ID) {
 			IPackageFragment res= (IPackageFragment) dialog.getFirstResult();
-			fNameDialogField.setText(res.getElementName() + "*"); //$NON-NLS-1$
+			fNameDialogField.setText(res.getElementName() + ".*"); //$NON-NLS-1$
 		}
 	}
 
@@ -162,6 +166,6 @@
 	@Override
 	protected void configureShell(Shell newShell) {
 		super.configureShell(newShell);
-		PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, IJavaHelpContextIds.TYPE_FILTER_PREFERENCE_PAGE);
+		PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, helpContextId);
 	}
 }
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/TypeFilterPreferencePage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/TypeFilterPreferencePage.java
index 982a190..2f08ad5 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/TypeFilterPreferencePage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/TypeFilterPreferencePage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -13,12 +13,8 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.ui.preferences;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Hashtable;
-import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
@@ -31,35 +27,24 @@
 import org.eclipse.swt.widgets.Link;
 
 import org.eclipse.jface.dialogs.Dialog;
-import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.preference.PreferencePage;
-import org.eclipse.jface.viewers.LabelProvider;
-import org.eclipse.jface.viewers.ViewerComparator;
-import org.eclipse.jface.window.Window;
 
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPreferencePage;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.dialogs.PreferencesUtil;
 
-import org.eclipse.jdt.core.IPackageFragment;
 import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.jdt.core.search.IJavaSearchScope;
-import org.eclipse.jdt.core.search.SearchEngine;
 
 import org.eclipse.jdt.ui.PreferenceConstants;
 
 import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
 import org.eclipse.jdt.internal.ui.JavaPlugin;
-import org.eclipse.jdt.internal.ui.dialogs.PackageSelectionDialog;
-import org.eclipse.jdt.internal.ui.util.BusyIndicatorRunnableContext;
-import org.eclipse.jdt.internal.ui.wizards.dialogfields.CheckedListDialogField;
-import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
-import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
-import org.eclipse.jdt.internal.ui.wizards.dialogfields.IListAdapter;
-import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
-import org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField;
+import org.eclipse.jdt.internal.ui.filtertable.FilterManager;
+import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable;
+import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.ButtonLabel;
+import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.FilterTableConfig;
 import org.eclipse.jdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField;
 
 /*
@@ -72,61 +57,9 @@
 	private static final String PREF_FILTER_ENABLED= PreferenceConstants.TYPEFILTER_ENABLED;
 	private static final String PREF_FILTER_DISABLED= PreferenceConstants.TYPEFILTER_DISABLED;
 
-	private static String[] unpackOrderList(String str) {
-		StringTokenizer tok= new StringTokenizer(str, ";"); //$NON-NLS-1$
-		int nTokens= tok.countTokens();
-		String[] res= new String[nTokens];
-		for (int i= 0; i < nTokens; i++) {
-			res[i]= tok.nextToken();
-		}
-		return res;
-	}
+	private static final String ITEM_SEPARATOR = ";"; //$NON-NLS-1$
 
-	private static String packOrderList(List<String> orderList) {
-		StringBuilder buf= new StringBuilder();
-		for (String element : orderList) {
-			buf.append(element);
-			buf.append(';');
-		}
-		return buf.toString();
-	}
-
-	private class TypeFilterAdapter implements IListAdapter<String>, IDialogFieldListener {
-
-		private boolean canEdit(ListDialogField<String> field) {
-			return field.getSelectedElements().size() == 1;
-		}
-
-        @Override
-		public void customButtonPressed(ListDialogField<String> field, int index) {
-        	doButtonPressed(index);
-        }
-
-        @Override
-		public void selectionChanged(ListDialogField<String> field) {
-			fFilterListField.enableButton(IDX_EDIT, canEdit(field));
-        }
-
-        @Override
-		public void dialogFieldChanged(DialogField field) {
-        }
-
-        @Override
-		public void doubleClicked(ListDialogField<String> field) {
-        	if (canEdit(field)) {
-				doButtonPressed(IDX_EDIT);
-        	}
-        }
-	}
-
-	private static final int IDX_ADD= 0;
-	private static final int IDX_ADD_PACKAGE= 1;
-	private static final int IDX_EDIT= 2;
-	private static final int IDX_REMOVE= 3;
-	private static final int IDX_SELECT= 5;
-	private static final int IDX_DESELECT= 6;
-
-	private CheckedListDialogField<String> fFilterListField;
+	private JavaFilterTable fFilterTable;
 	private SelectionButtonDialogField fHideForbiddenField;
 	private SelectionButtonDialogField fHideDiscouragedField;
 
@@ -135,26 +68,21 @@
 		setPreferenceStore(JavaPlugin.getDefault().getPreferenceStore());
 		setDescription(PreferencesMessages.TypeFilterPreferencePage_description);
 
-		String[] buttonLabels= new String[] {
-			PreferencesMessages.TypeFilterPreferencePage_add_button,
-			PreferencesMessages.TypeFilterPreferencePage_addpackage_button,
-			PreferencesMessages.TypeFilterPreferencePage_edit_button,
-			PreferencesMessages.TypeFilterPreferencePage_remove_button,
-			/* 4 */  null,
-			PreferencesMessages.TypeFilterPreferencePage_selectall_button,
-			PreferencesMessages.TypeFilterPreferencePage_deselectall_button,
-		};
-
-		TypeFilterAdapter adapter= new TypeFilterAdapter();
-
-		fFilterListField= new CheckedListDialogField<>(adapter, buttonLabels, new LabelProvider());
-		fFilterListField.setDialogFieldListener(adapter);
-		fFilterListField.setLabelText(PreferencesMessages.TypeFilterPreferencePage_list_label);
-		fFilterListField.setCheckAllButtonIndex(IDX_SELECT);
-		fFilterListField.setUncheckAllButtonIndex(IDX_DESELECT);
-		fFilterListField.setRemoveButtonIndex(IDX_REMOVE);
-
-		fFilterListField.enableButton(IDX_EDIT, false);
+		fFilterTable= new JavaFilterTable(this,
+				new FilterManager(PREF_FILTER_ENABLED, PREF_FILTER_DISABLED, ITEM_SEPARATOR),
+				new FilterTableConfig()
+						.setLabelText(PreferencesMessages.TypeFilterPreferencePage_list_label)
+						.setAddFilter(new ButtonLabel(PreferencesMessages.TypeFilterPreferencePage_add_button))
+						.setEditFilter(new ButtonLabel(PreferencesMessages.TypeFilterPreferencePage_edit_button))
+						.setAddPackage(new ButtonLabel(PreferencesMessages.TypeFilterPreferencePage_addpackage_button))
+						.setRemove(new ButtonLabel(PreferencesMessages.TypeFilterPreferencePage_remove_button))
+						.setSelectAll(new ButtonLabel(PreferencesMessages.TypeFilterPreferencePage_selectall_button))
+						.setDeselectAll(new ButtonLabel(PreferencesMessages.TypeFilterPreferencePage_deselectall_button))
+						.setAddTypeDialog(new JavaFilterTable.DialogLabels(PreferencesMessages.TypeFilterInputDialog_title, PreferencesMessages.TypeFilterInputDialog_message))
+						.setAddPackageDialog(new JavaFilterTable.DialogLabels(PreferencesMessages.TypeFilterPreferencePage_choosepackage_label,
+								PreferencesMessages.TypeFilterPreferencePage_choosepackage_description))
+						.setHelpContextId(IJavaHelpContextIds.TYPE_FILTER_PREFERENCE_PAGE)
+						.setShowParents(true));
 
 		fHideForbiddenField= new SelectionButtonDialogField(SWT.CHECK);
 		fHideForbiddenField.setLabelText(PreferencesMessages.TypeFilterPreferencePage_hideForbidden_label);
@@ -188,12 +116,7 @@
 
 		composite.setLayout(layout);
 
-		fFilterListField.doFillIntoGrid(composite, 3);
-		LayoutUtil.setHorizontalSpan(fFilterListField.getLabelControl(null), 2);
-		LayoutUtil.setWidthHint(fFilterListField.getLabelControl(null), convertWidthInCharsToPixels(40));
-		LayoutUtil.setHorizontalGrabbing(fFilterListField.getListControl(null));
-
-		fFilterListField.getTableViewer().setComparator(new ViewerComparator());
+		fFilterTable.createTable(composite);
 
 		Label spacer= new Label(composite, SWT.LEFT );
 		GridData gd= new GridData(SWT.DEFAULT, convertHeightInCharsToPixels(1) / 2);
@@ -226,20 +149,9 @@
 	}
 
 	private void initialize(boolean fromDefault) {
-		IPreferenceStore store= getPreferenceStore();
-
-		String enabled= fromDefault ? store.getDefaultString(PREF_FILTER_ENABLED) : store.getString(PREF_FILTER_ENABLED);
-		String disabled= fromDefault ? store.getDefaultString(PREF_FILTER_DISABLED) : store.getString(PREF_FILTER_DISABLED);
-
-		ArrayList<String> res= new ArrayList<>();
-
-		String[] enabledEntries= unpackOrderList(enabled);
-		res.addAll(Arrays.asList(enabledEntries));
-		String[] disabledEntries= unpackOrderList(disabled);
-		res.addAll(Arrays.asList(disabledEntries));
-
-		fFilterListField.setElements(res);
-		fFilterListField.setCheckedElements(Arrays.asList(enabledEntries));
+		if (fromDefault) {
+			fFilterTable.performDefaults();
+		}
 
 		boolean hideForbidden= getJDTCoreOption(JavaCore.CODEASSIST_FORBIDDEN_REFERENCE_CHECK, fromDefault);
 		fHideForbiddenField.setSelection(hideForbidden);
@@ -252,67 +164,6 @@
 		return JavaCore.ENABLED.equals(value);
 	}
 
-	private void doButtonPressed(int index) {
-		switch (index) {
-			case IDX_ADD:
-				// add new
-				TypeFilterInputDialog dialog= new TypeFilterInputDialog(getShell(), fFilterListField.getElements());
-				if (dialog.open() == Window.OK) {
-					String res= (String) dialog.getResult();
-					fFilterListField.addElement(res);
-					fFilterListField.setChecked(res, true);
-				}
-				break;
-			case IDX_ADD_PACKAGE:
-				// add packages
-				String[] res= choosePackage();
-				if (res != null) {
-					fFilterListField.addElements(Arrays.asList(res));
-					for (String re : res) {
-						fFilterListField.setChecked(re, true);
-					}
-				}
-				break;
-			case IDX_EDIT:
-				// edit
-				List<String> selected= fFilterListField.getSelectedElements();
-				if (selected.isEmpty()) {
-					return;
-				}
-				String editedEntry= selected.get(0);
-				List<String> existing= fFilterListField.getElements();
-				existing.remove(editedEntry);
-				dialog= new TypeFilterInputDialog(getShell(), existing);
-				dialog.setInitialString(editedEntry);
-				if (dialog.open() == Window.OK) {
-					fFilterListField.replaceElement(editedEntry, (String) dialog.getResult());
-				}
-				break;
-			default:
-				break;
-		}
-	}
-
-	private String[] choosePackage() {
-		IJavaSearchScope scope= SearchEngine.createWorkspaceScope();
-		BusyIndicatorRunnableContext context= new BusyIndicatorRunnableContext();
-		int flags= PackageSelectionDialog.F_SHOW_PARENTS | PackageSelectionDialog.F_HIDE_DEFAULT_PACKAGE | PackageSelectionDialog.F_REMOVE_DUPLICATES;
-		PackageSelectionDialog dialog = new PackageSelectionDialog(getShell(), context, flags , scope);
-		dialog.setTitle(PreferencesMessages.TypeFilterPreferencePage_choosepackage_label);
-		dialog.setMessage(PreferencesMessages.TypeFilterPreferencePage_choosepackage_description);
-		dialog.setMultipleSelection(true);
-		if (dialog.open() == IDialogConstants.OK_ID) {
-			Object[] fragments= dialog.getResult();
-			String[] res= new String[fragments.length];
-			for (int i= 0; i < res.length; i++) {
-				res[i]= ((IPackageFragment) fragments[i]).getElementName() + ".*"; //$NON-NLS-1$
-			}
-			return res;
-		}
-		return null;
-	}
-
-
 	@Override
 	public void init(IWorkbench workbench) {
 	}
@@ -327,7 +178,6 @@
 		super.performDefaults();
     }
 
-
     /*
      * @see org.eclipse.jface.preference.IPreferencePage#performOk()
      */
@@ -335,12 +185,7 @@
 	public boolean performOk() {
   		IPreferenceStore prefs= JavaPlugin.getDefault().getPreferenceStore();
 
-  		List<String> checked= fFilterListField.getCheckedElements();
-  		List<String> unchecked= fFilterListField.getElements();
-  		unchecked.removeAll(checked);
-
-  		prefs.setValue(PREF_FILTER_ENABLED, packOrderList(checked));
-  		prefs.setValue(PREF_FILTER_DISABLED, packOrderList(unchecked));
+		fFilterTable.performOk(prefs);
 		JavaPlugin.flushInstanceScope();
 
 		Hashtable<String, String> coreOptions= JavaCore.getOptions();
@@ -353,7 +198,5 @@
         return true;
     }
 
-
 }
 
-
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaUI.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaUI.java
index f5b7833..fc00098 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaUI.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaUI.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -31,6 +31,7 @@
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.dialogs.SelectionDialog;
 
 import org.eclipse.ui.texteditor.IDocumentProvider;
@@ -412,7 +413,31 @@
 			boolean multipleSelection, boolean removeDuplicates, String filter) {
 
 		int flag= removeDuplicates ? PackageSelectionDialog.F_REMOVE_DUPLICATES : 0;
-		PackageSelectionDialog dialog= new PackageSelectionDialog(parent, context, flag, scope);
+		return createPackageDialog(parent, context, scope, multipleSelection, flag, filter);
+	}
+
+	/**
+	 * Creates a selection dialog that lists all packages of the given Java search scope.
+	 * The caller is responsible for opening the dialog with <code>Window.open</code>,
+	 * and subsequently extracting the selected package (of type
+	 * <code>IPackageFragment</code>) via <code>SelectionDialog.getResult</code>.
+	 *
+	 * @param parent the parent shell of the dialog to be created
+	 * @param context the runnable context to run the search in
+	 * @param scope the scope defining the available packages.
+	 * @param multipleSelection true if multiple selection is allowed
+	 * @param flags a combination of <code>PackageSelectionDialog.F_REMOVE_DUPLICATES</code>, <code>PackageSelectionDialog.F_SHOW_PARENTS</code>,
+	 *  <code>PackageSelectionDialog.F_HIDE_DEFAULT_PACKAGE</code> and  <code>PackageSelectionDialog.F_HIDE_EMPTY_INNER</code>
+	 * @param filter the initial pattern to filter the set of packages. For example "com" shows
+	 * all packages starting with "com". The meta character '?' representing any character and
+	 * '*' representing any string are supported. Clients can pass an empty string if no filtering
+	 * is required.
+	 * @return a new selection dialog
+	 *
+	 * @since 3.26
+	 */
+	public static SelectionDialog createPackageDialog(Shell parent, IRunnableContext context, IJavaSearchScope scope, boolean multipleSelection, int flags, String filter) {
+		PackageSelectionDialog dialog= new PackageSelectionDialog(parent, context, flags, scope);
 		dialog.setFilter(filter);
 		dialog.setIgnoreCase(false);
 		dialog.setMultipleSelection(multipleSelection);
@@ -420,6 +445,24 @@
 	}
 
 	/**
+	 * Creates a selection dialog that lists all packages of all the Java projects.
+	 *
+	 * @since 3.26
+	 * @param shell the parent shell of the dialog to be created
+	 * @param multipleSelection true if multiple selection is allowed
+	 * @param flags a combination of <code>PackageSelectionDialog.F_REMOVE_DUPLICATES</code>, <code>PackageSelectionDialog.F_SHOW_PARENTS</code>,
+	 *  <code>PackageSelectionDialog.F_HIDE_DEFAULT_PACKAGE</code> and  <code>PackageSelectionDialog.F_HIDE_EMPTY_INNER</code>
+	 * @param filter the initial pattern to filter the set of packages. For example "com" shows
+	 * all packages starting with "com". The meta character '?' representing any character and
+	 * '*' representing any string are supported. Clients can pass an empty string if no filtering
+	 * is required.
+	 * @return a new selection dialog
+	 */
+	public static SelectionDialog createPackageDialog(Shell shell, boolean multipleSelection, int flags, String filter) {
+		return createPackageDialog(shell, PlatformUI.getWorkbench().getProgressService(), SearchEngine.createWorkspaceScope(), multipleSelection, flags, filter);
+	}
+
+	/**
 	 * Creates a selection dialog that lists all packages of the given Java project.
 	 * The caller is responsible for opening the dialog with <code>Window.open</code>,
 	 * and subsequently extracting the selected package (of type