Skip to content

Commit

Permalink
Use LTK Refactoring when copying (duplicating) a Project.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
N1k145 committed Sep 20, 2024
1 parent 8df2017 commit f73dc58
Show file tree
Hide file tree
Showing 15 changed files with 841 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.14.600.qualifier
Bundle-Version: 3.15.0.qualifier
Bundle-Activator: org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
Expand Down
4 changes: 4 additions & 0 deletions bundles/org.eclipse.ltk.core.refactoring/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,9 @@
<contribution
class="org.eclipse.ltk.internal.core.refactoring.resource.MoveRenameResourceRefactoringContribution"
id="org.eclipse.ltk.core.refactoring.moverename.resource"/>
<contribution
class="org.eclipse.ltk.internal.core.refactoring.resource.CopyProjectRefactoringContribution"
id="org.eclipse.ltk.core.refactoring.copyproject.resource">
</contribution>
</extension>
</plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*******************************************************************************
* Copyright (c) 2024 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
*******************************************************************************/
package org.eclipse.ltk.core.refactoring.resource;

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.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.LocationKind;

import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
import org.eclipse.ltk.internal.core.refactoring.Messages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin;

/**
* {@link Change} that copies a project
*
* @since 3.15
*/
public class CopyProjectChange extends ResourceChange {

private final IProject fSourceProject;

private ChangeDescriptor fDescriptor;

private String fNewName;

private IPath fNewLocation;

/**
* Copy a project.
*
* @param resourcePath the project path
* @param newLocation location of the new project
* @param newName name of the new project
*/
public CopyProjectChange(IProject resourcePath, IPath newLocation, String newName) {
Assert.isNotNull(resourcePath);
fNewName= newName;
fNewLocation= newLocation;
fSourceProject= resourcePath;
setValidationMethod(SAVE_IF_DIRTY);
}

@Override
protected IResource getModifiedResource() {
return fSourceProject;
}


@Override
public String getName() {
return RefactoringCoreMessages.CopyProjectChange_Name + fSourceProject.getName();
}

@Override
public Change perform(IProgressMonitor pm) throws CoreException {
SubMonitor subMonitor= SubMonitor.convert(pm, RefactoringCoreMessages.CopyProjectChange_copying, 10);

if (fSourceProject == null || !fSourceProject.exists()) {
String message= Messages.format(RefactoringCoreMessages.CopyProjectChange_error_resource_not_exists,
BasicElementLabels.getPathLabel(fSourceProject.getFullPath().makeRelative(), false));
throw new CoreException(new Status(IStatus.ERROR, RefactoringCorePlugin.getPluginId(), message));
}

// make sure all files inside the resource are saved
if (fSourceProject.isAccessible()) {
fSourceProject.accept((IResourceVisitor) curr -> {
try {
if (curr instanceof IFile) {
// progress is covered outside.
saveFileIfNeeded((IFile) curr, new NullProgressMonitor());
}
} catch (CoreException e) {
// ignore
}
return true;
}, IResource.DEPTH_INFINITE, false);
}

IProjectDescription description= fSourceProject.getDescription();

if (fNewLocation != null && (fNewLocation.equals(Platform.getLocation()) || fNewLocation.isRoot())) {
fNewLocation= null;
}

description.setName(fNewName);
description.setLocation(fNewLocation);

fSourceProject.copy(description, IResource.FORCE | IResource.SHALLOW, subMonitor.newChild(10));

IProject targetProject= fSourceProject.getWorkspace().getRoot().getProject(fNewName);

return new DeleteResourceChange(targetProject.getFullPath(), true, true);

}

private static void saveFileIfNeeded(IFile file, IProgressMonitor pm) throws CoreException {
ITextFileBuffer buffer= FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE);
SubMonitor subMonitor= SubMonitor.convert(pm, 2);
if (buffer != null && buffer.isDirty() && buffer.isStateValidated() && buffer.isSynchronized()) {
buffer.commit(subMonitor.newChild(1), false);
file.refreshLocal(IResource.DEPTH_ONE, subMonitor.newChild(1));
buffer.commit(subMonitor.newChild(1), false);
file.refreshLocal(IResource.DEPTH_ONE, subMonitor.newChild(1));
} else {
subMonitor.worked(2);
}
}

@Override
public ChangeDescriptor getDescriptor() {
return fDescriptor;
}

/**
* Sets the change descriptor to be returned by {@link Change#getDescriptor()}.
*
* @param descriptor the change descriptor
*/
public void setDescriptor(ChangeDescriptor descriptor) {
fDescriptor= descriptor;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*******************************************************************************
* Copyright (c) 2024 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
*******************************************************************************/
package org.eclipse.ltk.core.refactoring.resource;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.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.Messages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
import org.eclipse.ltk.internal.core.refactoring.resource.CopyProjectProcessor;

/**
* Refactoring descriptor for the copy project refactoring.
* <p>
* 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}).
* </p>
* <p>
* Note: this class is not intended to be subclassed or instantiated by clients.
* </p>
*
* @since 3.15
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class CopyProjectDescriptor extends RefactoringDescriptor {
/**
* Refactoring id of the 'Copy Project' refactoring (value:
* <code>org.eclipse.ltk.core.refactoring.copyproject.resources</code>).
* <p>
* Clients may safely cast the obtained refactoring descriptor to {@link CopyProjectDescriptor}.
* </p>
*/
public static final String ID= "org.eclipse.ltk.core.refactoring.copyproject.resource"; //$NON-NLS-1$

private IPath fSourcePath;

private String fNewName;

private IPath fNewLocation;

/**
* Creates a new refactoring descriptor.
* <p>
* Clients should not instantiated this class but use
* {@link RefactoringCore#getRefactoringContribution(String)} with {@link #ID} to get the
* contribution that can create the descriptor.
* </p>
*/
public CopyProjectDescriptor() {
super(ID, null, RefactoringCoreMessages.RenameResourceDescriptor_unnamed_descriptor, null, RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE);
}

/**
* The resource paths to delete.
*
* @return an array of IPaths.
*/
public IPath getSourcePath() {
return fSourcePath;
}

public String getNewName() {
return fNewName;
}

public IPath getNewLocation() {
return fNewLocation;
}

/**
* The paths to the resources to be deleted. The resources can be {@link IProject} or a mixture
* of {@link IFile} and {@link IFolder}.
*
* @param resourcePath paths of the resources to be deleted
*/
public void setResourcePath(IPath resourcePath) {
if (resourcePath == null)
throw new IllegalArgumentException();
fSourcePath= resourcePath;
}

/**
* The project to be copied.
*
* @param project {@link IProject} to be copied
*/
public void setProjectToCopy(IProject project) {
if (project == null)
throw new IllegalArgumentException();
setResourcePath(project.getFullPath());
}

@Override
public Refactoring createRefactoring(RefactoringStatus status) throws CoreException {
IWorkspaceRoot wsRoot= ResourcesPlugin.getWorkspace().getRoot();
IResource resource= wsRoot.findMember(fSourcePath);
if (resource == null || !resource.exists()) {
status.addFatalError(Messages.format(RefactoringCoreMessages.CopyProjectDescriptor_project_copy_does_not_exist, BasicElementLabels.getPathLabel(fSourcePath, false)));
return null;
}
if (resource instanceof IProject project) {
return new CopyRefactoring(new CopyProjectProcessor(project, fNewName, fNewLocation));
}
return null;
}

public void setNewName(String newName) {
fNewName= newName;

}

public void setNewLocation(IPath newLocation) {
fNewLocation= newLocation;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ public final class RefactoringCoreMessages extends NLS {

public static String CompositeChange_performingChangesTask_name;

public static String CopyProjectChange_copying;

public static String CopyProjectChange_error_resource_not_exists;

public static String CopyProjectChange_Name;

public static String CopyProjectDescriptor_project_copy_does_not_exist;

public static String CopyProjectProcessor_description;

public static String CopyProjectProcessor_error_project_exists;

public static String CopyProjectProcessor_name;

public static String CreateChangeOperation_unknown_Refactoring;

public static String DefaultRefactoringDescriptor_cannot_create_refactoring;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ BufferValidationState_character_encoding_changed=The character encoding of ''{0}
CheckConditionContext_error_checker_exists= A checker of type ''{0}'' already exists.

CompositeChange_performingChangesTask_name=Performing changes...
CopyProjectChange_copying=Copying...
CopyProjectChange_error_resource_not_exists=Can not copy Project ''{0}''. Project does not exist.
CopyProjectChange_Name=Copy Project
CopyProjectDescriptor_project_copy_does_not_exist=The Project ''{0}'' to copy does not exist.
CopyProjectProcessor_description=Copy Project ''{0}''
CopyProjectProcessor_error_project_exists=There is already a Project with the name ''{0}''
CopyProjectProcessor_name=Copy Project

ProcessorBasedRefactoring_initial_conditions=Checking preconditions...
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.
Expand Down
Loading

0 comments on commit f73dc58

Please sign in to comment.