diff --git a/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF b/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF index 9f0f659919b..91b818cdfc2 100644 --- a/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Automatic-Module-Name: org.eclipse.ltk.core.refactoring Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ltk.core.refactoring; singleton:=true -Bundle-Version: 3.15.200.qualifier +Bundle-Version: 3.16.0.qualifier Bundle-Activator: org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/bundles/org.eclipse.ltk.core.refactoring/plugin.xml b/bundles/org.eclipse.ltk.core.refactoring/plugin.xml index d59248c24ca..fe30354aad2 100644 --- a/bundles/org.eclipse.ltk.core.refactoring/plugin.xml +++ b/bundles/org.eclipse.ltk.core.refactoring/plugin.xml @@ -52,5 +52,8 @@ class="org.eclipse.ltk.internal.core.refactoring.resource.CopyProjectRefactoringContribution" id="org.eclipse.ltk.core.refactoring.copyproject.resource"> + \ No newline at end of file diff --git a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/core/refactoring/resource/CopyResourceChange.java b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/core/refactoring/resource/CopyResourceChange.java new file mode 100644 index 00000000000..a3d8894bc21 --- /dev/null +++ b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/core/refactoring/resource/CopyResourceChange.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2026 Felix Schmid + * + * 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: + * Felix Schmid - initial API and implementation and/or initial documentation + *******************************************************************************/ +package org.eclipse.ltk.core.refactoring.resource; + +import java.net.URI; +import java.text.MessageFormat; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.SubMonitor; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; + +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.ChangeDescriptor; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.NullChange; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.ReorgExecutionLog; +import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels; +import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages; + +/** + * {@link Change} that copies a resource. + * + * @since 3.16 + */ +public class CopyResourceChange extends ResourceChange { + + private ChangeDescriptor descriptor; + + private final IResource origin; + + private final ReorgExecutionLog log; + + private final IContainer destination; + + public CopyResourceChange(final IResource origin, final ReorgExecutionLog log, final IContainer destination) { + Assert.isTrue(origin instanceof IFile || origin instanceof IFolder); + this.origin= origin; + this.log= log; + this.destination= destination; + setValidationMethod(VALIDATE_NOT_DIRTY); + } + + @Override + public String getName() { + return MessageFormat.format(RefactoringCoreMessages.CopyResourceChange_name, + BasicElementLabels.getPathLabel(origin.getFullPath(), false), + BasicElementLabels.getResourceName(destination)); + } + + @Override + public final Change perform(final IProgressMonitor pm) throws CoreException, OperationCanceledException { + pm.beginTask(getName(), 2); + try { + String newName= log.getNewName(origin); + if (newName == null) { + newName= origin.getName(); + } + + final IResource resAtDest= destination.findMember(newName); + if (resAtDest != null && resAtDest.exists() && areEqualInWorkspaceOrOnDisk(origin, resAtDest)) { + return new NullChange(); + } + + final Change undoOverwrite= deleteIfAlreadyExists(resAtDest, SubMonitor.convert(pm, 1)); + + final IPath copyTo= destination.getFullPath().append(newName); + origin.copy(copyTo, getReorgFlags(), SubMonitor.convert(pm, 1)); + log.markAsProcessed(origin); + + if (undoOverwrite != null) { + return new CompositeChange(RefactoringCoreMessages.CopyResourceChange_undo_composite_name, + new Change[] { new DeleteResourceChange(copyTo, false), undoOverwrite }); + } + return new DeleteResourceChange(copyTo, false); + } finally { + pm.done(); + } + } + + @Override + protected IResource getModifiedResource() { + return origin; + } + + /** + * deletes a resource if it exists and returns a Change to undo the deletion + * + * @param resource the resource to delete + * @param pm the progress monitor + * @return returns an undo Change or null if nothing was deleted + * @throws CoreException thrown when the resource cannot be accessed + */ + private Change deleteIfAlreadyExists(final IResource resource, final IProgressMonitor pm) throws CoreException { + if (resource == null || !resource.exists()) { + pm.done(); + return null; + } + SubMonitor subMonitor= SubMonitor.convert(pm, + RefactoringCoreMessages.MoveResourceChange_progress_delete_destination, 3); + DeleteResourceChange deleteChange= new DeleteResourceChange(resource.getFullPath(), true); + deleteChange.initializeValidationData(subMonitor.newChild(1)); + RefactoringStatus deleteStatus= deleteChange.isValid(subMonitor.newChild(1)); + if (!deleteStatus.hasFatalError()) { + return deleteChange.perform(subMonitor.newChild(1)); + } + return null; + } + + private static boolean areEqualInWorkspaceOrOnDisk(final IResource r1, final IResource r2) { + if (r1 == null || r2 == null) { + return false; + } + if (r1.equals(r2)) { + return true; + } + final URI r1Location= r1.getLocationURI(); + final URI r2Location= r2.getLocationURI(); + if (r1Location == null || r2Location == null) { + return false; + } + return r1Location.equals(r2Location); + } + + private static int getReorgFlags() { + return IResource.KEEP_HISTORY | IResource.SHALLOW; + } + + @Override + public ChangeDescriptor getDescriptor() { + return descriptor; + } + + public void setDescriptor(ChangeDescriptor descriptor) { + this.descriptor= descriptor; + } +} diff --git a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/core/refactoring/resource/CopyResourcesDescriptor.java b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/core/refactoring/resource/CopyResourcesDescriptor.java new file mode 100644 index 00000000000..7e250c9de61 --- /dev/null +++ b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/core/refactoring/resource/CopyResourcesDescriptor.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2007, 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 + * Felix Schmid - adapted for copy resource descriptor + *******************************************************************************/ +package org.eclipse.ltk.core.refactoring.resource; + +import java.text.MessageFormat; +import java.util.Objects; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; + +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringCore; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.CopyRefactoring; +import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels; +import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages; +import org.eclipse.ltk.internal.core.refactoring.resource.CopyResourcesProcessor; + +/** + * Refactoring descriptor for the copy resource refactoring. + *

+ * An instance of this refactoring descriptor may be obtained by calling + * {@link RefactoringContribution#createDescriptor()} on a refactoring contribution requested by + * invoking {@link RefactoringCore#getRefactoringContribution(String)} with the refactoring id + * ({@link #ID}). + *

+ *

+ * Note: this class is not intended to be subclassed or instantiated by clients. + *

+ * + * @since 3.16 + * + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class CopyResourcesDescriptor extends RefactoringDescriptor { + /** + * Refactoring id of the 'Copy Resource' refactoring (value: + * org.eclipse.ltk.core.refactoring.copy.resources). + *

+ * Clients may safely cast the obtained refactoring descriptor to + * {@link CopyResourcesDescriptor}. + *

+ */ + public static final String ID= "org.eclipse.ltk.core.refactoring.copy.resources"; //$NON-NLS-1$ + + private IPath[] resourcePaths; + + private IPath[] destinationPaths; + + /** + * Creates a new refactoring descriptor. + *

+ * Clients should not instantiated this class but use + * {@link RefactoringCore#getRefactoringContribution(String)} with {@link #ID} to get the + * contribution that can create the descriptor. + *

+ */ + public CopyResourcesDescriptor() { + super(ID, null, RefactoringCoreMessages.RenameResourceDescriptor_unnamed_descriptor, null, + RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE); + } + + public IPath[] getResourcePaths() { + return resourcePaths; + } + + public IPath[] getDestinationPaths() { + return destinationPaths; + } + + @Override + public Refactoring createRefactoring(RefactoringStatus status) throws CoreException { + IWorkspaceRoot wsRoot= ResourcesPlugin.getWorkspace().getRoot(); + IResource[] resources= new IResource[resourcePaths.length]; + for (int i= 0; i < resourcePaths.length; i++) { + IResource resource= wsRoot.findMember(resourcePaths[i]); + if (resource == null || !resource.exists()) { + status.addFatalError(MessageFormat.format( + RefactoringCoreMessages.CopyResourcesDescriptor_error_resource_not_exists, + BasicElementLabels.getPathLabel(resourcePaths[i], false))); + return null; + } + resources[i]= resource; + } + return new CopyRefactoring(new CopyResourcesProcessor(resources, destinationPaths)); + } + + public void setResourcePaths(IPath[] resourcePaths) { + Objects.requireNonNull(resourcePaths); + this.resourcePaths= resourcePaths; + } + + public void setResources(IResource[] resources) { + Objects.requireNonNull(resources); + IPath[] paths= new IPath[resources.length]; + for (int i= 0; i < paths.length; i++) { + paths[i]= resources[i].getFullPath(); + } + setResourcePaths(paths); + } + + public void setDestinationPaths(IPath[] destinationPaths) { + Objects.requireNonNull(destinationPaths); + this.destinationPaths= destinationPaths; + } +} diff --git a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.java b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.java index 7af984b0cfc..83485f7b551 100644 --- a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.java +++ b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.java @@ -47,6 +47,24 @@ public final class RefactoringCoreMessages extends NLS { public static String CopyProjectProcessor_name; + public static String CopyResourceChange_name; + + public static String CopyResourceChange_undo_composite_name; + + public static String CopyResourcesDescriptor_error_resource_not_exists; + + public static String CopyResourcesProcessor_create_task; + + public static String CopyResourcesProcessor_description_multiple; + + public static String CopyResourcesProcessor_description_single; + + public static String CopyResourcesProcessor_destination_inside_moved; + + public static String CopyResourcesProcessor_error_multiple_destinatinos; + + public static String CopyResourcesProcessor_name; + public static String CreateChangeOperation_unknown_Refactoring; public static String DefaultRefactoringDescriptor_cannot_create_refactoring; diff --git a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.properties b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.properties index d44f72aa7a2..1015e9a449d 100644 --- a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.properties +++ b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.properties @@ -173,3 +173,13 @@ MoveResourceProcessor_destination_same_as_moved=The destination contains a resou MoveResourceProcessor_error_destination_not_exists=Destination does not exist MoveResourceProcessor_error_invalid_destination=Invalid parent MoveResourceProcessor_processor_name=Move Resources + +CopyResourceChange_name=Copy resource ''{0}'' to ''{1}'' +CopyResourceChange_undo_composite_name=Delete copy and restore overwritten resource. +CopyResourcesDescriptor_error_resource_not_exists=The resource ''{0}'' to copy does not exist. +CopyResourcesProcessor_create_task=Creating pending copies... +CopyResourcesProcessor_description_multiple=Copy {0} resources to ''{1}'' +CopyResourcesProcessor_description_single=Copy ''{0}'' to ''{1}'' +CopyResourcesProcessor_destination_inside_moved=Destination is inside copied resource ''{0}'' +CopyResourcesProcessor_error_multiple_destinatinos=Can only copy to a single destination. +CopyResourcesProcessor_name=Copy Resources diff --git a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/CopyResourcesProcessor.java b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/CopyResourcesProcessor.java new file mode 100644 index 00000000000..8b843750a8f --- /dev/null +++ b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/CopyResourcesProcessor.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2026 Felix Schmid + * + * 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: + * Felix Schmid - initial API and implementation and/or initial documentation + *******************************************************************************/ +package org.eclipse.ltk.internal.core.refactoring.resource; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; + +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor; +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.CopyArguments; +import org.eclipse.ltk.core.refactoring.participants.CopyParticipant; +import org.eclipse.ltk.core.refactoring.participants.CopyProcessor; +import org.eclipse.ltk.core.refactoring.participants.ParticipantManager; +import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; +import org.eclipse.ltk.core.refactoring.participants.ReorgExecutionLog; +import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; +import org.eclipse.ltk.core.refactoring.resource.CopyResourceChange; +import org.eclipse.ltk.core.refactoring.resource.CopyResourcesDescriptor; +import org.eclipse.ltk.core.refactoring.resource.Resources; +import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels; +import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages; + +/** + * A copy processor for {@link IResource resources}. The processor will copy the resources and load + * copy participants. + * + * @since 3.16 + */ +public final class CopyResourcesProcessor extends CopyProcessor { + + private final IResource[] resources; + + private final IPath[] destinationPaths; + + private IContainer destination; + + private final ReorgExecutionLog log; + + public CopyResourcesProcessor(final IResource[] resources, final IPath[] destinationPaths) { + Assert.isNotNull(resources); + Assert.isNotNull(destinationPaths); + Assert.isTrue(resources.length == destinationPaths.length); + this.resources= resources; + this.destinationPaths= destinationPaths; + log = new ReorgExecutionLog(); + } + + @Override + public RefactoringStatus checkInitialConditions(final IProgressMonitor pm) + throws CoreException, OperationCanceledException { + pm.beginTask("", resources.length); //$NON-NLS-1$ + try { + RefactoringStatus status= new RefactoringStatus(); + if (destinationPaths.length == 0) { + return status; // nothing to copy + } + + // check for unsaved changes + status.merge(RefactoringStatus.create(Resources.checkInSync(resources))); + + // check if destination containers are consistent for all copy locations + IPath destPath= destinationPaths[0].removeLastSegments(1); + destination= (IContainer) ResourcesPlugin.getWorkspace().getRoot().findMember(destPath); + if (!destinationValid(status)) { + return status; + } + + for (int i= 0; i < resources.length; i++) { + pm.worked(1); + if (!destinationPaths[i].removeLastSegments(1).equals(destPath)) { + status.addFatalError(RefactoringCoreMessages.CopyResourcesProcessor_error_multiple_destinatinos); + break; + } + + String destName= destinationPaths[i].lastSegment(); + if (!resources[i].getName().equals(destName)) { + // copy should use different name then origin + log.setNewName(resources[i], destName); + } + } + return status; + } finally { + pm.done(); + } + } + + @Override + public RefactoringStatus checkFinalConditions(final IProgressMonitor pm, final CheckConditionsContext context) + throws CoreException, OperationCanceledException { + return new RefactoringStatus(); + } + + public boolean destinationValid(RefactoringStatus status) { + if (destination == null || !destination.exists()) { + status.addFatalError(RefactoringCoreMessages.MoveResourceProcessor_error_destination_not_exists); + return false; + } + if (destination instanceof IWorkspaceRoot) { + status.addFatalError(RefactoringCoreMessages.MoveResourceProcessor_error_invalid_destination); + return false; + } + + IPath destinationPath= destination.getFullPath(); + for (IResource r : resources) { + IPath path= r.getFullPath(); + if (path.isPrefixOf(destinationPath) || path.equals(destinationPath)) { + status.addFatalError(MessageFormat.format( + RefactoringCoreMessages.CopyResourcesProcessor_destination_inside_moved, + BasicElementLabels.getPathLabel(path, false))); + return false; + } + } + return true; + } + + @Override + public Change createChange(final IProgressMonitor pm) throws CoreException, OperationCanceledException { + pm.beginTask(RefactoringCoreMessages.CopyResourcesProcessor_create_task, resources.length); + try { + final CompositeChange compChange= new CompositeChange(getDescription()); + compChange.markAsSynthetic(); + RefactoringChangeDescriptor descriptor= new RefactoringChangeDescriptor(createDescriptor()); + + for (IResource resource : resources) { + pm.worked(1); + CopyResourceChange copyChange= new CopyResourceChange(resource, log, destination); + copyChange.setDescriptor(descriptor); + compChange.add(copyChange); + } + return compChange; + } finally { + pm.done(); + } + } + + @Override + public RefactoringParticipant[] loadParticipants(final RefactoringStatus status, + final SharableParticipants sharedParticipants) throws CoreException { + final List result= new ArrayList<>(); + final String[] affectedNatures= ResourceProcessors.computeAffectedNatures(resources); + final CopyArguments copyArguments= new CopyArguments(destination, log); + + for (IResource resource : resources) { + final CopyParticipant[] participants= ParticipantManager.loadCopyParticipants(status, this, resource, + copyArguments, affectedNatures, sharedParticipants); + result.addAll(Arrays.asList(participants)); + } + return result.toArray(new RefactoringParticipant[result.size()]); + } + + @Override + public Object[] getElements() { + return resources; + } + + @Override + public String getIdentifier() { + return "org.eclipse.ltk.core.refactoring.copyResourceProcessor"; //$NON-NLS-1$ + } + + @Override + public String getProcessorName() { + return RefactoringCoreMessages.CopyResourcesProcessor_name; + } + + @Override + public boolean isApplicable() throws CoreException { + for (IResource r : resources) { + if (!canCopy(r)) { + return false; + } + } + return true; + } + + private static boolean canCopy(IResource res) { + return (res instanceof IFile || res instanceof IFolder) && res.exists(); + } + + protected CopyResourcesDescriptor createDescriptor() { + CopyResourcesDescriptor descriptor= new CopyResourcesDescriptor(); + descriptor.setProject(null); + descriptor.setDescription(getDescription()); + descriptor.setComment(descriptor.getDescription()); + descriptor.setFlags(RefactoringDescriptor.STRUCTURAL_CHANGE | + RefactoringDescriptor.MULTI_CHANGE | RefactoringDescriptor.BREAKING_CHANGE); + + descriptor.setResources(resources); + descriptor.setDestinationPaths(destinationPaths); + return descriptor; + } + + private String getDescription() { + if (resources.length == 1) { + return MessageFormat.format(RefactoringCoreMessages.CopyResourcesProcessor_description_single, + BasicElementLabels.getResourceName(resources[0]), + BasicElementLabels.getResourceName(destination)); + } else { + return MessageFormat.format(RefactoringCoreMessages.CopyResourcesProcessor_description_multiple, + resources.length, + BasicElementLabels.getResourceName(destination)); + } + } +} diff --git a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/CopyResourcesRefactoringContribution.java b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/CopyResourcesRefactoringContribution.java new file mode 100644 index 00000000000..04960e344a7 --- /dev/null +++ b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/CopyResourcesRefactoringContribution.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2007, 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 + * Felix Schmid - adapted for copy resource refactoring contribution + *******************************************************************************/ +package org.eclipse.ltk.internal.core.refactoring.resource; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.IPath; + +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; +import org.eclipse.ltk.core.refactoring.resource.CopyResourcesDescriptor; + +/** + * Refactoring contribution for the copy resources refactoring. + * + * @since 3.16 + */ +public class CopyResourcesRefactoringContribution extends RefactoringContribution { + + /** + * Key used for the number of resource to be copied + */ + private static final String ATTRIBUTE_NUMBER_OF_RESOURCES= "resources"; //$NON-NLS-1$ + + /** + * Key prefix used for the paths of the resources to be copied + *

+ * The element arguments are simply distinguished by appending a number to the argument name, + * e.g. element1. The indices of this argument are one-based. + *

+ */ + private static final String ATTRIBUTE_ELEMENT= "element"; //$NON-NLS-1$ + + /** + * Key prefix used for the destination paths of the resources to be copied + *

+ * The element arguments are simply distinguished by appending a number to the argument name, + * e.g. element1. The indices of this argument are one-based. + *

+ */ + private static final String ATTRIBUTE_DESTINATION= "destination"; //$NON-NLS-1$ + + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof CopyResourcesDescriptor copyDesc) { + HashMap map= new HashMap<>(); + String project= copyDesc.getProject(); + + IPath[] resourcePaths= copyDesc.getResourcePaths(); + map.put(ATTRIBUTE_NUMBER_OF_RESOURCES, String.valueOf(resourcePaths.length)); + storePaths(ATTRIBUTE_ELEMENT, resourcePaths, map, project); + storePaths(ATTRIBUTE_DESTINATION, copyDesc.getDestinationPaths(), map, project); + return map; + } + return Collections.emptyMap(); + } + + @Override + public RefactoringDescriptor createDescriptor() { + return new CopyResourcesDescriptor(); + } + + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, String comment, + Map arguments, int flags) throws IllegalArgumentException { + try { + int numResources= Integer.parseInt(arguments.get(ATTRIBUTE_NUMBER_OF_RESOURCES)); + if (numResources < 0 || numResources > 100000) { + throw new IllegalArgumentException("Can not restore CopyResourceDescriptor from map, number of moved elements invalid"); //$NON-NLS-1$ + } + + IPath[] resourcePaths= loadPaths(ATTRIBUTE_ELEMENT, numResources, arguments, project); + IPath[] destinationPaths= loadPaths(ATTRIBUTE_DESTINATION, numResources, arguments, project); + + if (resourcePaths.length > 0) { + CopyResourcesDescriptor descriptor= new CopyResourcesDescriptor(); + descriptor.setProject(project); + descriptor.setDescription(description); + descriptor.setComment(comment); + descriptor.setFlags(flags); + descriptor.setResourcePaths(resourcePaths); + descriptor.setDestinationPaths(destinationPaths); + return descriptor; + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Can not restore CopyResourceDescriptor from map"); //$NON-NLS-1$ + } + throw new IllegalArgumentException("Can not restore CopyResourceDescriptor from map"); //$NON-NLS-1$ + } + + private void storePaths(String keyPrefix, IPath[] paths, Map arguments, String project) { + for (int i= 0; i < paths.length; i++) { + arguments.put(keyPrefix + (i + 1), ResourceProcessors.resourcePathToHandle(project, paths[i])); + } + } + + private IPath[] loadPaths(String keyPrefix, int pathCount, Map arguments, String project) { + IPath[] paths= new IPath[pathCount]; + for (int i= 0; i < pathCount; i++) { + String path= arguments.get(keyPrefix + String.valueOf(i + 1)); + if (path == null) { + throw new IllegalArgumentException("Can not restore CopyResourceDescriptor from map, path missing"); //$NON-NLS-1$ + } + paths[i]= ResourceProcessors.handleToResourcePath(project, path); + } + return paths; + } +} diff --git a/bundles/org.eclipse.ltk.ui.refactoring/META-INF/MANIFEST.MF b/bundles/org.eclipse.ltk.ui.refactoring/META-INF/MANIFEST.MF index dea006509ad..ae7f9a413c0 100644 --- a/bundles/org.eclipse.ltk.ui.refactoring/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ltk.ui.refactoring/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Automatic-Module-Name: org.eclipse.ltk.ui.refactoring Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ltk.ui.refactoring; singleton:=true -Bundle-Version: 3.14.100.qualifier +Bundle-Version: 3.14.200.qualifier Bundle-Activator: org.eclipse.ltk.internal.ui.refactoring.RefactoringUIPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/bundles/org.eclipse.ltk.ui.refactoring/plugin.properties b/bundles/org.eclipse.ltk.ui.refactoring/plugin.properties index e9c2307299a..18cc431f4d3 100644 --- a/bundles/org.eclipse.ltk.ui.refactoring/plugin.properties +++ b/bundles/org.eclipse.ltk.ui.refactoring/plugin.properties @@ -20,6 +20,9 @@ changeViewerExtensionPoint= Refactoring Change Viewer RefactoringPropertyPage_name=Refactoring History RefactoringPropertyPage_keywords=refactoring history team comment share refactoring.category=Refactoring +copyResources.name=Copy Resources +copyResources.description=Copy the selected resources and notify LTK participants. +copyResources.commandParameter.destinationPaths=The destination paths of the copies. deleteResources.name=Delete Resources deleteResources.description=Delete the selected resources and notify LTK participants. moveResources.name=Move Resources diff --git a/bundles/org.eclipse.ltk.ui.refactoring/plugin.xml b/bundles/org.eclipse.ltk.ui.refactoring/plugin.xml index f6de5371b47..7f2d14588a9 100644 --- a/bundles/org.eclipse.ltk.ui.refactoring/plugin.xml +++ b/bundles/org.eclipse.ltk.ui.refactoring/plugin.xml @@ -148,5 +148,17 @@ optional="false"> + + + + diff --git a/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.java b/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.java index 8a503ded5ce..7bc95ff269c 100644 --- a/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.java +++ b/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.java @@ -58,6 +58,8 @@ public final class RefactoringUIMessages extends NLS { public static String ComparePreviewer_refactored_source; + public static String CopyResourcesHandler_problem_occurred; + public static String DeleteResourcesHandler_title; public static String DeleteResourcesWizard_label_multi; diff --git a/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.properties b/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.properties index 2cddb283aa3..2c155c8fca3 100644 --- a/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.properties +++ b/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/RefactoringUIMessages.properties @@ -223,3 +223,5 @@ MoveResourcesWizard_description_single=&Choose destination for ''{0}'': MoveResourcesWizard_error_no_selection=Select an resource. MoveResourcesWizard_page_title=Move Resources MoveResourcesWizard_window_title=Move Resources + +CopyResourcesHandler_problem_occurred=Problem Occurred diff --git a/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/actions/CopyResourcesHandler.java b/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/actions/CopyResourcesHandler.java new file mode 100644 index 00000000000..aedc68bbe04 --- /dev/null +++ b/bundles/org.eclipse.ltk.ui.refactoring/src/org/eclipse/ltk/internal/ui/refactoring/actions/CopyResourcesHandler.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2024, 2026 Vector Informatik GmbH 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: + * Vector Informatik GmbH - initial implementation + * Felix Schmid - adapted for copy resource handler + *******************************************************************************/ +package org.eclipse.ltk.internal.ui.refactoring.actions; + +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; + +import org.eclipse.core.resources.IResource; + +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; + +import org.eclipse.ui.handlers.HandlerUtil; + +import org.eclipse.ltk.core.refactoring.CheckConditionsOperation; +import org.eclipse.ltk.core.refactoring.CreateChangeOperation; +import org.eclipse.ltk.core.refactoring.PerformChangeOperation; +import org.eclipse.ltk.core.refactoring.RefactoringCore; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.CopyRefactoring; +import org.eclipse.ltk.internal.core.refactoring.resource.CopyResourcesProcessor; +import org.eclipse.ltk.internal.ui.refactoring.RefactoringUIMessages; +import org.eclipse.ltk.ui.refactoring.RefactoringUI; + +public class CopyResourcesHandler extends AbstractResourcesHandler { + + private static final String LTK_COPY_RESOURCE_COMMAND_DESTINATION_KEY = "org.eclipse.ltk.ui.refactoring.commands.copyResources.destinationPaths.parameter.key"; //$NON-NLS-1$ + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection sel= HandlerUtil.getCurrentSelection(event); + Object dest= HandlerUtil.getVariable(event, LTK_COPY_RESOURCE_COMMAND_DESTINATION_KEY); + Shell shell= HandlerUtil.getActiveShell(event); + + if (sel instanceof IStructuredSelection selection && dest instanceof IPath[] destPaths) { + IResource[] resources= getSelectedResources(selection); + CopyRefactoring copyRefactoring= new CopyRefactoring(new CopyResourcesProcessor(resources, destPaths)); + try { + CreateChangeOperation create= new CreateChangeOperation( + new CheckConditionsOperation(copyRefactoring, CheckConditionsOperation.ALL_CONDITIONS), + RefactoringStatus.FATAL); + + PerformChangeOperation perform= new PerformChangeOperation(create); + perform.setUndoManager(RefactoringCore.getUndoManager(), copyRefactoring.getName()); + perform.run(new NullProgressMonitor()); + + if (perform.getConditionCheckingStatus().getSeverity() >= RefactoringStatus.WARNING) { + openErrorDialog(shell, perform.getConditionCheckingStatus()); + } + } catch (CoreException e) { + openErrorDialog(shell, e.getStatus()); + } + } + return null; + } + + private void openErrorDialog(Shell shell, RefactoringStatus status) { + Display.getDefault().asyncExec(() -> RefactoringUI.createLightWeightStatusDialog(status, shell, + RefactoringUIMessages.CopyResourcesHandler_problem_occurred).open()); + } + + private void openErrorDialog(Shell shell, IStatus status) { + Display.getDefault().asyncExec(() -> ErrorDialog.openError(shell, null, null, status)); + } +} diff --git a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CopyFilesAndFoldersOperation.java b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CopyFilesAndFoldersOperation.java index 82504e776a5..ec6bb904f44 100644 --- a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CopyFilesAndFoldersOperation.java +++ b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CopyFilesAndFoldersOperation.java @@ -77,6 +77,7 @@ import org.eclipse.ui.internal.ide.IDEInternalPreferences; import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; +import org.eclipse.ui.internal.ide.actions.LTKLauncher; import org.eclipse.ui.internal.ide.dialogs.IDEResourceInfoUtils; import org.eclipse.ui.statushandlers.StatusManager; import org.eclipse.ui.wizards.datatransfer.FileStoreStructureProvider; @@ -1241,6 +1242,14 @@ boolean isDestinationSameAsSource(IResource[] sourceResources, private boolean performCopy(IResource[] resources, IPath destination, IProgressMonitor monitor) { try { + IPath[] destPaths = new IPath[resources.length]; + for (int i = 0; i < resources.length; i++) { + destPaths[i] = destination.append(resources[i].getName()); + } + if (LTKLauncher.copyResources(resources, destPaths)) { + return true; + } + AbstractWorkspaceOperation op = getUndoableCopyOrMoveOperation( resources, destination); op.setModelProviderIds(getModelProviderIds()); @@ -1301,6 +1310,11 @@ private boolean performCopyWithAutoRename(IResource[] resources, workspace); } } + + if (LTKLauncher.copyResources(resources, destinationPaths)) { + return true; + } + CopyResourcesOperation op = new CopyResourcesOperation(resources, destinationPaths, IDEWorkbenchMessages.CopyFilesAndFoldersOperation_copyTitle); diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java index 9c82d6b85e1..8aeac8df1cd 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java @@ -145,7 +145,6 @@ protected void doExecute(IProgressMonitor monitor, IAdaptable uiInfo) */ protected void copy(IProgressMonitor monitor, IAdaptable uiInfo) throws CoreException { - SubMonitor subMonitor = SubMonitor.convert(monitor, resources.length + (resourceDescriptions != null ? resourceDescriptions.length : 0)); subMonitor.setTaskName(UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress); diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/LTKLauncher.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/LTKLauncher.java index 99f18dd0c94..39369a5da23 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/LTKLauncher.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/LTKLauncher.java @@ -28,6 +28,7 @@ import org.eclipse.core.commands.common.NotDefinedException; import org.eclipse.core.expressions.EvaluationContext; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; @@ -50,6 +51,8 @@ public class LTKLauncher { private static final String LTK_COPY_PROJECT_ID = "org.eclipse.ltk.ui.refactoring.commands.copyProject"; //$NON-NLS-1$ private static final String LTK_COPY_PROJECT_COMMAND_NEWNAME_KEY = "org.eclipse.ltk.ui.refactoring.commands.copyProject.newName.parameter.key"; //$NON-NLS-1$ private static final String LTK_COPY_PROJECT_COMMAND_NEWLOCATION_KEY = "org.eclipse.ltk.ui.refactoring.commands.copyProject.newLocation.parameter.key"; //$NON-NLS-1$ + private static final String LTK_COPY_RESOURCES_ID = "org.eclipse.ltk.ui.refactoring.commands.copyResources"; //$NON-NLS-1$ + private static final String LTK_COPY_RESOURCES_COMMAND_DESTINATION_KEY = "org.eclipse.ltk.ui.refactoring.commands.copyResources.destinationPaths.parameter.key"; //$NON-NLS-1$ /** * Open the LTK delete resources wizard if available. @@ -127,6 +130,12 @@ public static boolean copyProject(IProject project, String newName, IPath newLoc return runCommand(LTK_COPY_PROJECT_ID, new StructuredSelection(project), commandParameters); } + public static boolean copyResources(IResource[] resources, IPath[] destinationPaths) { + Map commandParameters = new HashMap<>(); + commandParameters.put(LTK_COPY_RESOURCES_COMMAND_DESTINATION_KEY, destinationPaths); + return runCommand(LTK_COPY_RESOURCES_ID, new StructuredSelection(resources), commandParameters); + } + private static boolean runCommand(String commandId, IStructuredSelection selection, Map commandParameters) { diff --git a/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/participants/CopyRefactoringWithRefUpdateTest.java b/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/participants/CopyRefactoringWithRefUpdateTest.java new file mode 100644 index 00000000000..7f527441973 --- /dev/null +++ b/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/participants/CopyRefactoringWithRefUpdateTest.java @@ -0,0 +1,220 @@ +/******************************************************************************* + * Copyright (c) 2024, 2026 Advantest Europe GmbH 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: + * Raghunandana Murthappa + * Felix Schmid - adapted for copy refactoring test + *******************************************************************************/ +package org.eclipse.ltk.core.refactoring.tests.participants; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; + +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; + +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CheckConditionsOperation; +import org.eclipse.ltk.core.refactoring.PerformRefactoringOperation; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.CopyArguments; +import org.eclipse.ltk.core.refactoring.participants.CopyParticipant; +import org.eclipse.ltk.core.refactoring.participants.CopyProcessor; +import org.eclipse.ltk.core.refactoring.participants.CopyRefactoring; +import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; +import org.eclipse.ltk.core.refactoring.participants.ReorgExecutionLog; +import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; +import org.eclipse.ltk.core.refactoring.resource.CopyResourceChange; +import org.eclipse.ltk.core.refactoring.tests.util.SimpleTestProject; + +class CopyRefactoringWithRefUpdateTest { + + private SimpleTestProject project; + + private static class RefUpdateParticipant extends CopyParticipant { + private IFile file; + private IContainer destination; + + @Override + protected boolean initialize(Object element) { + file= (IFile) element; + destination= (IContainer) getArguments().getDestination(); + return true; + } + + @Override + public String getName() { + return "copy participant"; + } + + @Override + public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) throws OperationCanceledException { + return new RefactoringStatus(); + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + IPath path = destination.getFullPath().append(file.getName()); + return new CopyFileChange(path); + } + } + + private static class CopyFileChange extends Change { + + private final IPath newFile; + + protected CopyFileChange(final IPath newFile) { + this.newFile = newFile; + } + + @Override + public void initializeValidationData(final IProgressMonitor pm) { + // nothing to do + } + + @Override + public RefactoringStatus isValid(final IProgressMonitor pm) throws CoreException, OperationCanceledException { + return new RefactoringStatus(); + } + + @Override + public String getName() { + return "copy file change"; + } + + @Override + public Change perform(final IProgressMonitor pm) throws CoreException { + IFile file = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(newFile); + + TextFileChange result= new TextFileChange("", file); + MultiTextEdit root= new MultiTextEdit(); + root.addChild(new ReplaceEdit(9, 12, "destFolder")); + result.setEdit(root); + result.perform(pm); + + return null; // no undo change necessary, the element will be deleted + } + + @Override + public IResource getModifiedElement() { + return null; // not needed for test + } + + @Override + public Object[] getAffectedObjects() { + return null; // not needed for test + } + } + + private static class TestCopyProcessor extends CopyProcessor { + + private IFile origin; + private IFolder destination; + private ReorgExecutionLog log; + + public TestCopyProcessor(IFile origin, IFolder destination) { + this.origin = origin; + this.destination = destination; + log = new ReorgExecutionLog(); + } + + @Override + public Object[] getElements() { + return new Object[] { origin }; + } + + @Override + public String getIdentifier() { + return "org.eclipse.ltk.core.refactoring.tests.CopyProcessor"; + } + + @Override + public String getProcessorName() { + return "copy processor"; + } + + @Override + public boolean isApplicable() throws CoreException { + return true; + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { + return new RefactoringStatus(); + } + + @Override + public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException, OperationCanceledException { + return new RefactoringStatus(); + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + return new CopyResourceChange(origin, log, destination); + } + + @Override + public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants sharedParticipants) throws CoreException { + RefUpdateParticipant participant= new RefUpdateParticipant(); + participant.initialize(this, origin, new CopyArguments(destination, log)); + return new RefactoringParticipant[] { participant }; + } + } + + @BeforeEach + void setUp() throws Exception { + project= new SimpleTestProject(); + } + + @AfterEach + void tearDown() throws Exception { + project.delete(); + } + + @Test + void testCopyRefactoringWithParticipants() throws Exception { + IFolder srcFold= project.createFolder("originFolder"); + IFolder destination= project.createFolder("destFolder"); + // the copy should specify the package as "destFolder", while the origin says "originFolder" + IFile origin= project.createFile(srcFold, "testFile.txt", "package: originFolder"); + + CopyRefactoring refactoring= new CopyRefactoring(new TestCopyProcessor(origin, destination)); + PerformRefactoringOperation op= new PerformRefactoringOperation(refactoring, CheckConditionsOperation.ALL_CONDITIONS); + ResourcesPlugin.getWorkspace().run(op, null); + + IFile copy = this.project.getProject().getFolder("destFolder").getFile("testFile.txt"); + assertTrue(copy.exists(), "File is not copied"); + + // original file was not changed + String originContent= project.getContent(origin); + assertEquals("package: originFolder", originContent); + + // package of copy was changed + String copyContent = project.getContent(copy); + assertEquals("package: destFolder", copyContent); + } +} diff --git a/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java b/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java index 8b075f415a7..dc8a578209f 100644 --- a/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java +++ b/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java @@ -49,7 +49,10 @@ import org.eclipse.ltk.core.refactoring.RefactoringCore; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.ReorgExecutionLog; import org.eclipse.ltk.core.refactoring.resource.CopyProjectDescriptor; +import org.eclipse.ltk.core.refactoring.resource.CopyResourceChange; +import org.eclipse.ltk.core.refactoring.resource.CopyResourcesDescriptor; import org.eclipse.ltk.core.refactoring.resource.DeleteResourcesDescriptor; import org.eclipse.ltk.core.refactoring.resource.MoveRenameResourceDescriptor; import org.eclipse.ltk.core.refactoring.resource.MoveResourceChange; @@ -299,6 +302,225 @@ public void testMoveRenameRefactoring3() throws Exception { assertEquals(content2, fProject.getContent(file2)); } + @Test + void testCopyChangeFile() throws Exception { + String content= "hello"; + + IFolder testFolder= fProject.createFolder("test"); + IFile file= fProject.createFile(testFolder, "myFile.txt", content); + IFolder destination= fProject.createFolder("dest"); + ReorgExecutionLog log = new ReorgExecutionLog(); + + Change undoChange= perform(new CopyResourceChange(file, log, destination)); + + IResource copiedResource= assertCopy(file, log, destination); + + perform(undoChange); + + assertFalse(copiedResource.exists()); + } + + @Test + void testCopyChangeFolder() throws Exception { + String content= "hello"; + + IFolder testFolder= fProject.createFolder("test"); + fProject.createFile(testFolder, "myFile.txt", content); + IFolder destination= fProject.createFolder("dest"); + ReorgExecutionLog log = new ReorgExecutionLog(); + + Change undoChange= perform(new CopyResourceChange(testFolder, log, destination)); + + IFolder copiedResource= (IFolder) assertCopy(testFolder, log, destination); + assertTrue(copiedResource.getFile("myFile.txt").exists()); + + perform(undoChange); + + assertFalse(copiedResource.exists()); + assertTrue(testFolder.getFile("myFile.txt").exists()); + } + + @Test + void testCopyChangeOverwrite() throws Exception { + String content1= "hello"; + String content2= "world"; + + IFolder testFolder= fProject.createFolder("test"); + IFile file1= fProject.createFile(testFolder, "myFile.txt", content1); + + IFolder destination= fProject.createFolder("dest"); + IFile file2= fProject.createFile(destination, "myFile.txt", content2); + + ReorgExecutionLog log = new ReorgExecutionLog(); + + Change undoChange= perform(new CopyResourceChange(file1, log, destination)); + + assertCopy(file1, log, destination); + + perform(undoChange); + + assertEquals(content2, fProject.getContent(file2)); + } + + @Test + void testCopyRefactoringFile() throws Exception { + String content= "hello"; + + IFolder testFolder= fProject.createFolder("test"); + IFile file= fProject.createFile(testFolder, "myFile.txt", content); + + IFolder destination= fProject.createFolder("dest"); + + RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(CopyResourcesDescriptor.ID); + CopyResourcesDescriptor descriptor= (CopyResourcesDescriptor) contribution.createDescriptor(); + ReorgExecutionLog log = new ReorgExecutionLog(); + + descriptor.setResources(new IResource[] { file }); + descriptor.setDestinationPaths(new IPath[] { destination.getFullPath().append("myFile.txt") }); + + Change undoChange= perform(descriptor); + + IResource copiedResource= assertCopy(file, log, destination); + + perform(undoChange); + + assertFalse(copiedResource.exists()); + } + + @Test + void testCopyRefactoringFolder() throws Exception { + String content= "hello"; + + IFolder testFolder= fProject.createFolder("test"); + fProject.createFile(testFolder, "myFile.txt", content); + IFolder destination= fProject.createFolder("dest"); + + RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(CopyResourcesDescriptor.ID); + CopyResourcesDescriptor descriptor= (CopyResourcesDescriptor) contribution.createDescriptor(); + ReorgExecutionLog log = new ReorgExecutionLog(); + + descriptor.setResources(new IResource[] { testFolder }); + descriptor.setDestinationPaths(new IPath[] { destination.getFullPath().append("test") }); + + Change undoChange= perform(descriptor); + + IFolder copiedResource= (IFolder) assertCopy(testFolder, log, destination); + assertTrue(copiedResource.getFile("myFile.txt").exists()); + + perform(undoChange); + + assertFalse(copiedResource.exists()); + assertTrue(testFolder.getFile("myFile.txt").exists()); + } + + @Test + void testCopyRefactoringOverwrite() throws Exception { + String content1= "hello"; + String content2= "world"; + + IFolder testFolder= fProject.createFolder("test"); + IFile file1= fProject.createFile(testFolder, "myFile.txt", content1); + + IFolder destination= fProject.createFolder("dest"); + IFile file2= fProject.createFile(destination, "myFile.txt", content2); + + RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(CopyResourcesDescriptor.ID); + CopyResourcesDescriptor descriptor= (CopyResourcesDescriptor) contribution.createDescriptor(); + ReorgExecutionLog log = new ReorgExecutionLog(); + + descriptor.setResources(new IResource[] { file1 }); + descriptor.setDestinationPaths(new IPath[] { destination.getFullPath().append("myFile.txt") }); + + Change undoChange= perform(descriptor); + + assertCopy(file1, log, destination); + + perform(undoChange); + + assertEquals(content2, fProject.getContent(file2)); + } + + @Test + void testCopyRenameRefactoringFile() throws Exception { + String content= "hello"; + + IFolder testFolder= fProject.createFolder("test"); + IFile file= fProject.createFile(testFolder, "myFile.txt", content); + + IFolder destination= fProject.createFolder("dest"); + + RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(CopyResourcesDescriptor.ID); + CopyResourcesDescriptor descriptor= (CopyResourcesDescriptor) contribution.createDescriptor(); + ReorgExecutionLog log = new ReorgExecutionLog(); + log.setNewName(file, "newNameFile.txt"); + + descriptor.setResources(new IResource[] { file }); + descriptor.setDestinationPaths(new IPath[] { destination.getFullPath().append("newNameFile.txt") }); + + Change undoChange= perform(descriptor); + + IResource copiedResource= assertCopy(file, log, destination); + + perform(undoChange); + + assertFalse(copiedResource.exists()); + } + + @Test + void testCopyRenameRefactoringFolder() throws Exception { + String content= "hello"; + + IFolder testFolder= fProject.createFolder("test"); + fProject.createFile(testFolder, "myFile.txt", content); + IFolder destination= fProject.createFolder("dest"); + + RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(CopyResourcesDescriptor.ID); + CopyResourcesDescriptor descriptor= (CopyResourcesDescriptor) contribution.createDescriptor(); + ReorgExecutionLog log = new ReorgExecutionLog(); + log.setNewName(testFolder, "newNameFolder"); + + descriptor.setResources(new IResource[] { testFolder }); + descriptor.setDestinationPaths(new IPath[] { destination.getFullPath().append("newNameFolder") }); + + Change undoChange= perform(descriptor); + + IFolder copiedResource= (IFolder) assertCopy(testFolder, log, destination); + assertTrue(copiedResource.getFile("myFile.txt").exists()); + + perform(undoChange); + + assertFalse(copiedResource.exists()); + assertTrue(testFolder.getFile("myFile.txt").exists()); + } + + @Test + void testCopyRenameRefactoringOverwrite() throws Exception { + String content1= "hello"; + String content2= "world"; + + IFolder testFolder= fProject.createFolder("test"); + IFile file1= fProject.createFile(testFolder, "myFile.txt", content1); + + IFolder destination= fProject.createFolder("dest"); + IFile file2= fProject.createFile(destination, "myFile2.txt", content2); + + RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(CopyResourcesDescriptor.ID); + CopyResourcesDescriptor descriptor= (CopyResourcesDescriptor) contribution.createDescriptor(); + ReorgExecutionLog log = new ReorgExecutionLog(); + log.setNewName(file1, "myFile2.txt"); + + descriptor.setResources(new IResource[] { file1 }); + descriptor.setDestinationPaths(new IPath[] { destination.getFullPath().append("myFile2.txt") }); + + Change undoChange= perform(descriptor); + + assertCopy(file1, log, destination); + + perform(undoChange); + + assertEquals(content2, fProject.getContent(file2)); + } + @Test public void testDeleteRefactoring1_bug343584() throws Exception { IFolder testFolder= fProject.createFolder("test"); @@ -457,6 +679,24 @@ private IResource assertMove(IResource source, IContainer destination, String co return res; } + private IResource assertCopy(IResource source, ReorgExecutionLog log, IContainer destination) throws CoreException, IOException { + String newName = log.getNewName(source); + if (newName == null) { + newName = source.getName(); + } + + IResource res= destination.findMember(newName); + + assertTrue(source.exists()); + assertNotNull(res); + assertEquals(res.getType(), source.getType()); + + if (source instanceof IFile file) { + assertEquals(fProject.getContent(file), fProject.getContent((IFile) res)); + } + return res; + } + private IResource assertMoveRename(IResource source, IContainer destination, String newName, String content) throws CoreException, IOException { IResource res= destination.findMember(newName);