Skip to content

Commit e222d9f

Browse files
committed
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 6368b06 commit e222d9f

File tree

11 files changed

+793
-1
lines changed

11 files changed

+793
-1
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>
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
//TODO
82+
return "Copy Project " + fSourceProject.getName(); //$NON-NLS-1$
83+
}
84+
85+
@Override
86+
public Change perform(IProgressMonitor pm) throws CoreException {
87+
SubMonitor subMonitor= SubMonitor.convert(pm, RefactoringCoreMessages.DeleteResourceChange_deleting, 10);
88+
89+
if (fSourceProject == null || !fSourceProject.exists()) {
90+
//TODO
91+
String message= Messages.format(RefactoringCoreMessages.DeleteResourceChange_error_resource_not_exists,
92+
BasicElementLabels.getPathLabel(fSourceProject.getFullPath().makeRelative(), false));
93+
throw new CoreException(new Status(IStatus.ERROR, RefactoringCorePlugin.getPluginId(), message));
94+
}
95+
96+
// make sure all files inside the resource are saved
97+
if (fSourceProject.isAccessible()) {
98+
fSourceProject.accept((IResourceVisitor) curr -> {
99+
try {
100+
if (curr instanceof IFile) {
101+
// progress is covered outside.
102+
saveFileIfNeeded((IFile) curr, new NullProgressMonitor());
103+
}
104+
} catch (CoreException e) {
105+
// ignore
106+
}
107+
return true;
108+
}, IResource.DEPTH_INFINITE, false);
109+
}
110+
111+
IProjectDescription description= fSourceProject.getDescription();
112+
113+
if (fNewLocation != null && fNewLocation.equals(Platform.getLocation())) {
114+
fNewLocation= null;
115+
}
116+
117+
description.setName(fNewName);
118+
description.setLocation(fNewLocation);
119+
120+
fSourceProject.copy(description, IResource.FORCE | IResource.SHALLOW, subMonitor.newChild(10));
121+
122+
IProject targetProject= fSourceProject.getWorkspace().getRoot().getProject(fNewName);
123+
124+
return new DeleteResourceChange(targetProject.getFullPath(), true, true);
125+
126+
}
127+
128+
private static void saveFileIfNeeded(IFile file, IProgressMonitor pm) throws CoreException {
129+
ITextFileBuffer buffer= FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE);
130+
SubMonitor subMonitor= SubMonitor.convert(pm, 2);
131+
if (buffer != null && buffer.isDirty() && buffer.isStateValidated() && buffer.isSynchronized()) {
132+
buffer.commit(subMonitor.newChild(1), false);
133+
file.refreshLocal(IResource.DEPTH_ONE, subMonitor.newChild(1));
134+
buffer.commit(subMonitor.newChild(1), false);
135+
file.refreshLocal(IResource.DEPTH_ONE, subMonitor.newChild(1));
136+
} else {
137+
subMonitor.worked(2);
138+
}
139+
}
140+
141+
@Override
142+
public ChangeDescriptor getDescriptor() {
143+
return fDescriptor;
144+
}
145+
146+
/**
147+
* Sets the change descriptor to be returned by {@link Change#getDescriptor()}.
148+
*
149+
* @param descriptor the change descriptor
150+
*/
151+
public void setDescriptor(ChangeDescriptor descriptor) {
152+
fDescriptor= descriptor;
153+
}
154+
155+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
//TODO
128+
status.addFatalError(Messages.format(RefactoringCoreMessages.DeleteResourcesDescriptor_error_delete_not_exists, BasicElementLabels.getPathLabel(fSourcePath, false)));
129+
return null;
130+
}
131+
if (resource instanceof IProject project) {
132+
return new CopyRefactoring(new CopyProjectProcessor(project, fNewName, fNewLocation));
133+
}
134+
return null;
135+
}
136+
137+
public void setNewName(String newName) {
138+
fNewName= newName;
139+
140+
}
141+
142+
public void setNewLocation(IPath newLocation) {
143+
fNewLocation= newLocation;
144+
}
145+
146+
}

0 commit comments

Comments
 (0)