diff --git a/ui/org.eclipse.pde.ui/plugin.properties b/ui/org.eclipse.pde.ui/plugin.properties index 66cb7c89f7..7c58d3d329 100644 --- a/ui/org.eclipse.pde.ui/plugin.properties +++ b/ui/org.eclipse.pde.ui/plugin.properties @@ -158,6 +158,8 @@ 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 +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 4a34c0b6ac..10d9d1a201 100644 --- a/ui/org.eclipse.pde.ui/plugin.xml +++ b/ui/org.eclipse.pde.ui/plugin.xml @@ -1225,6 +1225,38 @@ + + + + + + + + + + + + + + + + + + + + packages, + IProgressMonitor monitor) throws CoreException { + List packageNames = packages.stream().map(IPackageFragment::getElementName).toList(); + if (packageNames.isEmpty()) { + return null; + } try { Bundle bundle = getBundle(file, monitor); if (bundle == null) { @@ -82,8 +88,8 @@ public static Change createEmptyPackageChange(IFile file, String packageName, IP bundle.getModel().addModelChangedListener(listener); BasePackageHeader header = (BasePackageHeader) bundle.getManifestHeader(Constants.EXPORT_PACKAGE); - if (header != null && header.hasPackage(packageName)) { - removePackage(header, packageName); + if (header != null) { + packageNames.stream().filter(header::hasPackage).forEach(name -> removePackage(header, name)); } TextEdit[] operations = listener.getTextOperations(); if (operations.length > 0) { 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 0000000000..9d4e1aa349 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestPackageDeleteParticipant.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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.HashSet; +import java.util.Set; + +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.IPackageFragment; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments; +import org.eclipse.pde.internal.core.WorkspaceModelManager; +import org.eclipse.pde.internal.core.project.PDEProject; +import org.eclipse.pde.internal.ui.PDEPlugin; +import org.eclipse.pde.internal.ui.PDEUIMessages; + +/** + * Handles package deletion in PDE plugin projects. Updates the bundle's + * MANIFEST.MF after deleting packages. + */ +public class ManifestPackageDeleteParticipant extends PDEDeleteParticipant { + + private IProject fProject; + private Set fPackages = new HashSet<>(); + + @Override + protected boolean initialize(Object element) { + IPackageFragment fragment = (IPackageFragment) element; + try { + if (!fragment.containsJavaResources()) { + return false; + } + } catch (JavaModelException e) { + PDEPlugin.logException(e); + } + fProject = fragment.getJavaProject().getProject(); + if (WorkspaceModelManager.isPluginProject(fProject)) { + fPackages.add(fragment); + return true; + } + return false; + } + + @Override + public void addElement(Object element, RefactoringArguments arguments) { + fPackages.add((IPackageFragment) element); + } + + @Override + public String getName() { + return PDEUIMessages.ManifestPackageDeleteParticipant_packageDelete; + } + + @Override + protected void addChanges(CompositeChange result, IProgressMonitor pm) throws CoreException { + IFile file = PDEProject.getManifest(fProject); + if (file.exists()) { + Change change = BundleManifestChange.createDeletePackagesChange(file, fPackages, pm); + if (change != null) { + result.add(change); + } + } + } + +} diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeDeleteParticipant.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeDeleteParticipant.java new file mode 100644 index 0000000000..ada50e5754 --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeDeleteParticipant.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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 java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +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.participants.RefactoringArguments; +import org.eclipse.pde.internal.core.WorkspaceModelManager; +import org.eclipse.pde.internal.core.project.PDEProject; +import org.eclipse.pde.internal.ui.PDEUIMessages; + +/** + * Handles class deletion in PDE plugin projects. Updates the bundle's MANIFEST.MF + * when a package becomes empty after deleting types. + */ +public class ManifestTypeDeleteParticipant extends PDEDeleteParticipant { + + private IProject fProject; + private Set fTypes = new LinkedHashSet<>(); + + @Override + protected boolean initialize(Object element) { + IType type = (IType) element; + IJavaProject javaProject = type.getJavaProject(); + if (javaProject != null) { + fProject = javaProject.getProject(); + if (WorkspaceModelManager.isPluginProject(fProject)) { + fTypes.add(type); + return true; + } + } + return false; + } + + @Override + public void addElement(Object element, RefactoringArguments arguments) { + IType type = (IType) element; + fTypes.add(type); + } + + @Override + public String getName() { + return PDEUIMessages.ManifestTypeDeleteParticipant_composite; + } + + @Override + protected void addChanges(CompositeChange result, IProgressMonitor pm) throws CoreException { + IFile file = PDEProject.getManifest(fProject); + if (!file.exists()) { + return; + } + // Group deleted types by package and collect their compilation units + Map> deletedCUsByPackage = new HashMap<>(); + for (IType type : fTypes) { + IPackageFragment pkg = type.getPackageFragment(); + ICompilationUnit cu = type.getCompilationUnit(); + if (cu != null) { + deletedCUsByPackage.computeIfAbsent(pkg, k -> new HashSet<>()).add(cu); + } + } + // Check each package to see if it becomes empty after deletion + List emptiedPackages = deletedCUsByPackage.entrySet().stream().filter(e -> { + IPackageFragment pkg = e.getKey(); + Set deletedCUs = e.getValue(); + try { + return willPackageBeEmpty(pkg, deletedCUs); + } catch (CoreException ex) { + return false; + } + }).map(Map.Entry::getKey).toList(); + + Change change = BundleManifestChange.createDeletePackagesChange(file, emptiedPackages, pm); + if (change != null) { + result.add(change); + } + + } + + /** + * Checks if a package will be empty after deleting the specified compilation units. + * + * @param pkg the package to check + * @param deletedCUs the compilation units 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, Set deletedCUs) throws CoreException { + // Check for non-Java resources (properties files, XML files, etc.) + Object[] nonJavaResources = pkg.getNonJavaResources(); + if (nonJavaResources != null && nonJavaResources.length > 0) { + return false; + } + // Check if any compilation unit in the package is NOT being deleted + ICompilationUnit[] compilationUnits = pkg.getCompilationUnits(); + for (ICompilationUnit cu : compilationUnits) { + if (!deletedCUs.contains(cu)) { + // This compilation unit is not being deleted, so package is not + // empty + return false; + } + } + return true; + } + +} diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeMoveParticipant.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeMoveParticipant.java index f32dba6bc3..3fcaa40577 100644 --- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeMoveParticipant.java +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/ManifestTypeMoveParticipant.java @@ -115,7 +115,7 @@ protected void addChange(CompositeChange result, IProgressMonitor pm) throws Cor int totalFiles = children.length; int movingCount = movingTypes.size(); if (totalFiles == movingCount) { - Change change = BundleManifestChange.createEmptyPackageChange(file, pkg.getElementName(), pm); + Change change = BundleManifestChange.createDeletePackagesChange(file, List.of(pkg), pm); if (change != null) { result.add(change); } 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 0000000000..6606be753a --- /dev/null +++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/refactoring/PDEDeleteParticipant.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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 org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +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; + +public abstract class PDEDeleteParticipant extends DeleteParticipant implements ISharableParticipant { + + @Override + public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) + throws OperationCanceledException { + return new RefactoringStatus(); + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + CompositeChange result = new CompositeChange(getName()); + addChanges(result, pm); + return result.getChildren().length == 0 ? null : result; + } + + protected abstract void addChanges(CompositeChange result, IProgressMonitor pm) throws CoreException; + +}