diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/leak/InterfaceExtendsLeak.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/leak/InterfaceExtendsLeak.java index e9c56258c9d..86f9d5b475b 100644 --- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/leak/InterfaceExtendsLeak.java +++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/leak/InterfaceExtendsLeak.java @@ -371,4 +371,28 @@ private void x14(boolean inc) { String typename = "Etest14"; //$NON-NLS-1$ deployLeakTest(typename + ".java", inc);//$NON-NLS-1$ } + + /** + * Tests that a static inner class implementing its parent interface does NOT produce + * a leak warning (inner class referencing enclosing type should be excluded) + * using a full build + */ + public void testStaticInnerClassImplementsParent15F() { + x15(false); + } + + /** + * Tests that a static inner class implementing its parent interface does NOT produce + * a leak warning (inner class referencing enclosing type should be excluded) + * using an incremental build + */ + public void testStaticInnerClassImplementsParent15I() { + x15(true); + } + + private void x15(boolean inc) { + expectingNoProblems(); + String typename = "Etest15"; //$NON-NLS-1$ + deployLeakTest(typename + ".java", inc);//$NON-NLS-1$ + } } diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java index a8d12aa0ca1..b88bb7b0543 100644 --- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java +++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java @@ -252,4 +252,37 @@ public void testAnnotateStoreFromBundle() throws CoreException { assertFalse("the new filter store must not be an instance of ApiFilterStore", store instanceof ApiFilterStore); //$NON-NLS-1$ assertTrue("the new filter store must be an instance of FilterStore", store instanceof FilterStore); //$NON-NLS-1$ } + + /** + * Tests that filters work correctly when the resource path uses portable string format. + * This test verifies that problems created with toPortableString() paths are correctly + * matched with filters, which is important for Tycho builds where paths may differ from IDE builds. + */ + @Test + public void testFilterWithPortablePath() throws CoreException { + IApiBaseline profile = ApiPlugin.getDefault().getApiBaselineManager().getWorkspaceBaseline(); + IApiComponent component = profile.getApiComponent(TESTING_PLUGIN_PROJECT_NAME); + assertNotNull("the testing project api component must exist", component); //$NON-NLS-1$ + IProject project = getTestingJavaProject(TESTING_PLUGIN_PROJECT_NAME).getProject(); + IResource resource = project.findMember(IPath.fromOSString("src/x/y/z/C4.java")); //$NON-NLS-1$ + assertNotNull("the resource src/x/y/z/C4.java must exist", resource); //$NON-NLS-1$ + + // Create a problem with portable path (as done in BaseApiAnalyzer) + String portablePath = resource.getProjectRelativePath().toPortableString(); + IApiProblem problem = ApiProblemFactory.newApiProblem(portablePath, + null, null, null, null, -1, -1, -1, IApiProblem.CATEGORY_USAGE, 0, RestrictionModifiers.NO_IMPLEMENT, + IApiProblem.NO_FLAGS); + + // Add filter + IApiFilterStore store = component.getFilterStore(); + store.addFiltersFor(new IApiProblem[] { problem }); + + // Create another problem with the same portable path to test isFiltered + IApiProblem testProblem = ApiProblemFactory.newApiProblem(portablePath, + null, null, null, null, -1, -1, -1, IApiProblem.CATEGORY_USAGE, 0, RestrictionModifiers.NO_IMPLEMENT, + IApiProblem.NO_FLAGS); + + // This should work with the fix (fromPortableString instead of fromOSString) + assertTrue("problem with portable path should be filtered", store.isFiltered(testProblem)); //$NON-NLS-1$ + } } diff --git a/apitools/org.eclipse.pde.api.tools.tests/test-builder/leak/interface/Etest15.java b/apitools/org.eclipse.pde.api.tools.tests/test-builder/leak/interface/Etest15.java new file mode 100644 index 00000000000..eefdb57d351 --- /dev/null +++ b/apitools/org.eclipse.pde.api.tools.tests/test-builder/leak/interface/Etest15.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2025 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 + *******************************************************************************/ +package x.y.z; + +/** + * Test case for static inner class implementing parent interface. + * This pattern should NOT produce a leak warning as the inner class + * is referencing its enclosing type which has the same visibility. + */ +public interface Etest15 { + + /** + * Static inner class implementing the parent interface. + * This should not be reported as leaking non-API interface. + */ + static class Etest15Impl implements Etest15 { + // Implementation + } +} diff --git a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiToolsLabelProvider.java b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiToolsLabelProvider.java index a1fa232b901..8470dd3397b 100644 --- a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiToolsLabelProvider.java +++ b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiToolsLabelProvider.java @@ -132,7 +132,7 @@ private Image getApiProblemElementImage(IApiProblem problem) { case IDelta.API_BASELINE_ELEMENT_TYPE: return getBaselineImage(); case IDelta.API_COMPONENT_ELEMENT_TYPE: { - IPath path = IPath.fromOSString(problem.getResourcePath()); + IPath path = IPath.fromPortableString(problem.getResourcePath()); // try to find the component via the resource handle IResource res = ResourcesPlugin.getWorkspace().getRoot().findMember(path); if (res != null) { diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiFilterStore.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiFilterStore.java index 5fc9306050e..a430f3edada 100644 --- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiFilterStore.java +++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiFilterStore.java @@ -188,7 +188,7 @@ public synchronized void addFilters(IApiProblemFilter[] filters) { if (resourcePath == null) { continue; } - IResource resource = fProject.getProject().findMember(IPath.fromOSString(resourcePath)); + IResource resource = fProject.getProject().findMember(IPath.fromPortableString(resourcePath)); if (resource == null) { continue; } @@ -247,7 +247,7 @@ public synchronized boolean isFiltered(IApiProblem problem) { if (resourcePath == null) { return false; } - IResource resource = fProject.getProject().findMember(IPath.fromOSString(resourcePath)); + IResource resource = fProject.getProject().findMember(IPath.fromPortableString(resourcePath)); if (resource == null) { if (ApiPlugin.DEBUG_FILTER_STORE) { System.out.println("no resource exists: [" + resourcePath + "]"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -317,7 +317,7 @@ public synchronized boolean removeFilters(IApiProblemFilter[] filters) { if (resourcePath == null) { continue; } - IResource resource = fProject.getProject().findMember(IPath.fromOSString(resourcePath)); + IResource resource = fProject.getProject().findMember(IPath.fromPortableString(resourcePath)); if (resource == null) { resource = fProject.getProject().getFile(resourcePath); } @@ -499,7 +499,7 @@ protected synchronized void internalAddFilters(IApiProblem[] problems, String[] if (resourcePath == null) { continue; } - IResource resource = fProject.getProject().findMember(IPath.fromOSString(resourcePath)); + IResource resource = fProject.getProject().findMember(IPath.fromPortableString(resourcePath)); if (resource == null) { continue; } diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java index 08206b96900..8c90bef874e 100644 --- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java +++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java @@ -208,7 +208,7 @@ protected boolean problemsMatch(IApiProblem filterProblem, IApiProblem problem) // one is missing a path they may still be equal String problemPath = problem.getResourcePath(); String filterProblemPath = filterProblem.getResourcePath(); - if (problemPath != null && filterProblemPath != null && !(IPath.fromOSString(problemPath).equals(IPath.fromOSString(filterProblemPath)))) { + if (problemPath != null && filterProblemPath != null && !(IPath.fromPortableString(problemPath).equals(IPath.fromPortableString(filterProblemPath)))) { return false; } String problemTypeName = problem.getTypeName(); diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java index 5070c52b71c..62f4fb4712f 100644 --- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java +++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java @@ -1150,7 +1150,7 @@ IResource resolveResource(IApiProblem problem) { if (resourcePath == null) { return null; } - IResource resource = currentproject.findMember(IPath.fromOSString(resourcePath)); + IResource resource = currentproject.findMember(IPath.fromPortableString(resourcePath)); if (resource == null) { // might be re-exported try to look it up IJavaProject jp = JavaCore.create(currentproject); diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java index 6053d28e711..57f58de90cb 100644 --- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java +++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java @@ -1010,6 +1010,15 @@ protected boolean consider(Reference ref) { } return false; } + // Also check the reverse: if this type is a member of the referenced type + // (e.g., inner class referencing its enclosing type) + if (fType.getName().startsWith(referencedTypeName)) { + int refLength = referencedTypeName.length(); + if (fType.getName().length() > refLength && fType.getName().charAt(refLength) == '$') { + // This is a member type referencing its enclosing type - exclude it + return false; + } + } return true; }