Skip to content

Commit 0aaee1a

Browse files
Felix-Schmidvogella
authored andcommitted
Add copy refactoring infrastructure to LTK
1 parent 37dfddc commit 0aaee1a

19 files changed

Lines changed: 1383 additions & 4 deletions

File tree

bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Automatic-Module-Name: org.eclipse.ltk.core.refactoring
33
Bundle-ManifestVersion: 2
44
Bundle-Name: %pluginName
55
Bundle-SymbolicName: org.eclipse.ltk.core.refactoring; singleton:=true
6-
Bundle-Version: 3.15.300.qualifier
6+
Bundle-Version: 3.16.0.qualifier
77
Bundle-Activator: org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin
88
Bundle-ActivationPolicy: lazy
99
Bundle-Vendor: %providerName

bundles/org.eclipse.ltk.core.refactoring/plugin.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,8 @@
5252
class="org.eclipse.ltk.internal.core.refactoring.resource.CopyProjectRefactoringContribution"
5353
id="org.eclipse.ltk.core.refactoring.copyproject.resource">
5454
</contribution>
55+
<contribution
56+
class="org.eclipse.ltk.internal.core.refactoring.resource.CopyResourcesRefactoringContribution"
57+
id="org.eclipse.ltk.core.refactoring.copy.resources"/>
5558
</extension>
5659
</plugin>
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Felix Schmid
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Felix Schmid - initial API and implementation and/or initial documentation
13+
*******************************************************************************/
14+
package org.eclipse.ltk.core.refactoring.resource;
15+
16+
import java.net.URI;
17+
import java.text.MessageFormat;
18+
19+
import org.eclipse.core.runtime.Assert;
20+
import org.eclipse.core.runtime.CoreException;
21+
import org.eclipse.core.runtime.IPath;
22+
import org.eclipse.core.runtime.IProgressMonitor;
23+
import org.eclipse.core.runtime.OperationCanceledException;
24+
import org.eclipse.core.runtime.SubMonitor;
25+
26+
import org.eclipse.core.resources.IContainer;
27+
import org.eclipse.core.resources.IFile;
28+
import org.eclipse.core.resources.IFolder;
29+
import org.eclipse.core.resources.IResource;
30+
31+
import org.eclipse.ltk.core.refactoring.Change;
32+
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
33+
import org.eclipse.ltk.core.refactoring.CompositeChange;
34+
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
35+
import org.eclipse.ltk.core.refactoring.participants.ReorgExecutionLog;
36+
import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
37+
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
38+
39+
/**
40+
* {@link Change} that copies a resource.
41+
*
42+
* @since 3.16
43+
*/
44+
public class CopyResourceChange extends ResourceChange {
45+
46+
private ChangeDescriptor descriptor;
47+
48+
private final IResource origin;
49+
50+
private final ReorgExecutionLog log;
51+
52+
private final IContainer destination;
53+
54+
public CopyResourceChange(final IResource origin, final ReorgExecutionLog log, final IContainer destination) {
55+
Assert.isTrue(origin instanceof IFile || origin instanceof IFolder);
56+
this.origin= origin;
57+
this.log= log;
58+
this.destination= destination;
59+
setValidationMethod(VALIDATE_NOT_DIRTY);
60+
}
61+
62+
@Override
63+
public String getName() {
64+
return MessageFormat.format(RefactoringCoreMessages.CopyResourceChange_name,
65+
BasicElementLabels.getPathLabel(origin.getFullPath(), false),
66+
BasicElementLabels.getResourceName(destination));
67+
}
68+
69+
@Override
70+
public final Change perform(final IProgressMonitor pm) throws CoreException, OperationCanceledException {
71+
CompositeChange undoChanges= new CompositeChange(
72+
RefactoringCoreMessages.CopyResourceChange_undo_composite_name);
73+
copyMerge(origin, destination, undoChanges, pm);
74+
return undoChanges;
75+
}
76+
77+
private void copyMerge(IResource res, IContainer dest, CompositeChange undoChanges,
78+
IProgressMonitor pm) throws CoreException {
79+
SubMonitor monitor= SubMonitor.convert(pm, getName(), 1);
80+
81+
if (res instanceof IFile) { // copy and overwrite normal files
82+
performResourceCopy(res, dest, undoChanges, monitor.split(1));
83+
} else if (res instanceof IFolder folder) {
84+
String newName= log.getNewName(res);
85+
if (newName == null) {
86+
newName= res.getName();
87+
}
88+
final IResource resAtDest= dest.findMember(newName);
89+
90+
if (!resourceExists(resAtDest) || !(resAtDest instanceof IFolder folderAtDest) ||
91+
!homogeneousResources(folder, resAtDest)) { // copy non-existing or non-homogeneous folders
92+
performResourceCopy(res, dest, undoChanges, monitor.split(1));
93+
} else { // merge copy members of existing folders
94+
monitor.setWorkRemaining(folder.members().length);
95+
for (IResource member : folder.members()) {
96+
copyMerge(member, folderAtDest, undoChanges, monitor.split(1));
97+
}
98+
}
99+
}
100+
}
101+
102+
private void performResourceCopy(IResource res, IContainer dest, CompositeChange undoChanges,
103+
IProgressMonitor pm) throws CoreException {
104+
SubMonitor subMonitor= SubMonitor.convert(pm, getName(), 2);
105+
String newName= log.getNewName(res);
106+
if (newName == null) {
107+
newName= res.getName();
108+
}
109+
final IResource resAtDest= dest.findMember(newName);
110+
111+
if (resourceExists(resAtDest) && areEqualInWorkspaceOrOnDisk(res, resAtDest)) {
112+
log.markAsProcessed(res);
113+
return;
114+
}
115+
116+
final Change undoOverwrite= deleteIfAlreadyExists(resAtDest, subMonitor.newChild(1));
117+
final IPath copyTo= dest.getFullPath().append(newName);
118+
res.copy(copyTo, getReorgFlags(), subMonitor.newChild(1));
119+
log.markAsProcessed(res);
120+
121+
undoChanges.add(new DeleteResourceChange(copyTo, false));
122+
if (undoOverwrite != null) {
123+
undoChanges.add(undoOverwrite);
124+
}
125+
}
126+
127+
@Override
128+
protected IResource getModifiedResource() {
129+
return origin;
130+
}
131+
132+
/**
133+
* deletes a resource if it exists and returns a <code>Change</code> to undo the deletion
134+
*
135+
* @param resource the resource to delete
136+
* @param pm the progress monitor
137+
* @return returns an undo <code>Change</code> or <code>null</code> if nothing was deleted
138+
* @throws CoreException thrown when the resource cannot be accessed
139+
*/
140+
private Change deleteIfAlreadyExists(final IResource resource, final IProgressMonitor pm) throws CoreException {
141+
if (!resourceExists(resource)) {
142+
pm.done();
143+
return null;
144+
}
145+
SubMonitor subMonitor= SubMonitor.convert(pm,
146+
RefactoringCoreMessages.MoveResourceChange_progress_delete_destination, 3);
147+
DeleteResourceChange deleteChange= new DeleteResourceChange(resource.getFullPath(), true);
148+
deleteChange.initializeValidationData(subMonitor.newChild(1));
149+
RefactoringStatus deleteStatus= deleteChange.isValid(subMonitor.newChild(1));
150+
if (!deleteStatus.hasFatalError()) {
151+
return deleteChange.perform(subMonitor.newChild(1));
152+
}
153+
return null;
154+
}
155+
156+
private static boolean areEqualInWorkspaceOrOnDisk(final IResource r1, final IResource r2) {
157+
if (r1 == null || r2 == null) {
158+
return false;
159+
}
160+
if (r1.equals(r2)) {
161+
return true;
162+
}
163+
final URI r1Location= r1.getLocationURI();
164+
final URI r2Location= r2.getLocationURI();
165+
if (r1Location == null || r2Location == null) {
166+
return false;
167+
}
168+
return r1Location.equals(r2Location);
169+
}
170+
171+
private static boolean resourceExists(final IResource resource) {
172+
return resource != null && resource.exists();
173+
}
174+
175+
private static boolean homogeneousResources(IResource res1, IResource res2) {
176+
boolean res1Linked= res1.isLinked();
177+
boolean res2Linked= res2.isLinked();
178+
return res1Linked && res2Linked || !res1Linked && !res2Linked;
179+
}
180+
181+
private static int getReorgFlags() {
182+
return IResource.KEEP_HISTORY | IResource.SHALLOW;
183+
}
184+
185+
@Override
186+
public ChangeDescriptor getDescriptor() {
187+
return descriptor;
188+
}
189+
190+
public void setDescriptor(ChangeDescriptor descriptor) {
191+
this.descriptor= descriptor;
192+
}
193+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2007, 2026 IBM Corporation and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* IBM Corporation - initial API and implementation
13+
* Felix Schmid - adapted for copy resource descriptor
14+
*******************************************************************************/
15+
package org.eclipse.ltk.core.refactoring.resource;
16+
17+
import java.text.MessageFormat;
18+
import java.util.Objects;
19+
20+
import org.eclipse.core.runtime.CoreException;
21+
import org.eclipse.core.runtime.IPath;
22+
23+
import org.eclipse.core.resources.IResource;
24+
import org.eclipse.core.resources.IWorkspaceRoot;
25+
import org.eclipse.core.resources.ResourcesPlugin;
26+
27+
import org.eclipse.ltk.core.refactoring.Refactoring;
28+
import org.eclipse.ltk.core.refactoring.RefactoringContribution;
29+
import org.eclipse.ltk.core.refactoring.RefactoringCore;
30+
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
31+
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
32+
import org.eclipse.ltk.core.refactoring.participants.CopyRefactoring;
33+
import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
34+
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
35+
import org.eclipse.ltk.internal.core.refactoring.resource.CopyResourcesProcessor;
36+
37+
/**
38+
* Refactoring descriptor for the copy resource refactoring.
39+
* <p>
40+
* An instance of this refactoring descriptor may be obtained by calling
41+
* {@link RefactoringContribution#createDescriptor()} on a refactoring contribution requested by
42+
* invoking {@link RefactoringCore#getRefactoringContribution(String)} with the refactoring id
43+
* ({@link #ID}).
44+
* </p>
45+
* <p>
46+
* Note: this class is not intended to be subclassed or instantiated by clients.
47+
* </p>
48+
*
49+
* @since 3.16
50+
*
51+
* @noinstantiate This class is not intended to be instantiated by clients.
52+
*/
53+
public final class CopyResourcesDescriptor extends RefactoringDescriptor {
54+
/**
55+
* Refactoring id of the 'Copy Resource' refactoring (value:
56+
* <code>org.eclipse.ltk.core.refactoring.copy.resources</code>).
57+
* <p>
58+
* Clients may safely cast the obtained refactoring descriptor to
59+
* {@link CopyResourcesDescriptor}.
60+
* </p>
61+
*/
62+
public static final String ID= "org.eclipse.ltk.core.refactoring.copy.resources"; //$NON-NLS-1$
63+
64+
private IPath[] resourcePaths;
65+
66+
private IPath[] destinationPaths;
67+
68+
/**
69+
* Creates a new refactoring descriptor.
70+
* <p>
71+
* Clients should not instantiated this class but use
72+
* {@link RefactoringCore#getRefactoringContribution(String)} with {@link #ID} to get the
73+
* contribution that can create the descriptor.
74+
* </p>
75+
*/
76+
public CopyResourcesDescriptor() {
77+
super(ID, null, RefactoringCoreMessages.RenameResourceDescriptor_unnamed_descriptor, null,
78+
RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE);
79+
}
80+
81+
public IPath[] getResourcePaths() {
82+
return resourcePaths;
83+
}
84+
85+
public IPath[] getDestinationPaths() {
86+
return destinationPaths;
87+
}
88+
89+
@Override
90+
public Refactoring createRefactoring(RefactoringStatus status) throws CoreException {
91+
IWorkspaceRoot wsRoot= ResourcesPlugin.getWorkspace().getRoot();
92+
IResource[] resources= new IResource[resourcePaths.length];
93+
for (int i= 0; i < resourcePaths.length; i++) {
94+
IResource resource= wsRoot.findMember(resourcePaths[i]);
95+
if (resource == null || !resource.exists()) {
96+
status.addFatalError(MessageFormat.format(
97+
RefactoringCoreMessages.CopyResourcesDescriptor_error_resource_not_exists,
98+
BasicElementLabels.getPathLabel(resourcePaths[i], false)));
99+
return null;
100+
}
101+
resources[i]= resource;
102+
}
103+
return new CopyRefactoring(new CopyResourcesProcessor(resources, destinationPaths));
104+
}
105+
106+
public void setResourcePaths(IPath[] resourcePaths) {
107+
Objects.requireNonNull(resourcePaths);
108+
this.resourcePaths= resourcePaths;
109+
}
110+
111+
public void setResources(IResource[] resources) {
112+
Objects.requireNonNull(resources);
113+
IPath[] paths= new IPath[resources.length];
114+
for (int i= 0; i < paths.length; i++) {
115+
paths[i]= resources[i].getFullPath();
116+
}
117+
setResourcePaths(paths);
118+
}
119+
120+
public void setDestinationPaths(IPath[] destinationPaths) {
121+
Objects.requireNonNull(destinationPaths);
122+
this.destinationPaths= destinationPaths;
123+
}
124+
}

bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,24 @@ public final class RefactoringCoreMessages extends NLS {
4747

4848
public static String CopyProjectProcessor_name;
4949

50+
public static String CopyResourceChange_name;
51+
52+
public static String CopyResourceChange_undo_composite_name;
53+
54+
public static String CopyResourcesDescriptor_error_resource_not_exists;
55+
56+
public static String CopyResourcesProcessor_create_task;
57+
58+
public static String CopyResourcesProcessor_description_multiple;
59+
60+
public static String CopyResourcesProcessor_description_single;
61+
62+
public static String CopyResourcesProcessor_destination_inside_moved;
63+
64+
public static String CopyResourcesProcessor_error_multiple_destinatinos;
65+
66+
public static String CopyResourcesProcessor_name;
67+
5068
public static String CreateChangeOperation_unknown_Refactoring;
5169

5270
public static String DefaultRefactoringDescriptor_cannot_create_refactoring;

bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/RefactoringCoreMessages.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,13 @@ MoveResourceProcessor_destination_same_as_moved=The destination contains a resou
173173
MoveResourceProcessor_error_destination_not_exists=Destination does not exist
174174
MoveResourceProcessor_error_invalid_destination=Invalid parent
175175
MoveResourceProcessor_processor_name=Move Resources
176+
177+
CopyResourceChange_name=Copy resource ''{0}'' to ''{1}''
178+
CopyResourceChange_undo_composite_name=Delete copy and restore overwritten resource.
179+
CopyResourcesDescriptor_error_resource_not_exists=The resource ''{0}'' to copy does not exist.
180+
CopyResourcesProcessor_create_task=Creating pending copies...
181+
CopyResourcesProcessor_description_multiple=Copy {0} resources to ''{1}''
182+
CopyResourcesProcessor_description_single=Copy ''{0}'' to ''{1}''
183+
CopyResourcesProcessor_destination_inside_moved=Destination is inside copied resource ''{0}''
184+
CopyResourcesProcessor_error_multiple_destinatinos=Can only copy to a single destination.
185+
CopyResourcesProcessor_name=Copy Resources

0 commit comments

Comments
 (0)