Skip to content

Commit 78fa7e1

Browse files
N1k145mickaelistria
authored andcommitted
Use LTK Refactoring when copying (duplicating) a Project.
The Idea is that currently it is not possible to modify a project when it is copied and renamed. This leads to usability issues for example in PDE, where the rename of a project modifies the MANIFEST.MF but a copy does not. This solves this by providing a copy project refactoring that can be extended using a copy participant.
1 parent 7aaa014 commit 78fa7e1

File tree

16 files changed

+842
-3
lines changed

16 files changed

+842
-3
lines changed

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.14.600.qualifier
6+
Bundle-Version: 3.15.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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,9 @@
4848
<contribution
4949
class="org.eclipse.ltk.internal.core.refactoring.resource.MoveRenameResourceRefactoringContribution"
5050
id="org.eclipse.ltk.core.refactoring.moverename.resource"/>
51+
<contribution
52+
class="org.eclipse.ltk.internal.core.refactoring.resource.CopyProjectRefactoringContribution"
53+
id="org.eclipse.ltk.core.refactoring.copyproject.resource">
54+
</contribution>
5155
</extension>
5256
</plugin>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vector Informatik GmbH 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+
* Vector Informatik GmbH - initial implementation
13+
*******************************************************************************/
14+
package org.eclipse.ltk.core.refactoring.resource;
15+
16+
import org.eclipse.core.runtime.Assert;
17+
import org.eclipse.core.runtime.CoreException;
18+
import org.eclipse.core.runtime.IPath;
19+
import org.eclipse.core.runtime.IProgressMonitor;
20+
import org.eclipse.core.runtime.IStatus;
21+
import org.eclipse.core.runtime.NullProgressMonitor;
22+
import org.eclipse.core.runtime.Platform;
23+
import org.eclipse.core.runtime.Status;
24+
import org.eclipse.core.runtime.SubMonitor;
25+
26+
import org.eclipse.core.resources.IFile;
27+
import org.eclipse.core.resources.IProject;
28+
import org.eclipse.core.resources.IProjectDescription;
29+
import org.eclipse.core.resources.IResource;
30+
import org.eclipse.core.resources.IResourceVisitor;
31+
32+
import org.eclipse.core.filebuffers.FileBuffers;
33+
import org.eclipse.core.filebuffers.ITextFileBuffer;
34+
import org.eclipse.core.filebuffers.LocationKind;
35+
36+
import org.eclipse.ltk.core.refactoring.Change;
37+
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
38+
import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
39+
import org.eclipse.ltk.internal.core.refactoring.Messages;
40+
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
41+
import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin;
42+
43+
/**
44+
* {@link Change} that copies a project
45+
*
46+
* @since 3.15
47+
*/
48+
public class CopyProjectChange extends ResourceChange {
49+
50+
private final IProject fSourceProject;
51+
52+
private ChangeDescriptor fDescriptor;
53+
54+
private String fNewName;
55+
56+
private IPath fNewLocation;
57+
58+
/**
59+
* Copy a project.
60+
*
61+
* @param resourcePath the project path
62+
* @param newLocation location of the new project
63+
* @param newName name of the new project
64+
*/
65+
public CopyProjectChange(IProject resourcePath, IPath newLocation, String newName) {
66+
Assert.isNotNull(resourcePath);
67+
fNewName= newName;
68+
fNewLocation= newLocation;
69+
fSourceProject= resourcePath;
70+
setValidationMethod(SAVE_IF_DIRTY);
71+
}
72+
73+
@Override
74+
protected IResource getModifiedResource() {
75+
return fSourceProject;
76+
}
77+
78+
79+
@Override
80+
public String getName() {
81+
return RefactoringCoreMessages.CopyProjectChange_Name + fSourceProject.getName();
82+
}
83+
84+
@Override
85+
public Change perform(IProgressMonitor pm) throws CoreException {
86+
SubMonitor subMonitor= SubMonitor.convert(pm, RefactoringCoreMessages.CopyProjectChange_copying, 10);
87+
88+
if (fSourceProject == null || !fSourceProject.exists()) {
89+
String message= Messages.format(RefactoringCoreMessages.CopyProjectChange_error_resource_not_exists,
90+
BasicElementLabels.getPathLabel(fSourceProject.getFullPath().makeRelative(), false));
91+
throw new CoreException(new Status(IStatus.ERROR, RefactoringCorePlugin.getPluginId(), message));
92+
}
93+
94+
// make sure all files inside the resource are saved
95+
if (fSourceProject.isAccessible()) {
96+
fSourceProject.accept((IResourceVisitor) curr -> {
97+
try {
98+
if (curr instanceof IFile) {
99+
// progress is covered outside.
100+
saveFileIfNeeded((IFile) curr, new NullProgressMonitor());
101+
}
102+
} catch (CoreException e) {
103+
// ignore
104+
}
105+
return true;
106+
}, IResource.DEPTH_INFINITE, false);
107+
}
108+
109+
IProjectDescription description= fSourceProject.getDescription();
110+
111+
if (fNewLocation != null && (fNewLocation.equals(Platform.getLocation()) || fNewLocation.isRoot())) {
112+
fNewLocation= null;
113+
}
114+
115+
description.setName(fNewName);
116+
description.setLocation(fNewLocation);
117+
118+
fSourceProject.copy(description, IResource.FORCE | IResource.SHALLOW, subMonitor.newChild(10));
119+
120+
IProject targetProject= fSourceProject.getWorkspace().getRoot().getProject(fNewName);
121+
122+
return new DeleteResourceChange(targetProject.getFullPath(), true, true);
123+
124+
}
125+
126+
private static void saveFileIfNeeded(IFile file, IProgressMonitor pm) throws CoreException {
127+
ITextFileBuffer buffer= FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE);
128+
SubMonitor subMonitor= SubMonitor.convert(pm, 2);
129+
if (buffer != null && buffer.isDirty() && buffer.isStateValidated() && buffer.isSynchronized()) {
130+
buffer.commit(subMonitor.newChild(1), false);
131+
file.refreshLocal(IResource.DEPTH_ONE, subMonitor.newChild(1));
132+
buffer.commit(subMonitor.newChild(1), false);
133+
file.refreshLocal(IResource.DEPTH_ONE, subMonitor.newChild(1));
134+
} else {
135+
subMonitor.worked(2);
136+
}
137+
}
138+
139+
@Override
140+
public ChangeDescriptor getDescriptor() {
141+
return fDescriptor;
142+
}
143+
144+
/**
145+
* Sets the change descriptor to be returned by {@link Change#getDescriptor()}.
146+
*
147+
* @param descriptor the change descriptor
148+
*/
149+
public void setDescriptor(ChangeDescriptor descriptor) {
150+
fDescriptor= descriptor;
151+
}
152+
153+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vector Informatik GmbH 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+
* Vector Informatik GmbH - initial implementation
13+
*******************************************************************************/
14+
package org.eclipse.ltk.core.refactoring.resource;
15+
16+
import org.eclipse.core.runtime.CoreException;
17+
import org.eclipse.core.runtime.IPath;
18+
19+
import org.eclipse.core.resources.IFile;
20+
import org.eclipse.core.resources.IFolder;
21+
import org.eclipse.core.resources.IProject;
22+
import org.eclipse.core.resources.IResource;
23+
import org.eclipse.core.resources.IWorkspaceRoot;
24+
import org.eclipse.core.resources.ResourcesPlugin;
25+
26+
import org.eclipse.ltk.core.refactoring.Refactoring;
27+
import org.eclipse.ltk.core.refactoring.RefactoringContribution;
28+
import org.eclipse.ltk.core.refactoring.RefactoringCore;
29+
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
30+
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
31+
import org.eclipse.ltk.core.refactoring.participants.CopyRefactoring;
32+
import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
33+
import org.eclipse.ltk.internal.core.refactoring.Messages;
34+
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
35+
import org.eclipse.ltk.internal.core.refactoring.resource.CopyProjectProcessor;
36+
37+
/**
38+
* Refactoring descriptor for the copy project 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.15
50+
*
51+
* @noinstantiate This class is not intended to be instantiated by clients.
52+
* @noextend This class is not intended to be subclassed by clients.
53+
*/
54+
public class CopyProjectDescriptor extends RefactoringDescriptor {
55+
/**
56+
* Refactoring id of the 'Copy Project' refactoring (value:
57+
* <code>org.eclipse.ltk.core.refactoring.copyproject.resources</code>).
58+
* <p>
59+
* Clients may safely cast the obtained refactoring descriptor to {@link CopyProjectDescriptor}.
60+
* </p>
61+
*/
62+
public static final String ID= "org.eclipse.ltk.core.refactoring.copyproject.resource"; //$NON-NLS-1$
63+
64+
private IPath fSourcePath;
65+
66+
private String fNewName;
67+
68+
private IPath fNewLocation;
69+
70+
/**
71+
* Creates a new refactoring descriptor.
72+
* <p>
73+
* Clients should not instantiated this class but use
74+
* {@link RefactoringCore#getRefactoringContribution(String)} with {@link #ID} to get the
75+
* contribution that can create the descriptor.
76+
* </p>
77+
*/
78+
public CopyProjectDescriptor() {
79+
super(ID, null, RefactoringCoreMessages.RenameResourceDescriptor_unnamed_descriptor, null, RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE);
80+
}
81+
82+
/**
83+
* The resource paths to delete.
84+
*
85+
* @return an array of IPaths.
86+
*/
87+
public IPath getSourcePath() {
88+
return fSourcePath;
89+
}
90+
91+
public String getNewName() {
92+
return fNewName;
93+
}
94+
95+
public IPath getNewLocation() {
96+
return fNewLocation;
97+
}
98+
99+
/**
100+
* The paths to the resources to be deleted. The resources can be {@link IProject} or a mixture
101+
* of {@link IFile} and {@link IFolder}.
102+
*
103+
* @param resourcePath paths of the resources to be deleted
104+
*/
105+
public void setResourcePath(IPath resourcePath) {
106+
if (resourcePath == null)
107+
throw new IllegalArgumentException();
108+
fSourcePath= resourcePath;
109+
}
110+
111+
/**
112+
* The project to be copied.
113+
*
114+
* @param project {@link IProject} to be copied
115+
*/
116+
public void setProjectToCopy(IProject project) {
117+
if (project == null)
118+
throw new IllegalArgumentException();
119+
setResourcePath(project.getFullPath());
120+
}
121+
122+
@Override
123+
public Refactoring createRefactoring(RefactoringStatus status) throws CoreException {
124+
IWorkspaceRoot wsRoot= ResourcesPlugin.getWorkspace().getRoot();
125+
IResource resource= wsRoot.findMember(fSourcePath);
126+
if (resource == null || !resource.exists()) {
127+
status.addFatalError(Messages.format(RefactoringCoreMessages.CopyProjectDescriptor_project_copy_does_not_exist, BasicElementLabels.getPathLabel(fSourcePath, false)));
128+
return null;
129+
}
130+
if (resource instanceof IProject project) {
131+
return new CopyRefactoring(new CopyProjectProcessor(project, fNewName, fNewLocation));
132+
}
133+
return null;
134+
}
135+
136+
public void setNewName(String newName) {
137+
fNewName= newName;
138+
139+
}
140+
141+
public void setNewLocation(IPath newLocation) {
142+
fNewLocation= newLocation;
143+
}
144+
145+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ public final class RefactoringCoreMessages extends NLS {
3333

3434
public static String CompositeChange_performingChangesTask_name;
3535

36+
public static String CopyProjectChange_copying;
37+
38+
public static String CopyProjectChange_error_resource_not_exists;
39+
40+
public static String CopyProjectChange_Name;
41+
42+
public static String CopyProjectDescriptor_project_copy_does_not_exist;
43+
44+
public static String CopyProjectProcessor_description;
45+
46+
public static String CopyProjectProcessor_error_project_exists;
47+
48+
public static String CopyProjectProcessor_name;
49+
3650
public static String CreateChangeOperation_unknown_Refactoring;
3751

3852
public static String DefaultRefactoringDescriptor_cannot_create_refactoring;

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ BufferValidationState_character_encoding_changed=The character encoding of ''{0}
5454
CheckConditionContext_error_checker_exists= A checker of type ''{0}'' already exists.
5555

5656
CompositeChange_performingChangesTask_name=Performing changes...
57+
CopyProjectChange_copying=Copying...
58+
CopyProjectChange_error_resource_not_exists=Can not copy Project ''{0}''. Project does not exist.
59+
CopyProjectChange_Name=Copy Project
60+
CopyProjectDescriptor_project_copy_does_not_exist=The Project ''{0}'' to copy does not exist.
61+
CopyProjectProcessor_description=Copy Project ''{0}''
62+
CopyProjectProcessor_error_project_exists=There is already a Project with the name ''{0}''
63+
CopyProjectProcessor_name=Copy Project
5764

5865
ProcessorBasedRefactoring_initial_conditions=Checking preconditions...
5966
ProcessorBasedRefactoring_check_condition_participant_failed=The participant ''{0}'' caused an internal error and has been disabled for this refactoring. See the error log for more details.

0 commit comments

Comments
 (0)