| /******************************************************************************* |
| * Copyright (c) 2000, 2020 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.jdt.internal.corext.refactoring.reorg; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Platform; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceVisitor; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; |
| |
| import org.eclipse.core.filebuffers.FileBuffers; |
| import org.eclipse.core.filebuffers.ITextFileBuffer; |
| import org.eclipse.core.filebuffers.LocationKind; |
| |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; |
| import org.eclipse.ltk.core.refactoring.RefactoringStatus; |
| import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; |
| import org.eclipse.ltk.core.refactoring.participants.DeleteProcessor; |
| import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; |
| import org.eclipse.ltk.core.refactoring.participants.ResourceChangeChecker; |
| import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; |
| |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IField; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IMethod; |
| import org.eclipse.jdt.core.IPackageFragment; |
| import org.eclipse.jdt.core.IPackageFragmentRoot; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.refactoring.IJavaRefactorings; |
| import org.eclipse.jdt.core.refactoring.descriptors.DeleteDescriptor; |
| import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; |
| |
| import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; |
| import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; |
| import org.eclipse.jdt.internal.corext.codemanipulation.GetterSetterUtil; |
| import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; |
| import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments; |
| import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil; |
| import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTester; |
| import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; |
| import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange; |
| import org.eclipse.jdt.internal.corext.refactoring.participants.JavaProcessors; |
| import org.eclipse.jdt.internal.corext.refactoring.participants.ResourceProcessors; |
| import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil; |
| import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil; |
| import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| import org.eclipse.jdt.internal.corext.util.Resources; |
| |
| import org.eclipse.jdt.ui.JavaElementLabels; |
| import org.eclipse.jdt.ui.refactoring.IRefactoringProcessorIds; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| |
| public final class JavaDeleteProcessor extends DeleteProcessor { |
| |
| private static final String ATTRIBUTE_RESOURCES= "resources"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_ELEMENTS= "elements"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_SUGGEST_ACCESSORS= "accessors"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_DELETE_SUBPACKAGES= "subPackages"; //$NON-NLS-1$ |
| |
| private boolean fAccessorsDeleted; |
| private boolean fWasCanceled; |
| private boolean fSuggestGetterSetterDeletion; |
| private Object[] fElements; |
| private IResource[] fResources; |
| private IJavaElement[] fJavaElements; |
| private IReorgQueries fDeleteQueries; |
| private DeleteModifications fDeleteModifications; |
| |
| private Change fDeleteChange; |
| private boolean fDeleteSubPackages; |
| |
| public JavaDeleteProcessor(Object[] elements) { |
| fElements= elements; |
| if (fElements != null) { |
| fResources= RefactoringAvailabilityTester.getResources(elements); |
| fJavaElements= RefactoringAvailabilityTester.getJavaElements(elements); |
| } |
| fSuggestGetterSetterDeletion= true; |
| fDeleteSubPackages= false; |
| fWasCanceled= false; |
| } |
| |
| public JavaDeleteProcessor(JavaRefactoringArguments arguments, RefactoringStatus status) { |
| this(null); |
| RefactoringStatus initStatus= initialize(arguments); |
| status.merge(initStatus); |
| } |
| |
| @Override |
| public String getIdentifier() { |
| return IRefactoringProcessorIds.DELETE_PROCESSOR; |
| } |
| |
| @Override |
| public boolean isApplicable() throws CoreException { |
| if (fElements.length == 0) |
| return false; |
| if (fElements.length != fResources.length + fJavaElements.length) |
| return false; |
| for (IResource resource : fResources) { |
| if (!RefactoringAvailabilityTester.isDeleteAvailable(resource)) { |
| return false; |
| } |
| } |
| for (IJavaElement javaElement : fJavaElements) { |
| if (!RefactoringAvailabilityTester.isDeleteAvailable(javaElement)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public boolean needsProgressMonitor() { |
| if (fResources != null && fResources.length > 0) |
| return true; |
| if (fJavaElements != null) { |
| for (IJavaElement javaElement : fJavaElements) { |
| int type= javaElement.getElementType(); |
| if (type <= IJavaElement.CLASS_FILE) |
| return true; |
| } |
| } |
| return false; |
| |
| } |
| |
| @Override |
| public String getProcessorName() { |
| return RefactoringCoreMessages.DeleteRefactoring_7; |
| } |
| |
| @Override |
| public Object[] getElements() { |
| return fElements; |
| } |
| |
| @Override |
| public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants shared) throws CoreException { |
| return fDeleteModifications.loadParticipants(status, this, getAffectedProjectNatures(), shared); |
| } |
| |
| private String[] getAffectedProjectNatures() throws CoreException { |
| String[] jNatures= JavaProcessors.computeAffectedNaturs(fJavaElements); |
| String[] rNatures= ResourceProcessors.computeAffectedNatures(fResources); |
| Set<String> result= new HashSet<>(Arrays.asList(jNatures)); |
| result.addAll(Arrays.asList(rNatures)); |
| return result.toArray(new String[result.size()]); |
| } |
| |
| /* |
| * This has to be customizable because when drag and drop is performed on a field, |
| * you don't want to suggest deleting getter/setter if only the field was moved. |
| */ |
| public void setSuggestGetterSetterDeletion(boolean suggest){ |
| fSuggestGetterSetterDeletion= suggest; |
| } |
| |
| public void setDeleteSubPackages(boolean selection) { |
| fDeleteSubPackages= selection; |
| } |
| |
| public boolean getDeleteSubPackages() { |
| return fDeleteSubPackages; |
| } |
| |
| public boolean hasSubPackagesToDelete() { |
| try { |
| for (IJavaElement javaElement : fJavaElements) { |
| if (javaElement instanceof IPackageFragment) { |
| IPackageFragment packageFragment = (IPackageFragment) javaElement; |
| if (packageFragment.isDefaultPackage()) |
| continue; // see bug 132576 (can remove this if(..) continue; statement when bug is fixed) |
| if (packageFragment.hasSubpackages()) |
| return true; |
| } |
| } |
| } catch (JavaModelException e) { |
| JavaPlugin.log(e); |
| } |
| return false; |
| } |
| |
| public void setQueries(IReorgQueries queries){ |
| Assert.isNotNull(queries); |
| fDeleteQueries= queries; |
| } |
| |
| public IJavaElement[] getJavaElementsToDelete(){ |
| return fJavaElements; |
| } |
| |
| public boolean wasCanceled() { |
| return fWasCanceled; |
| } |
| |
| public IResource[] getResourcesToDelete(){ |
| return fResources; |
| } |
| |
| @Override |
| public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { |
| Assert.isNotNull(fDeleteQueries);//must be set before checking activation |
| RefactoringStatus result= new RefactoringStatus(); |
| IResource[] resources= ReorgUtils.getNotLinked(fResources); |
| IStatus status= Resources.checkInSync(resources); |
| if (!status.isOK()) { |
| boolean autoRefresh= Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, false, null); |
| if (autoRefresh) { |
| for (IResource resource : resources) { |
| try { |
| resource.refreshLocal(IResource.DEPTH_INFINITE, pm); |
| } catch (CoreException e) { |
| break; |
| } |
| status= Resources.checkInSync(resources); |
| } |
| } |
| } |
| result.merge(RefactoringStatus.create(status)); |
| IResource[] javaResources= ReorgUtils.getResources(fJavaElements); |
| resources= ReorgUtils.getNotNulls(javaResources); |
| status= Resources.checkInSync(resources); |
| if (!status.isOK()) { |
| boolean autoRefresh= Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, false, null); |
| if (autoRefresh) { |
| for (IResource resource : resources) { |
| try { |
| resource.refreshLocal(IResource.DEPTH_INFINITE, pm); |
| } catch (CoreException e) { |
| break; |
| } |
| status= Resources.checkInSync(resources); |
| } |
| } |
| } |
| result.merge(RefactoringStatus.create(status)); |
| for (IJavaElement element : fJavaElements) { |
| if (element instanceof IType && ((IType)element).isAnonymous()) { |
| // work around for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=44450 |
| // result.addFatalError("Currently, there isn't any support to delete an anonymous type."); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException { |
| pm.beginTask(RefactoringCoreMessages.DeleteRefactoring_1, 1); |
| try{ |
| fWasCanceled= false; |
| RefactoringStatus result= new RefactoringStatus(); |
| |
| recalculateElementsToDelete(); |
| |
| checkDirtyCompilationUnits(result); |
| checkDirtyResources(result); |
| fDeleteModifications= new DeleteModifications(); |
| fDeleteModifications.delete(fResources); |
| fDeleteModifications.delete(fJavaElements); |
| List<IResource> packageDeletes= fDeleteModifications.postProcess(pm); |
| |
| TextChangeManager manager= new TextChangeManager(); |
| fDeleteChange= DeleteChangeCreator.createDeleteChange(manager, fResources, fJavaElements, getProcessorName(), packageDeletes); |
| |
| ResourceChangeChecker checker= context.getChecker(ResourceChangeChecker.class); |
| IResourceChangeDescriptionFactory deltaFactory= checker.getDeltaFactory(); |
| fDeleteModifications.buildDelta(deltaFactory); |
| for (IFile file : ResourceUtil.getFiles(manager.getAllCompilationUnits())) { |
| deltaFactory.change(file); |
| } |
| return result; |
| } catch (OperationCanceledException e) { |
| fWasCanceled= true; |
| throw e; |
| } finally{ |
| pm.done(); |
| } |
| } |
| |
| private void checkDirtyCompilationUnits(RefactoringStatus result) throws CoreException { |
| if (fJavaElements == null || fJavaElements.length == 0) |
| return; |
| for (IJavaElement element : fJavaElements) { |
| if (element instanceof ICompilationUnit) { |
| checkDirtyCompilationUnit(result, (ICompilationUnit)element); |
| } else if (element instanceof IPackageFragment) { |
| for (ICompilationUnit unit : ((IPackageFragment)element).getCompilationUnits()) { |
| checkDirtyCompilationUnit(result, unit); |
| } |
| } |
| } |
| } |
| |
| private void checkDirtyCompilationUnit(RefactoringStatus result, ICompilationUnit cunit) { |
| IResource resource= cunit.getResource(); |
| if (resource == null || resource.getType() != IResource.FILE) |
| return; |
| checkDirtyFile(result, (IFile)resource); |
| } |
| |
| private void checkDirtyResources(final RefactoringStatus result) throws CoreException { |
| for (IResource resource : fResources) { |
| resource.accept((IResourceVisitor) visitedResource -> { |
| if (visitedResource instanceof IFile) { |
| checkDirtyFile(result, (IFile)visitedResource); |
| } |
| return true; |
| }, IResource.DEPTH_INFINITE, false); |
| } |
| } |
| |
| private void checkDirtyFile(RefactoringStatus result, IFile file) { |
| if (file == null || !file.exists()) |
| return; |
| ITextFileBuffer buffer= FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); |
| if (buffer != null && buffer.isDirty()) { |
| if (buffer.isStateValidated() && buffer.isSynchronized()) { |
| result.addWarning(Messages.format( |
| RefactoringCoreMessages.JavaDeleteProcessor_unsaved_changes, |
| BasicElementLabels.getPathLabel(file.getFullPath(), false))); |
| } else { |
| result.addFatalError(Messages.format( |
| RefactoringCoreMessages.JavaDeleteProcessor_unsaved_changes, |
| BasicElementLabels.getPathLabel(file.getFullPath(), false))); |
| } |
| } |
| } |
| |
| /* |
| * The set of elements that will eventually be deleted may be very different from the set |
| * originally selected - there may be fewer, more or different elements. |
| * This method is used to calculate the set of elements that will be deleted - if necessary, |
| * it asks the user. |
| */ |
| private void recalculateElementsToDelete() throws CoreException { |
| //the sequence is critical here |
| fAccessorsDeleted= false; |
| if (fDeleteSubPackages) /* add subpackages first, to allow removing elements with parents in selection etc. */ |
| addSubPackages(); |
| |
| removeElementsWithParentsInSelection(); /*ask before adding empty cus - you don't want to ask if you, for example delete |
| *the package, in which the cus live*/ |
| removeUnconfirmedFoldersThatContainSourceFolders(); /* a selected folder may be a parent of a source folder |
| * we must inform the user about it and ask if ok to delete the folder*/ |
| removeUnconfirmedReferencedArchives(); |
| addEmptyCusToDelete(); |
| removeJavaElementsChildrenOfJavaElements();/*because adding cus may create elements (types in cus) |
| *whose parents are in selection*/ |
| confirmDeletingReadOnly(); /*after empty cus - you want to ask for all cus that are to be deleted*/ |
| |
| if (fSuggestGetterSetterDeletion) |
| addGettersSetters();/*at the end - this cannot invalidate anything*/ |
| |
| addDeletableParentPackagesOnPackageDeletion(); /* do not change the sequence in fJavaElements after this method */ |
| } |
| |
| /** |
| * Adds all subpackages of the selected packages to the list of items to be |
| * deleted. |
| * |
| * @throws JavaModelException should not happen |
| */ |
| private void addSubPackages() throws JavaModelException { |
| |
| final Set<IJavaElement> javaElements= new HashSet<>(); |
| for (IJavaElement javaElement : fJavaElements) { |
| if (javaElement instanceof IPackageFragment) { |
| javaElements.addAll(Arrays.asList(JavaElementUtil.getPackageAndSubpackages((IPackageFragment) javaElement))); |
| } else { |
| javaElements.add(javaElement); |
| } |
| } |
| |
| fJavaElements= javaElements.toArray(new IJavaElement[javaElements.size()]); |
| } |
| |
| /** |
| * Add deletable parent packages to the list of items to delete. |
| * |
| * @throws CoreException should not happen |
| */ |
| private void addDeletableParentPackagesOnPackageDeletion() throws CoreException { |
| |
| @SuppressWarnings("unchecked") |
| final List<IPackageFragment> initialPackagesToDelete= (List<IPackageFragment>) ReorgUtils.getElementsOfType(fJavaElements, IJavaElement.PACKAGE_FRAGMENT); |
| |
| if (initialPackagesToDelete.isEmpty()) |
| return; |
| |
| // Move from inner to outer packages |
| Collections.sort(initialPackagesToDelete, (one, two) -> two.getElementName().compareTo(one.getElementName())); |
| |
| // Get resources and java elements which will be deleted as well |
| final Set<IResource> deletedChildren= new HashSet<>(Arrays.asList(fResources)); |
| for (IJavaElement javaElement : fJavaElements) { |
| if (!ReorgUtils.isInsideCompilationUnit(javaElement)) { |
| deletedChildren.add(javaElement.getResource()); |
| } |
| } |
| |
| // new package list in the right sequence |
| final Set<IPackageFragment>allFragmentsToDelete= new LinkedHashSet<>(); |
| |
| IsCompletelySelected isCompletelySelected = new IsCompletelySelected(initialPackagesToDelete); |
| Set<IPackageFragment> packagesToDelete = new HashSet<>(initialPackagesToDelete); // or use binary search, since the array is sorted? |
| |
| for (IPackageFragment currentPackageFragment : initialPackagesToDelete) { |
| // The package will at least be cleared |
| allFragmentsToDelete.add(currentPackageFragment); |
| |
| if (isCompletelySelected.test(currentPackageFragment)) { |
| |
| final IPackageFragment parent= JavaElementUtil.getParentSubpackage(currentPackageFragment); |
| |
| if (parent != null && !packagesToDelete.contains(parent)) { |
| |
| final List<IPackageFragment>emptyParents= new ArrayList<>(); |
| addDeletableParentPackages(parent, packagesToDelete, deletedChildren, emptyParents); |
| |
| // Add parents in the right sequence (inner to outer) |
| allFragmentsToDelete.addAll(emptyParents); |
| } |
| } |
| } |
| |
| // Remove resources in deleted packages; and the packages as well |
| final List<IJavaElement>javaElements= new ArrayList<>(); |
| for (IJavaElement javaElement : fJavaElements) { |
| if (!(javaElement instanceof IPackageFragment)) { |
| // remove children of deleted packages |
| final IPackageFragment frag= (IPackageFragment) javaElement.getAncestor(IJavaElement.PACKAGE_FRAGMENT); |
| if (!allFragmentsToDelete.contains(frag)) { |
| javaElements.add(javaElement); |
| } |
| } |
| } |
| // Re-add deleted packages - note the (new) sequence |
| javaElements.addAll(allFragmentsToDelete); |
| |
| // Remove resources in deleted folders |
| final List<IResource>resources= new ArrayList<>(); |
| for (IResource resource : fResources) { |
| IContainer parent= resource.getParent(); |
| if (!deletedChildren.contains(parent)) |
| resources.add(resource); |
| } |
| |
| fJavaElements= javaElements.toArray(new IJavaElement[javaElements.size()]); |
| fResources= resources.toArray(new IResource[resources.size()]); |
| } |
| |
| /** |
| * Adds deletable parent packages of the fragment "frag" to the list |
| * "deletableParentPackages"; also adds the resources of those packages to the |
| * set "resourcesToDelete". |
| * @param frag the package fragment |
| * @param initialPackagesToDelete the initial packages to delete |
| * @param resourcesToDelete result to add resources to delete |
| * @param deletableParentPackages result ro add deletable parent packages |
| * @throws CoreException should not happen |
| */ |
| private void addDeletableParentPackages(IPackageFragment frag, Collection<IPackageFragment> initialPackagesToDelete, Set<IResource> resourcesToDelete, List<IPackageFragment> deletableParentPackages) |
| throws CoreException { |
| |
| if (frag.getResource().isLinked()) { |
| final IConfirmQuery query= fDeleteQueries.createYesNoQuery(RefactoringCoreMessages.JavaDeleteProcessor_confirm_linked_folder_delete, false, IReorgQueries.CONFIRM_DELETE_LINKED_PARENT); |
| if (!query.confirm(Messages.format(RefactoringCoreMessages.JavaDeleteProcessor_delete_linked_folder_question, BasicElementLabels.getResourceName(frag.getResource())))) |
| return; |
| } |
| |
| for (IResource child : ((IContainer) frag.getResource()).members()) { |
| // Child must be a package fragment already in the list, |
| // or a resource which is deleted as well. |
| if (!resourcesToDelete.contains(child)) { |
| return; |
| } |
| } |
| resourcesToDelete.add(frag.getResource()); |
| deletableParentPackages.add(frag); |
| |
| final IPackageFragment parent= JavaElementUtil.getParentSubpackage(frag); |
| if (parent != null && !initialPackagesToDelete.contains(parent)) |
| addDeletableParentPackages(parent, initialPackagesToDelete, resourcesToDelete, deletableParentPackages); |
| } |
| |
| // ask for confirmation of deletion of all package fragment roots that are |
| // on classpaths of other projects |
| private void removeUnconfirmedReferencedArchives() throws JavaModelException { |
| String queryTitle= RefactoringCoreMessages.DeleteRefactoring_2; |
| IConfirmQuery query= fDeleteQueries.createYesYesToAllNoNoToAllQuery(queryTitle, true, IReorgQueries.CONFIRM_DELETE_REFERENCED_ARCHIVES); |
| removeUnconfirmedReferencedPackageFragmentRoots(query); |
| removeUnconfirmedReferencedArchiveFiles(query); |
| } |
| |
| private void removeUnconfirmedReferencedArchiveFiles(IConfirmQuery query) throws JavaModelException, OperationCanceledException { |
| List<IResource> filesToSkip= new ArrayList<>(0); |
| for (IResource resource : fResources) { |
| if (! (resource instanceof IFile)) |
| continue; |
| |
| IJavaProject project= JavaCore.create(resource.getProject()); |
| if (project == null || ! project.exists()) |
| continue; |
| IPackageFragmentRoot root= project.findPackageFragmentRoot(resource.getFullPath()); |
| if (root == null) |
| continue; |
| List<IJavaProject> referencingProjects= Arrays.asList(JavaElementUtil.getReferencingProjects(root)); |
| if (skipDeletingReferencedRoot(query, root, referencingProjects)) |
| filesToSkip.add(resource); |
| } |
| removeFromSetToDelete(filesToSkip.toArray(new IFile[filesToSkip.size()])); |
| } |
| |
| private void removeUnconfirmedReferencedPackageFragmentRoots(IConfirmQuery query) throws JavaModelException, OperationCanceledException { |
| List<IPackageFragmentRoot> rootsToSkip= new ArrayList<>(0); |
| for (IJavaElement element : fJavaElements) { |
| if (! (element instanceof IPackageFragmentRoot)) |
| continue; |
| IPackageFragmentRoot root= (IPackageFragmentRoot)element; |
| ArrayList<IJavaProject> referencingProjects= new ArrayList<>(Arrays.asList(JavaElementUtil.getReferencingProjects(root))); |
| referencingProjects.remove(root.getJavaProject()); |
| if (skipDeletingReferencedRoot(query, root, referencingProjects)) |
| rootsToSkip.add(root); |
| } |
| removeFromSetToDelete(rootsToSkip.toArray(new IJavaElement[rootsToSkip.size()])); |
| } |
| |
| private static boolean skipDeletingReferencedRoot(IConfirmQuery query, IPackageFragmentRoot root, List<IJavaProject> referencingProjects) throws OperationCanceledException { |
| if (referencingProjects.isEmpty() || root == null || ! root.exists() ||! root.isArchive()) |
| return false; |
| String label= JavaElementLabels.getElementLabel(root, JavaElementLabels.ALL_DEFAULT); |
| String question= referencingProjects.size() == 1 ? Messages.format(RefactoringCoreMessages.DeleteRefactoring_3_singular, label) : Messages.format( |
| RefactoringCoreMessages.DeleteRefactoring_3_plural, label); |
| return ! query.confirm(question, referencingProjects.toArray()); |
| } |
| |
| private void removeUnconfirmedFoldersThatContainSourceFolders() throws CoreException { |
| String queryTitle= RefactoringCoreMessages.DeleteRefactoring_4; |
| IConfirmQuery query= fDeleteQueries.createYesYesToAllNoNoToAllQuery(queryTitle, true, IReorgQueries.CONFIRM_DELETE_FOLDERS_CONTAINING_SOURCE_FOLDERS); |
| List<IFolder> foldersToSkip= new ArrayList<>(0); |
| for (IResource resource : fResources) { |
| if (resource instanceof IFolder){ |
| IFolder folder= (IFolder)resource; |
| if (containsSourceFolder(folder)){ |
| String question= Messages.format(RefactoringCoreMessages.DeleteRefactoring_5, BasicElementLabels.getResourceName(folder)); |
| if (! query.confirm(question)) |
| foldersToSkip.add(folder); |
| } |
| } |
| } |
| removeFromSetToDelete(foldersToSkip.toArray(new IResource[foldersToSkip.size()])); |
| } |
| |
| private static boolean containsSourceFolder(IFolder folder) throws CoreException { |
| for (IResource subFolder : folder.members()) { |
| if (!(subFolder instanceof IFolder)) { |
| continue; |
| } |
| IJavaElement element= JavaCore.create(folder); |
| if (element instanceof IPackageFragmentRoot) |
| return true; |
| if (element instanceof IPackageFragment) |
| continue; |
| if (containsSourceFolder((IFolder) subFolder)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void removeElementsWithParentsInSelection() { |
| ParentChecker parentUtil= new ParentChecker(fResources, fJavaElements); |
| parentUtil.removeElementsWithAncestorsOnList(false); |
| fJavaElements= parentUtil.getJavaElements(); |
| fResources= parentUtil.getResources(); |
| } |
| |
| private void removeJavaElementsChildrenOfJavaElements(){ |
| ParentChecker parentUtil= new ParentChecker(fResources, fJavaElements); |
| parentUtil.removeElementsWithAncestorsOnList(true); |
| fJavaElements= parentUtil.getJavaElements(); |
| } |
| |
| @Override |
| public Change createChange(IProgressMonitor monitor) throws CoreException { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.JavaDeleteProcessor_creating_change, 1); |
| final Map<String, String> arguments= new HashMap<>(); |
| final String description= fElements.length == 1 ? RefactoringCoreMessages.JavaDeleteProcessor_description_singular : RefactoringCoreMessages.JavaDeleteProcessor_description_plural; |
| final IProject resource= getSingleProject(); |
| final String project= resource != null ? resource.getName() : null; |
| final String source= project != null ? Messages.format(RefactoringCoreMessages.JavaDeleteProcessor_project_pattern, BasicElementLabels.getJavaElementName(project)) : RefactoringCoreMessages.JavaDeleteProcessor_workspace; |
| final String header= fElements.length == 1 ? Messages.format(RefactoringCoreMessages.JavaDeleteProcessor_header_singular, source) : Messages.format( |
| RefactoringCoreMessages.JavaDeleteProcessor_header_plural, new String[] { String.valueOf(fElements.length), source }); |
| int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE; |
| final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header); |
| if (fDeleteSubPackages) |
| comment.addSetting(RefactoringCoreMessages.JavaDeleteProcessor_delete_subpackages); |
| if (fAccessorsDeleted) |
| comment.addSetting(RefactoringCoreMessages.JavaDeleteProcessor_delete_accessors); |
| final DeleteDescriptor descriptor= RefactoringSignatureDescriptorFactory.createDeleteDescriptor(project, description, comment.asString(), arguments, flags); |
| arguments.put(ATTRIBUTE_DELETE_SUBPACKAGES, Boolean.toString(fDeleteSubPackages)); |
| arguments.put(ATTRIBUTE_SUGGEST_ACCESSORS, Boolean.toString(fSuggestGetterSetterDeletion)); |
| arguments.put(ATTRIBUTE_RESOURCES, Integer.toString(fResources.length)); |
| for (int offset= 0; offset < fResources.length; offset++) |
| arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + (offset + 1), JavaRefactoringDescriptorUtil.resourceToHandle(project, fResources[offset])); |
| arguments.put(ATTRIBUTE_ELEMENTS, Integer.toString(fJavaElements.length)); |
| for (int offset= 0; offset < fJavaElements.length; offset++) |
| arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + (offset + fResources.length + 1), JavaRefactoringDescriptorUtil.elementToHandle(project, fJavaElements[offset])); |
| return new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.DeleteRefactoring_7, new Change[] { fDeleteChange}); |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| private IProject getSingleProject() { |
| IProject first= null; |
| for (Object javaElement : fElements) { |
| IProject project= null; |
| if (javaElement instanceof IJavaElement) { |
| project= ((IJavaElement) javaElement).getJavaProject().getProject(); |
| } else if (javaElement instanceof IResource) { |
| project= ((IResource) javaElement).getProject(); |
| } |
| if (project != null) { |
| if (first == null) |
| first= project; |
| else if (!project.equals(first)) |
| return null; |
| } |
| } |
| return first; |
| } |
| |
| private void addToSetToDelete(IJavaElement[] newElements){ |
| fJavaElements= ReorgUtils.union(fJavaElements, newElements); |
| } |
| |
| private void removeFromSetToDelete(IResource[] resourcesToNotDelete) { |
| fResources= ReorgUtils.setMinus(fResources, resourcesToNotDelete); |
| } |
| |
| private void removeFromSetToDelete(IJavaElement[] elementsToNotDelete) { |
| fJavaElements= ReorgUtils.setMinus(fJavaElements, elementsToNotDelete); |
| } |
| |
| private void addGettersSetters() throws JavaModelException { |
| IField[] fields= getFields(fJavaElements); |
| if (fields.length == 0) |
| return; |
| //IField -> IMethod[] |
| Map<IField, IMethod[]> getterSetterMapping= createGetterSetterMapping(fields); |
| if (getterSetterMapping.isEmpty()) |
| return; |
| removeAlreadySelectedMethods(getterSetterMapping); |
| if (getterSetterMapping.isEmpty()) |
| return; |
| fAccessorsDeleted= true; |
| List<IMethod> gettersSettersToAdd= getGettersSettersToDelete(getterSetterMapping); |
| addToSetToDelete(gettersSettersToAdd.toArray(new IMethod[gettersSettersToAdd.size()])); |
| } |
| |
| private List<IMethod> getGettersSettersToDelete(Map<IField, IMethod[]> getterSetterMapping) { |
| List<IMethod> gettersSettersToAdd= new ArrayList<>(getterSetterMapping.size()); |
| String queryTitle= RefactoringCoreMessages.DeleteRefactoring_8; |
| IConfirmQuery getterSetterQuery= fDeleteQueries.createYesYesToAllNoNoToAllQuery(queryTitle, true, IReorgQueries.CONFIRM_DELETE_GETTER_SETTER); |
| for (IField field : getterSetterMapping.keySet()) { |
| Assert.isTrue(hasGetter(getterSetterMapping, field) || hasSetter(getterSetterMapping, field)); |
| String deleteGetterSetter= Messages.format(RefactoringCoreMessages.DeleteRefactoring_9, JavaElementUtil.createFieldSignature(field)); |
| if (getterSetterQuery.confirm(deleteGetterSetter)){ |
| if (hasGetter(getterSetterMapping, field)) |
| gettersSettersToAdd.add(getGetter(getterSetterMapping, field)); |
| if (hasSetter(getterSetterMapping, field)) |
| gettersSettersToAdd.add(getSetter(getterSetterMapping, field)); |
| } |
| } |
| return gettersSettersToAdd; |
| } |
| |
| //note: modifies the mapping |
| private void removeAlreadySelectedMethods(Map<IField, IMethod[]> getterSetterMapping) { |
| List<IJavaElement> elementsToDelete= Arrays.asList(fJavaElements); |
| for (Iterator<IField> iter= getterSetterMapping.keySet().iterator(); iter.hasNext();) { |
| IField field= iter.next(); |
| //remove getter |
| IMethod getter= getGetter(getterSetterMapping, field); |
| if (getter != null && elementsToDelete.contains(getter)) |
| removeGetterFromMapping(getterSetterMapping, field); |
| |
| //remove setter |
| IMethod setter= getSetter(getterSetterMapping, field); |
| if (setter != null && elementsToDelete.contains(setter)) |
| removeSetterFromMapping(getterSetterMapping, field); |
| |
| //both getter and setter already included |
| if (! hasGetter(getterSetterMapping, field) && ! hasSetter(getterSetterMapping, field)) |
| iter.remove(); |
| } |
| } |
| |
| /* |
| * IField -> IMethod[] (array of 2 - [getter, setter], one of which can be null) |
| */ |
| private static Map<IField, IMethod[]> createGetterSetterMapping(IField[] fields) throws JavaModelException { |
| Map<IField, IMethod[]> result= new HashMap<>(); |
| for (IField field : fields) { |
| IMethod[] getterSetter= getGetterSetter(field); |
| if (getterSetter != null) |
| result.put(field, getterSetter); |
| } |
| return result; |
| } |
| private static boolean hasSetter(Map<IField, IMethod[]> getterSetterMapping, IField field){ |
| return getterSetterMapping.containsKey(field) && |
| getSetter(getterSetterMapping, field) != null; |
| } |
| private static boolean hasGetter(Map<IField, IMethod[]> getterSetterMapping, IField field){ |
| return getterSetterMapping.containsKey(field) && |
| getGetter(getterSetterMapping, field) != null; |
| } |
| private static void removeGetterFromMapping(Map<IField, IMethod[]> getterSetterMapping, IField field){ |
| getterSetterMapping.get(field)[0]= null; |
| } |
| private static void removeSetterFromMapping(Map<IField, IMethod[]> getterSetterMapping, IField field){ |
| getterSetterMapping.get(field)[1]= null; |
| } |
| private static IMethod getGetter(Map<IField, IMethod[]> getterSetterMapping, IField field){ |
| return getterSetterMapping.get(field)[0]; |
| } |
| private static IMethod getSetter(Map<IField, IMethod[]> getterSetterMapping, IField field){ |
| return getterSetterMapping.get(field)[1]; |
| } |
| private static IField[] getFields(IJavaElement[] elements){ |
| List<IJavaElement> fields= new ArrayList<>(3); |
| for (IJavaElement element : elements) { |
| if (element instanceof IField) { |
| fields.add(element); |
| } |
| } |
| return fields.toArray(new IField[fields.size()]); |
| } |
| |
| /* |
| * returns an array of 2 [getter, setter] or null if no getter or setter exists |
| */ |
| private static IMethod[] getGetterSetter(IField field) throws JavaModelException { |
| IMethod getter= GetterSetterUtil.getGetter(field); |
| IMethod setter= GetterSetterUtil.getSetter(field); |
| if (getter != null && getter.exists() || setter != null && setter.exists()) |
| return new IMethod[]{getter, setter}; |
| else |
| return null; |
| } |
| |
| //----------- read-only confirmation business ------ |
| private void confirmDeletingReadOnly() throws CoreException { |
| if (! ReadOnlyResourceFinder.confirmDeleteOfReadOnlyElements(fJavaElements, fResources, fDeleteQueries)) |
| throw new OperationCanceledException(); //saying 'no' to this one is like cancelling the whole operation |
| } |
| |
| //----------- empty CUs related method |
| private void addEmptyCusToDelete() throws JavaModelException { |
| Set<ICompilationUnit> cusToEmpty= getCusToEmpty(); |
| addToSetToDelete(cusToEmpty.toArray(new ICompilationUnit[cusToEmpty.size()])); |
| } |
| |
| private Set<ICompilationUnit> getCusToEmpty() throws JavaModelException { |
| Set<IJavaElement> deletedElements= new HashSet<>(Arrays.asList(fJavaElements)); |
| Set<ICompilationUnit> result= new HashSet<>(); |
| for (IJavaElement element : fJavaElements) { |
| ICompilationUnit cu= ReorgUtils.getCompilationUnit(element); |
| if (cu != null && !result.contains(cu) && deletedElements.containsAll(topLevelTypes(cu))) |
| result.add(cu); |
| } |
| return result; |
| } |
| |
| private static List<IType> topLevelTypes(ICompilationUnit cu) throws JavaModelException { |
| return Arrays.asList(cu.getTypes()); |
| } |
| |
| private RefactoringStatus initialize(JavaRefactoringArguments extended) { |
| setQueries(new NullReorgQueries()); |
| final RefactoringStatus status= new RefactoringStatus(); |
| final String subPackages= extended.getAttribute(ATTRIBUTE_DELETE_SUBPACKAGES); |
| if (subPackages != null) { |
| fDeleteSubPackages= Boolean.parseBoolean(subPackages); |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_DELETE_SUBPACKAGES)); |
| final String suggest= extended.getAttribute(ATTRIBUTE_SUGGEST_ACCESSORS); |
| if (suggest != null) { |
| fSuggestGetterSetterDeletion= Boolean.parseBoolean(suggest); |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_SUGGEST_ACCESSORS)); |
| int resourceCount= 0; |
| int elementCount= 0; |
| String value= extended.getAttribute(ATTRIBUTE_RESOURCES); |
| if (value != null && !"".equals(value)) {//$NON-NLS-1$ |
| try { |
| resourceCount= Integer.parseInt(value); |
| } catch (NumberFormatException exception) { |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_RESOURCES)); |
| } |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_RESOURCES)); |
| value= extended.getAttribute(ATTRIBUTE_ELEMENTS); |
| if (value != null && !"".equals(value)) {//$NON-NLS-1$ |
| try { |
| elementCount= Integer.parseInt(value); |
| } catch (NumberFormatException exception) { |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_ELEMENTS)); |
| } |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_ELEMENTS)); |
| String handle= null; |
| List<IAdaptable> elements= new ArrayList<>(); |
| for (int index= 0; index < resourceCount; index++) { |
| final String attribute= JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + (index + 1); |
| handle= extended.getAttribute(attribute); |
| if (handle != null && !"".equals(handle)) { //$NON-NLS-1$ |
| final IResource resource= JavaRefactoringDescriptorUtil.handleToResource(extended.getProject(), handle); |
| if (resource == null || !resource.exists()) |
| status.merge(JavaRefactoringDescriptorUtil.createInputWarningStatus(resource, getProcessorName(), IJavaRefactorings.DELETE)); |
| else |
| elements.add(resource); |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, attribute)); |
| } |
| fResources= elements.toArray(new IResource[elements.size()]); |
| elements= new ArrayList<>(); |
| for (int index= 0; index < elementCount; index++) { |
| final String attribute= JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + (resourceCount + index + 1); |
| handle= extended.getAttribute(attribute); |
| if (handle != null && !"".equals(handle)) { //$NON-NLS-1$ |
| final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(extended.getProject(), handle, false); |
| if (element == null || !element.exists()) |
| status.merge(JavaRefactoringDescriptorUtil.createInputWarningStatus(element, getProcessorName(), IJavaRefactorings.DELETE)); |
| else |
| elements.add(element); |
| } else |
| return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, attribute)); |
| } |
| fJavaElements= elements.toArray(new IJavaElement[elements.size()]); |
| fElements= new Object[fResources.length + fJavaElements.length]; |
| System.arraycopy(fResources, 0, fElements, 0, fResources.length); |
| System.arraycopy(fJavaElements, 0, fElements, fResources.length, fJavaElements.length); |
| return status; |
| } |
| } |