diff --git a/ui/org.eclipse.pde.ui/plugin.properties b/ui/org.eclipse.pde.ui/plugin.properties index 66cb7c89f7..0279b9f863 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 4a34c0b6ac..b8fba01309 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 0000000000..361bb872f1 --- /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