diff --git a/API_FILTER_REGRESSION_INVESTIGATION.md b/API_FILTER_REGRESSION_INVESTIGATION.md
new file mode 100644
index 00000000000..f127bd2b9a9
--- /dev/null
+++ b/API_FILTER_REGRESSION_INVESTIGATION.md
@@ -0,0 +1,154 @@
+# API Filter "Unused Filter" Regression Investigation
+
+## Issue
+https://github.com/eclipse-pde/eclipse.pde/issues/2096
+
+JDT reports that API filters are being reported as unused when they shouldn't be.
+
+## Background
+
+### JDT Scenario
+JDT has classes that extend `ASTNode` (@noextend) and implement `IDocElement` (@noextend, @noimplement).
+These violations are intentional and filtered using `.api_filters`.
+
+Example from JDT's .api_filters:
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Filter Storage Format
+- **Type name**: Fully qualified (e.g., `org.eclipse.jdt.core.dom.AbstractTagElement`)
+- **Message arguments**: Simple names (e.g., `IDocElement`, `ASTNode`)
+- **Resource path**: Relative to project (e.g., `dom/org/eclipse/jdt/core/dom/AbstractTagElement.java`)
+
+### Problem Generation
+Two code paths:
+1. **IDE** (`AbstractProblemDetector.createProblemWithContext`):
+ - Uses `getMessageArgs()` -> returns simple names
+ - Used when workspace resources exist
+
+2. **Headless** (`AbstractProblemDetector.createProblem`):
+ - Uses `getQualifiedMessageArgs()` -> returns fully qualified names
+ - Used in headless builds/API analysis
+
+### Filter Matching Logic
+`FilterStore.argumentsEquals()` compares message arguments:
+
+```java
+private boolean argumentsEquals(String[] problemMessageArguments, String[] filterProblemMessageArguments) {
+ // filter problems message arguments are always simple name
+ // problem message arguments are fully qualified name outside the IDE
+ int length = problemMessageArguments.length;
+ if (length == filterProblemMessageArguments.length) {
+ for (int i = 0; i < length; i++) {
+ String problemMessageArgument = problemMessageArguments[i];
+ String filterProblemMessageArgument = filterProblemMessageArguments[i];
+ if (problemMessageArgument.equals(filterProblemMessageArgument)) {
+ continue;
+ }
+ int index = problemMessageArgument.lastIndexOf('.');
+ int filterProblemIndex = filterProblemMessageArgument.lastIndexOf('.');
+ if (index == -1) {
+ // Problem is simple name
+ if (filterProblemIndex == -1) {
+ return false; // Both simple, should have matched above
+ }
+ // Filter is FQ, extract simple name
+ if (!filterProblemMessageArgument.substring(filterProblemIndex + 1)
+ .equals(problemMessageArgument)) {
+ return false;
+ }
+ } else if (filterProblemIndex != -1) {
+ // Both FQ but didn't match -> different types
+ return false;
+ } else {
+ // Problem is FQ, filter is simple - extract and compare
+ if (!problemMessageArgument.substring(index + 1).equals(filterProblemMessageArgument)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+}
+```
+
+**Expected behavior:**
+- Simple vs Simple: exact match
+- FQ vs FQ: exact match
+- FQ vs Simple: extract simple name from FQ, then match
+- Simple vs FQ: extract simple name from FQ, then match
+
+This logic should handle both IDE (simple names) and headless (FQ names) scenarios correctly.
+
+## Investigation Results
+
+### Problem IDs
+JDT filter IDs decoded:
+- `576725006` (0x2260200E): USAGE category, TYPE element, API_LEAK kind - for implementing IDocElement
+- `576778288` (0x22602210): USAGE category, TYPE element, API_LEAK kind - for extending ASTNode
+
+### Expected Matching
+For headless build:
+- **Filter**: `["IDocElement", "AbstractTagElement"]` (simple)
+- **Problem**: `["org.eclipse.jdt.core.dom.IDocElement", "org.eclipse.jdt.core.dom.AbstractTagElement"]` (FQ)
+- **Should match**: Yes, via lines 265-268 of argumentsEquals
+
+For IDE build:
+- **Filter**: `["IDocElement", "AbstractTagElement"]` (simple)
+- **Problem**: `["IDocElement", "AbstractTagElement"]` (simple)
+- **Should match**: Yes, via line 250 of argumentsEquals
+
+## Hypothesis
+
+The matching logic in `FilterStore.argumentsEquals()` appears correct. Possible causes:
+
+1. **Problem ID mismatch**: Filter IDs don't match generated problem IDs
+2. **Type name mismatch**: Type names don't match exactly
+3. **Resource path mismatch**: Resource paths differ
+4. **Code path change**: IDE vs headless code path selection changed
+5. **Message args generation change**: getQualifiedMessageArgs() behavior changed
+
+## Test Cases Created
+
+1. **FilterMatchingRegressionTests.java**: Unit tests for filter matching logic
+ - Tests simple vs simple matching
+ - Tests FQ vs simple matching
+ - Uses JDT-like scenarios
+
+2. **UnusedFilterRegressionTests.java**: Integration test skeleton
+ - Framework for full workspace test
+ - Needs test project setup to run
+
+## Next Steps
+
+1. **Run FilterMatchingRegressionTests** in proper Eclipse/Tycho build to verify matching logic
+2. **Enable debug logging**: Set `ApiPlugin.DEBUG_FILTER_STORE = true` in JDT build to see:
+ - Which filters are checked
+ - Which problems are generated
+ - Why filters don't match
+3. **Compare problem generation**: Check if JDT problems have expected IDs, paths, type names, and message args
+4. **Check for recent changes**: Look for commits in eclipse-pde that changed:
+ - Problem ID generation
+ - Message argument generation
+ - Filter matching logic
+ - Problem path handling
+
+## Files Modified
+
+- `apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterMatchingRegressionTests.java` - Unit test for filter matching
+- `apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/usage/UnusedFilterRegressionTests.java` - Integration test skeleton
diff --git a/SUMMARY.md b/SUMMARY.md
new file mode 100644
index 00000000000..21eb895869b
--- /dev/null
+++ b/SUMMARY.md
@@ -0,0 +1,187 @@
+# API Filter Regression - Summary and Recommendations
+
+## Issue Reference
+https://github.com/eclipse-pde/eclipse.pde/issues/2096
+
+## Problem Statement
+JDT Core build reports API filters as "unused" when they should be matching actual API problems. The filters are for classes that extend `ASTNode` (@noextend) and implement `IDocElement` (@noextend/@noimplement).
+
+## Work Completed
+
+### 1. Test Cases Created
+- **FilterMatchingRegressionTests.java**: Unit tests for filter matching logic
+ - Verifies simple vs simple name matching works
+ - Verifies fully qualified vs simple name matching works
+ - Uses realistic JDT problem scenarios
+
+- **UnusedFilterRegressionTests.java**: Integration test skeleton
+ - Framework for workspace-based testing
+ - Ready for expansion with actual test projects
+
+### 2. Investigation Documentation
+- **API_FILTER_REGRESSION_INVESTIGATION.md**: Comprehensive analysis including:
+ - JDT scenario breakdown
+ - Filter storage format details
+ - Problem generation code paths
+ - Filter matching algorithm explanation
+ - Problem ID decoding
+ - Multiple hypotheses for root cause
+
+### 3. Testing Helper
+- **test-filter-matching.sh**: Script demonstrating expected matching behavior
+ - Shows all matching scenarios
+ - Provides debugging instructions
+
+## Key Technical Findings
+
+### Filter Matching Logic Analysis
+The `FilterStore.argumentsEquals()` method correctly handles:
+1. **Simple vs Simple**: Direct string comparison
+2. **FQ vs FQ**: Direct string comparison
+3. **FQ vs Simple**: Extracts simple name from FQ, then compares
+4. **Simple vs FQ**: Extracts simple name from FQ, then compares
+
+This logic **should work correctly** for both IDE and headless builds.
+
+### JDT Filter Format
+```xml
+
+
+
+
+
+
+```
+
+### Problem Generation
+- **IDE builds**: Generate problems with simple names via `getMessageArgs()`
+- **Headless builds**: Generate problems with FQ names via `getQualifiedMessageArgs()`
+
+Both should match the filters correctly.
+
+## Root Cause Hypotheses
+
+Since the matching logic appears correct, the issue likely lies in one of these areas:
+
+1. **Problem ID Generation Changed**
+ - Filter IDs don't match newly generated problem IDs
+ - Check if problem ID calculation has been modified
+
+2. **Resource Path Mismatch**
+ - Problem resource paths don't match filter resource paths
+ - Could be path format changes (forward vs back slash, relative vs absolute)
+
+3. **Type Name Mismatch**
+ - Problem type names don't match filter type names
+ - Could be package name changes or type name format issues
+
+4. **Code Path Selection Changed**
+ - Builds that used to be "headless" now use "IDE" path or vice versa
+ - Different message arg format than expected
+
+5. **Message Arguments Not Being Set**
+ - Problems generated without message arguments
+ - Empty arrays would fail length check
+
+## Recommended Next Steps
+
+### Immediate Actions
+1. **Run FilterMatchingRegressionTests**
+ - Requires proper Eclipse/Tycho build environment
+ - Will verify matching logic works as expected
+
+2. **Enable Debug Logging in JDT Build**
+ ```java
+ ApiPlugin.DEBUG_FILTER_STORE = true
+ ```
+ Or add to JDT build VM args:
+ ```
+ -DApiPlugin.DEBUG_FILTER_STORE=true
+ ```
+
+3. **Analyze Debug Output**
+ Look for these messages in JDT build logs:
+ - `"no resource exists: [...]"` - Resource path issue
+ - `"no filters defined for [...]"` - Filter loading issue
+ - `"no filter defined for problem: [...]"` - Matching failed, shows problem details
+ - `"filter used: [...]"` - Matching succeeded (should see this)
+
+### Deep Investigation
+1. **Compare Actual vs Expected Values**
+ - Problem ID: Should be 576725006 or 576778288
+ - Resource path: Should match filter's `path` attribute
+ - Type name: Should be fully qualified
+ - Message args: Should be FQ in headless, simple in IDE
+
+2. **Check Recent Commits**
+ - Search eclipse-pde repository for changes to:
+ - `FilterStore.java`
+ - `ApiFilterStore.java`
+ - `AbstractProblemDetector.java`
+ - `ApiProblemFactory.java`
+
+3. **Compare with Working Version**
+ - Identify last known working PDE version for JDT
+ - Compare filter matching and problem generation code
+
+### If Matching Logic Is Broken
+If tests show matching logic is actually broken:
+
+1. **Fix `FilterStore.argumentsEquals()`**
+ - Current logic at lines 242-274
+ - Ensure all four scenarios work correctly
+
+2. **Add More Test Coverage**
+ - Test edge cases (null, empty, mixed formats)
+ - Test with actual JDT filter XML
+
+3. **Update Documentation**
+ - Clarify expected behavior
+ - Document IDE vs headless differences
+
+### If Matching Logic Is Correct
+If tests show matching works but JDT still fails:
+
+1. **Problem Generation Issue**
+ - Check if JDT problems have correct problem IDs
+ - Verify message arguments are being set
+ - Confirm resource paths match filter format
+
+2. **Filter Loading Issue**
+ - Verify .api_filters file is being read
+ - Check filter parsing doesn't corrupt data
+ - Ensure filters are added to correct resource
+
+3. **Build Environment Issue**
+ - Wrong code path being used
+ - Missing baseline causing short-circuit
+ - Version/API changes in problem generation
+
+## Expected Outcomes
+
+### Success Criteria
+- FilterMatchingRegressionTests pass
+- JDT build no longer reports unused filters
+- Filters correctly suppress API violations
+
+### Deliverables
+- Working test cases demonstrating correct behavior
+- Fix for identified root cause (if found in PDE)
+- Or workaround/fix for JDT (if issue is in JDT setup)
+- Documentation of proper filter format and usage
+
+## Files Provided
+
+1. `FilterMatchingRegressionTests.java` - Unit tests
+2. `UnusedFilterRegressionTests.java` - Integration test skeleton
+3. `API_FILTER_REGRESSION_INVESTIGATION.md` - Detailed analysis
+4. `test-filter-matching.sh` - Testing helper script
+5. `SUMMARY.md` - This file
+
+## Contact/Follow-up
+
+For questions or to report findings:
+- Reference issue: https://github.com/eclipse-pde/eclipse.pde/issues/2096
+- Provide debug logs with `DEBUG_FILTER_STORE=true`
+- Share test results from FilterMatchingRegressionTests
+- Report which hypothesis matches actual findings
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/usage/UnusedFilterRegressionTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/usage/UnusedFilterRegressionTests.java
new file mode 100644
index 00000000000..03a417224bd
--- /dev/null
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/usage/UnusedFilterRegressionTests.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Eclipse contributors 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:
+ * Eclipse contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.api.tools.builder.tests.usage;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.pde.api.tools.internal.model.ApiModelFactory;
+import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory;
+import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
+import org.eclipse.pde.api.tools.internal.provisional.IApiBaselineManager;
+import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore;
+import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor;
+import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
+import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
+import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
+
+import junit.framework.Test;
+
+/**
+ * Tests for regression in unused API problem filter detection
+ *
+ * This test case is designed to reproduce the issue where API filters
+ * are incorrectly reported as unused when they should be matching
+ * actual problems.
+ *
+ * @see https://github.com/eclipse-pde/eclipse.pde/issues/2096
+ */
+public class UnusedFilterRegressionTests extends UsageTest {
+
+ public UnusedFilterRegressionTests(String name) {
+ super(name);
+ }
+
+ /**
+ * @return the tests for this class
+ */
+ public static Test suite() {
+ return buildTestSuite(UnusedFilterRegressionTests.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Create a baseline for comparison
+ assertStubBaseline(BASELINE);
+ }
+
+ /**
+ * Asserts a stub {@link IApiBaseline} that contains all of the workspace
+ * projects as API components
+ *
+ * @param name the name for the baseline
+ */
+ protected void assertStubBaseline(String name) {
+ IApiBaselineManager manager = ApiPlugin.getDefault().getApiBaselineManager();
+ IApiBaseline baseline = manager.getDefaultApiBaseline();
+ if (baseline == null) {
+ baseline = ApiModelFactory.newApiBaseline(name);
+ manager.addApiBaseline(baseline);
+ manager.setDefaultApiBaseline(baseline.getName());
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ removeBaseline(BASELINE);
+ super.tearDown();
+ }
+
+ /**
+ * Removes the baseline with the given name
+ */
+ private void removeBaseline(String name) {
+ IApiBaselineManager manager = ApiPlugin.getDefault().getApiBaselineManager();
+ IApiBaseline baseline = manager.getDefaultApiBaseline();
+ if (baseline != null) {
+ assertEquals("The given name should be the default baseline name", baseline.getName(), name);
+ assertTrue("The baseline [" + name + "] should have been removed", manager.removeApiBaseline(name));
+ }
+ }
+
+ @Override
+ protected void setBuilderOptions() {
+ super.setBuilderOptions();
+ enableLeakOptions(true);
+ }
+
+ @Override
+ protected int getDefaultProblemId() {
+ return ApiProblemFactory.createProblemId(
+ IApiProblem.CATEGORY_USAGE,
+ IElementDescriptor.TYPE,
+ IApiProblem.UNSUPPORTED_TAG_USE,
+ IApiProblem.NO_FLAGS);
+ }
+
+ /**
+ * Test that API filters for extend restrictions are not incorrectly
+ * reported as unused.
+ *
+ * This test simulates the scenario from JDT where classes extend
+ * ASTNode (which has @noextend) and have filters for this.
+ */
+ public void testExtendRestrictionsFilterNotReportedUnused() throws Exception {
+ // This is a regression test for issue #2096
+ // We need to verify that when a class extends a restricted type
+ // and has a filter for it, the filter is not reported as unused
+
+ // For now, this is a placeholder that demonstrates the test structure
+ // The actual implementation would require:
+ // 1. Setting up baseline with restricted type
+ // 2. Creating a workspace project with a class extending it
+ // 3. Adding an API filter
+ // 4. Running the builder
+ // 5. Verifying no unused filter markers are created
+
+ // TODO: Implement full test case
+ }
+
+ /**
+ * Test that API filters for implement restrictions are not incorrectly
+ * reported as unused.
+ *
+ * This test simulates the scenario from JDT where classes implement
+ * interfaces with restrictions and have filters for this.
+ */
+ public void testImplementRestrictionsFilterNotReportedUnused() throws Exception {
+ // This is a regression test for issue #2096
+ // Similar to testExtendRestrictionsFilterNotReportedUnused but for
+ // implement restrictions
+
+ // TODO: Implement full test case
+ }
+}
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterMatchingRegressionTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterMatchingRegressionTests.java
new file mode 100644
index 00000000000..6e5375fe017
--- /dev/null
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterMatchingRegressionTests.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Eclipse contributors 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:
+ * Eclipse contributors - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.api.tools.model.tests;
+
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.pde.api.tools.internal.FilterStore;
+import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory;
+import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor;
+import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
+import org.junit.Test;
+
+/**
+ * Tests for regressions in filter matching logic
+ *
+ * This tests the scenario where filters with simple type names in message
+ * arguments should match problems with fully qualified type names.
+ *
+ * @see https://github.com/eclipse-pde/eclipse.pde/issues/2096
+ */
+public class FilterMatchingRegressionTests {
+
+ /**
+ * Tests that a filter with simple type names in message arguments
+ * matches a problem with fully qualified type names.
+ *
+ * This simulates the JDT scenario where .api_filters has simple names
+ * but headless builds generate fully qualified names.
+ */
+ @Test
+ public void testFilterMatchesWithSimpleAndQualifiedNames() {
+ // Create a filter problem with simple names (as stored in .api_filters)
+ String[] filterMessageArgs = new String[] { "IDocElement", "AbstractTagElement" };
+ IApiProblem filterProblem = ApiProblemFactory.newApiProblem(
+ "dom/org/eclipse/jdt/core/dom/AbstractTagElement.java", // path
+ "org.eclipse.jdt.core.dom.AbstractTagElement", // type name (fully qualified)
+ filterMessageArgs, // message args (simple names)
+ null, null, -1, -1, -1,
+ IApiProblem.CATEGORY_USAGE,
+ IElementDescriptor.TYPE,
+ IApiProblem.API_LEAK,
+ IApiProblem.NO_FLAGS);
+
+ // Create an actual problem with fully qualified names (as generated by headless build)
+ String[] problemMessageArgs = new String[] { "org.eclipse.jdt.core.dom.IDocElement", "org.eclipse.jdt.core.dom.AbstractTagElement" };
+ IApiProblem actualProblem = ApiProblemFactory.newApiProblem(
+ "dom/org/eclipse/jdt/core/dom/AbstractTagElement.java", // path
+ "org.eclipse.jdt.core.dom.AbstractTagElement", // type name (fully qualified)
+ problemMessageArgs, // message args (fully qualified)
+ null, null, -1, -1, -1,
+ IApiProblem.CATEGORY_USAGE,
+ IElementDescriptor.TYPE,
+ IApiProblem.API_LEAK,
+ IApiProblem.NO_FLAGS);
+
+ // Create a filter store to test the matching
+ TestFilterStore store = new TestFilterStore();
+
+ // Test that the problems match
+ boolean matches = store.testProblemsMatch(filterProblem, actualProblem);
+
+ assertTrue("Filter with simple names should match problem with fully qualified names", matches);
+ }
+
+ /**
+ * Tests that a filter with simple type names matches a problem with simple type names
+ * (IDE scenario)
+ */
+ @Test
+ public void testFilterMatchesWithBothSimpleNames() {
+ // Create a filter problem with simple names
+ String[] filterMessageArgs = new String[] { "IDocElement", "AbstractTagElement" };
+ IApiProblem filterProblem = ApiProblemFactory.newApiProblem(
+ "dom/org/eclipse/jdt/core/dom/AbstractTagElement.java",
+ "org.eclipse.jdt.core.dom.AbstractTagElement",
+ filterMessageArgs,
+ null, null, -1, -1, -1,
+ IApiProblem.CATEGORY_USAGE,
+ IElementDescriptor.TYPE,
+ IApiProblem.API_LEAK,
+ IApiProblem.NO_FLAGS);
+
+ // Create an actual problem with simple names (as generated by IDE)
+ String[] problemMessageArgs = new String[] { "IDocElement", "AbstractTagElement" };
+ IApiProblem actualProblem = ApiProblemFactory.newApiProblem(
+ "dom/org/eclipse/jdt/core/dom/AbstractTagElement.java",
+ "org.eclipse.jdt.core.dom.AbstractTagElement",
+ problemMessageArgs,
+ null, null, -1, -1, -1,
+ IApiProblem.CATEGORY_USAGE,
+ IElementDescriptor.TYPE,
+ IApiProblem.API_LEAK,
+ IApiProblem.NO_FLAGS);
+
+ TestFilterStore store = new TestFilterStore();
+ boolean matches = store.testProblemsMatch(filterProblem, actualProblem);
+
+ assertTrue("Filter with simple names should match problem with simple names", matches);
+ }
+
+ /**
+ * Helper class to expose the protected problemsMatch method for testing
+ */
+ private static class TestFilterStore extends FilterStore {
+ public boolean testProblemsMatch(IApiProblem filterProblem, IApiProblem problem) {
+ return problemsMatch(filterProblem, problem);
+ }
+ }
+}
diff --git a/test-filter-matching.sh b/test-filter-matching.sh
new file mode 100755
index 00000000000..0c3691581e4
--- /dev/null
+++ b/test-filter-matching.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+# Simple script to test API filter matching logic
+# This demonstrates how the FilterStore.argumentsEquals() method should work
+
+echo "API Filter Matching Test"
+echo "========================"
+echo ""
+echo "Testing scenarios from JDT issue #2096"
+echo ""
+
+# Test case 1: Headless build (FQ problem vs simple filter)
+echo "Test 1: Headless Build Scenario"
+echo " Filter args: ['IDocElement', 'AbstractTagElement']"
+echo " Problem args: ['org.eclipse.jdt.core.dom.IDocElement', 'org.eclipse.jdt.core.dom.AbstractTagElement']"
+echo " Expected: MATCH (extract simple names from FQ)"
+echo " Logic: problemArg.substring(lastIndexOf('.')+1) == filterArg"
+echo ""
+
+# Test case 2: IDE build (simple problem vs simple filter)
+echo "Test 2: IDE Build Scenario"
+echo " Filter args: ['IDocElement', 'AbstractTagElement']"
+echo " Problem args: ['IDocElement', 'AbstractTagElement']"
+echo " Expected: MATCH (exact match)"
+echo " Logic: problemArg == filterArg"
+echo ""
+
+# Test case 3: Both FQ and same
+echo "Test 3: Both Fully Qualified (Same)"
+echo " Filter args: ['org.eclipse.jdt.core.dom.IDocElement']"
+echo " Problem args: ['org.eclipse.jdt.core.dom.IDocElement']"
+echo " Expected: MATCH (exact match)"
+echo " Logic: problemArg == filterArg"
+echo ""
+
+# Test case 4: Both FQ but different
+echo "Test 4: Both Fully Qualified (Different)"
+echo " Filter args: ['org.eclipse.jdt.core.dom.IDocElement']"
+echo " Problem args: ['org.eclipse.jdt.core.dom.OtherType']"
+echo " Expected: NO MATCH (different types)"
+echo " Logic: Both have dots, not equal -> return false"
+echo ""
+
+# Test case 5: Filter FQ, problem simple
+echo "Test 5: Filter FQ, Problem Simple"
+echo " Filter args: ['org.eclipse.jdt.core.dom.IDocElement']"
+echo " Problem args: ['IDocElement']"
+echo " Expected: MATCH (extract simple name from filter)"
+echo " Logic: filterArg.substring(lastIndexOf('.')+1) == problemArg"
+echo ""
+
+echo "To run actual unit tests:"
+echo " cd apitools/org.eclipse.pde.api.tools.tests"
+echo " mvn test -Dtest=FilterMatchingRegressionTests"
+echo ""
+echo "To enable debug logging in Eclipse:"
+echo " Set system property: -DApiPlugin.DEBUG_FILTER_STORE=true"
+echo " Or set ApiPlugin.DEBUG_FILTER_STORE = true in code"
+echo ""
+echo "To analyze JDT build:"
+echo " 1. Enable DEBUG_FILTER_STORE in JDT build"
+echo " 2. Look for messages like:"
+echo " - 'no resource exists: [...]'"
+echo " - 'no filters defined for [...]'"
+echo " - 'no filter defined for problem: [...]'"
+echo " - 'filter used: [...]' <- should see this if matching works"
+echo " 3. Compare actual vs expected problem IDs, paths, type names, message args"