From 5cbdda6249cdcaa186f483bb2a5008f48f655688 Mon Sep 17 00:00:00 2001 From: Neha Burnwal Date: Thu, 16 Apr 2026 22:36:15 +0530 Subject: [PATCH 1/2] Handle class deletion scenario for cleaning up empty exported packages --- ui/org.eclipse.pde.ui/plugin.properties | 1 + ui/org.eclipse.pde.ui/plugin.xml | 16 ++ .../pde/internal/ui/PDEUIMessages.java | 2 + .../pde/internal/ui/pderesources.properties | 1 + .../ManifestTypeDeleteParticipant.java | 139 ++++++++++++++++++ .../ui/refactoring/PDEDeleteParticipant.java | 64 ++++++++ 6 files changed, 223 insertions(+) create mode 100644 ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeDeleteParticipant.java create mode 100644 ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/PDEDeleteParticipant.java diff --git a/ui/org.eclipse.pde.ui/plugin.properties b/ui/org.eclipse.pde.ui/plugin.properties index 66cb7c89f73..0279b9f8632 100644 --- a/ui/org.eclipse.pde.ui/plugin.properties +++ b/ui/org.eclipse.pde.ui/plugin.properties @@ -158,6 +158,7 @@ rename.type.participant = Manifest Rename Type Participant rename.package.participant = Manifest Rename Package Participant move.type.participant = Manifest Move Type Participant move.package.participant = Manifest Move Package Participant +delete.type.participant = Manifest Delete Type Participant queryParticipant.name.0 = PDE Java Search Participant new.profile.name = Target Definition diff --git a/ui/org.eclipse.pde.ui/plugin.xml b/ui/org.eclipse.pde.ui/plugin.xml index 4a34c0b6acd..b8fba013098 100644 --- a/ui/org.eclipse.pde.ui/plugin.xml +++ b/ui/org.eclipse.pde.ui/plugin.xml @@ -1225,6 +1225,22 @@ + + + + + + + + + + (); + fElements.put(element, type.getFullyQualifiedName()); + return true; + } + } + return false; + } + + @Override + public String getName() { + return PDEUIMessages.ManifestTypeDeleteParticipant_composite; + } + + @Override + protected void addChange(CompositeChange result, IProgressMonitor pm) throws CoreException { + IFile file = PDEProject.getManifest(fProject); + if (!file.exists()) { + return; + } + + Map> deletedByPackage = new HashMap<>(); + fElements.forEach((element, fullyQualifiedName) -> { + if (element instanceof IType type) { + IPackageFragment pkg = type.getPackageFragment(); + deletedByPackage.computeIfAbsent(pkg, k -> new ArrayList<>()).add(type); + } + }); + // Check each package to see if it becomes empty after deletion + for (Map.Entry> entry : deletedByPackage.entrySet()) { + IPackageFragment pkg = entry.getKey(); + List deletedTypes = entry.getValue(); + + try { + if (willPackageBeEmpty(pkg, deletedTypes)) { + Change change = BundleManifestChange.createEmptyPackageChange(file, pkg.getElementName(), pm); + if (change != null) { + result.add(change); + } + } + } catch (CoreException e) { + e.printStackTrace(); + } + } + } + + /** + * Checks if a package will be empty after deleting the specified types. + * @param pkg the package to check + * @param deletedTypes the types being deleted + * @return true if the package will be empty after deletion + * @throws CoreException if an error occurs accessing package contents + */ + private boolean willPackageBeEmpty(IPackageFragment pkg, List deletedTypes) throws CoreException { + IJavaElement[] javaChildren = pkg.getChildren(); + if (javaChildren.length > deletedTypes.size()) { + return false; + } + // Check for non-Java resources (properties files, XML files, etc.) + Object[] nonJavaResources = pkg.getNonJavaResources(); + if (nonJavaResources != null && nonJavaResources.length > 0) { + return false; + } + + Set deletedJavaFileNames = new HashSet<>(); + for (IType type : deletedTypes) { + // Get the compilation unit (the .java file) containing this type + if (type.getCompilationUnit() != null) { + deletedJavaFileNames.add(type.getCompilationUnit().getElementName()); + } + } + // Check the underlying folder for any OTHER files + IResource resource = pkg.getCorrespondingResource(); + if (resource instanceof IFolder folder) { + IResource[] members = folder.members(); + for (IResource member : members) { + if (member instanceof IFile memberFile) { + String fileName = memberFile.getName(); + String extension = memberFile.getFileExtension(); + if ("class".equals(extension)) { //$NON-NLS-1$ + continue; + } + if ("java".equals(extension) && deletedJavaFileNames.contains(fileName)) { //$NON-NLS-1$ + continue; + } + return false; + } + } + } + return true; + } + +} diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/PDEDeleteParticipant.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/PDEDeleteParticipant.java new file mode 100644 index 00000000000..361bb872f1a --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/PDEDeleteParticipant.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2026 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 org.eclipse.pde.internal.ui.refactoring; + +import java.util.HashMap; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IType; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.DeleteParticipant; +import org.eclipse.ltk.core.refactoring.participants.ISharableParticipant; +import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments; + +public abstract class PDEDeleteParticipant extends DeleteParticipant implements ISharableParticipant { + + protected IProject fProject; + protected HashMap fElements; + + @Override + public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) + throws OperationCanceledException { + return new RefactoringStatus(); + } + + @Override + public void addElement(Object element, RefactoringArguments arguments) { + if (element instanceof IType type) { + fElements.put(element, type.getFullyQualifiedName()); + } else if (element instanceof IPackageFragment pkg) { + fElements.put(element, pkg.getElementName()); + } + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + CompositeChange result = new CompositeChange(getName()); + addChange(result, pm); + return (result.getChildren().length == 0) ? null : result; + } + + /** + * @throws CoreException may be thrown by overrides + */ + protected abstract void addChange(CompositeChange result, IProgressMonitor pm) throws CoreException; + +} \ No newline at end of file From 370ec49530b21468dc190f26825b91bfcce3c9d7 Mon Sep 17 00:00:00 2001 From: Neha Burnwal Date: Fri, 24 Apr 2026 13:21:02 +0530 Subject: [PATCH 2/2] Handle package deletion scenario for cleaning up exported packages --- ui/org.eclipse.pde.ui/plugin.properties | 1 + ui/org.eclipse.pde.ui/plugin.xml | 16 +++++ .../pde/internal/ui/PDEUIMessages.java | 2 + .../pde/internal/ui/pderesources.properties | 1 + .../ui/refactoring/BundleManifestChange.java | 52 ++++++++++++++ .../ManifestPackageDeleteParticipant.java | 72 +++++++++++++++++++ 6 files changed, 144 insertions(+) create mode 100644 ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestPackageDeleteParticipant.java diff --git a/ui/org.eclipse.pde.ui/plugin.properties b/ui/org.eclipse.pde.ui/plugin.properties index 0279b9f8632..7c58d3d3295 100644 --- a/ui/org.eclipse.pde.ui/plugin.properties +++ b/ui/org.eclipse.pde.ui/plugin.properties @@ -159,6 +159,7 @@ rename.package.participant = Manifest Rename Package Participant move.type.participant = Manifest Move Type Participant move.package.participant = Manifest Move Package Participant delete.type.participant = Manifest Delete Type Participant +delete.package.participant = Manifest Delete Package Participant queryParticipant.name.0 = PDE Java Search Participant new.profile.name = Target Definition diff --git a/ui/org.eclipse.pde.ui/plugin.xml b/ui/org.eclipse.pde.ui/plugin.xml index b8fba013098..0b58a0f5506 100644 --- a/ui/org.eclipse.pde.ui/plugin.xml +++ b/ui/org.eclipse.pde.ui/plugin.xml @@ -1241,6 +1241,22 @@ + + + + + + + + + + packageNames = new ArrayList<>(); + for (Object element : elements) { + if (element instanceof IPackageFragment pkg) { + packageNames.add(pkg.getElementName()); + } + } + + if (packageNames.isEmpty()) { + return null; + } + + if (packageNames.size() == 1) { + return createEmptyPackageChange(file, packageNames.get(0), monitor); + } + + try { + Bundle bundle = getBundle(file, monitor); + if (bundle == null) { + return null; + } + + BundleModel model = (BundleModel) bundle.getModel(); + BundleTextChangeListener listener = new BundleTextChangeListener(model.getDocument()); + bundle.getModel().addModelChangedListener(listener); + + BasePackageHeader header = (BasePackageHeader) bundle.getManifestHeader(Constants.EXPORT_PACKAGE); + if (header != null) { + for (String packageName : packageNames) { + if (header.hasPackage(packageName)) { + removePackage(header, packageName); + } + } + } + + TextEdit[] operations = listener.getTextOperations(); + if (operations.length > 0) { + TextFileChange change = new TextFileChange("", file); //$NON-NLS-1$ + MultiTextEdit multi = new MultiTextEdit(); + multi.addChildren(operations); + change.setEdit(multi); + PDEModelUtility.setChangeTextType(change, file); + return change; + } + } catch (CoreException | MalformedTreeException e) { + } finally { + FileBuffers.getTextFileBufferManager().disconnect(file.getFullPath(), LocationKind.NORMALIZE, monitor); + } + return null; + } + public static MoveFromChange createMovePackageChange(IFile file, Object[] elements, IProgressMonitor monitor) throws CoreException { try { Bundle bundle = getBundle(file, monitor); diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestPackageDeleteParticipant.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestPackageDeleteParticipant.java new file mode 100644 index 00000000000..b2070f4a70e --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestPackageDeleteParticipant.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2026 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 org.eclipse.pde.internal.ui.refactoring; + +import java.util.HashMap; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.pde.internal.core.WorkspaceModelManager; +import org.eclipse.pde.internal.core.project.PDEProject; +import org.eclipse.pde.internal.ui.PDEUIMessages; + +public class ManifestPackageDeleteParticipant extends PDEDeleteParticipant { + + @Override + protected boolean initialize(Object element) { + try { + if (element instanceof IPackageFragment fragment) { + if (!fragment.containsJavaResources()) { + return false; + } + IJavaProject javaProject = (IJavaProject) fragment.getAncestor(IJavaElement.JAVA_PROJECT); + IProject project = javaProject.getProject(); + if (WorkspaceModelManager.isPluginProject(project)) { + fProject = javaProject.getProject(); + fElements = new HashMap<>(); + fElements.put(fragment, fragment.getElementName()); + return true; + } + } + } catch (JavaModelException e) { + // Log error but return false to skip this participant + } + return false; + } + + @Override + public String getName() { + return PDEUIMessages.ManifestPackageDeleteParticipant_packageDelete; + } + + @Override + protected void addChange(CompositeChange result, IProgressMonitor pm) throws CoreException { + IFile file = PDEProject.getManifest(fProject); + if (file.exists()) { + Change change = BundleManifestChange.createMultiplePackageDeleteChange(file, fElements.keySet().toArray(), pm); + if (change != null) { + result.add(change); + } + } + } + +}