diff --git a/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF b/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
index 998c1b969ba..57bff4adfd3 100644
--- a/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
+++ b/resources/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true
-Bundle-Version: 3.19.200.qualifier
+Bundle-Version: 3.20.0.qualifier
Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin
Bundle-Vendor: %providerName
Bundle-Localization: plugin
@@ -23,6 +23,7 @@ Export-Package: org.eclipse.core.internal.dtree;x-internal:=true,
org.eclipse.core.resources.mapping,
org.eclipse.core.resources.refresh,
org.eclipse.core.resources.team,
+ org.eclipse.core.resources.undo.snapshot,
org.eclipse.core.resources.variableresolvers
Require-Bundle: org.eclipse.ant.core;bundle-version="[3.1.0,4.0.0)";resolution:=optional,
org.eclipse.core.expressions;bundle-version="[3.2.0,4.0.0)",
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/AbstractResourceSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/AbstractResourceSnapshot.java
new file mode 100644
index 00000000000..1e6a4f171c1
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/AbstractResourceSnapshot.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourceAttributes;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.undo.snapshot.IResourceSnapshot;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Base implementation of ResourceSnapshot that describes the common attributes
+ * of a resource to be created.
+ *
+ * This class is not intended to be instantiated or used by clients.
+ *
+ * @since 3.20
+ *
+ */
+abstract class AbstractResourceSnapshot implements IResourceSnapshot {
+ IContainer parent;
+
+ long modificationStamp = IResource.NULL_STAMP;
+
+ long localTimeStamp = IResource.NULL_STAMP;
+
+ ResourceAttributes resourceAttributes;
+
+ MarkerSnapshot[] markerDescriptions;
+
+ /**
+ * Create a resource snapshot with no initial attributes
+ */
+ protected AbstractResourceSnapshot() {
+ super();
+ }
+
+ /**
+ * Create a resource snapshot from the specified resource.
+ *
+ * @param resource the resource to be described
+ */
+ protected AbstractResourceSnapshot(IResource resource) {
+ super();
+ parent = resource.getParent();
+ if (resource.isAccessible()) {
+ modificationStamp = resource.getModificationStamp();
+ localTimeStamp = resource.getLocalTimeStamp();
+ resourceAttributes = resource.getResourceAttributes();
+ try {
+ IMarker[] markers = resource.findMarkers(null, true,
+ IResource.DEPTH_INFINITE);
+ markerDescriptions = new MarkerSnapshot[markers.length];
+ for (int i = 0; i < markers.length; i++) {
+ markerDescriptions[i] = new MarkerSnapshot(markers[i]);
+ }
+ } catch (CoreException e) {
+ // Eat this exception because it only occurs when the resource
+ // does not exist and we have already checked this.
+ // We do not want to throw exceptions on the simple constructor,
+ // as no one has actually tried to do anything yet.
+ }
+ }
+ }
+
+ @Override
+ public IResource createResource(IProgressMonitor monitor)
+ throws CoreException {
+ IResource resource = createResourceHandle();
+ createExistentResourceFromHandle(resource, monitor);
+ restoreResourceAttributes(resource);
+ return resource;
+ }
+
+ @Override
+ public boolean isValid() {
+ return parent == null || parent.exists();
+ }
+
+ /**
+ * Restore any saved attributed of the specified resource. This method is
+ * called after the existent resource represented by the receiver has been
+ * created.
+ *
+ * @param resource
+ * the newly created resource
+ * @throws CoreException
+ */
+ protected void restoreResourceAttributes(IResource resource)
+ throws CoreException {
+ if (modificationStamp != IResource.NULL_STAMP) {
+ resource.revertModificationStamp(modificationStamp);
+ }
+ if (localTimeStamp != IResource.NULL_STAMP) {
+ resource.setLocalTimeStamp(localTimeStamp);
+ }
+ if (resourceAttributes != null) {
+ resource.setResourceAttributes(resourceAttributes);
+ }
+ if (markerDescriptions != null) {
+ for (MarkerSnapshot markerDescription : markerDescriptions) {
+ if (markerDescription.resource.exists())
+ markerDescription.createMarker();
+ }
+ }
+ }
+
+ /*
+ * Return the workspace.
+ */
+ IWorkspace getWorkspace() {
+ return ResourcesPlugin.getWorkspace();
+ }
+
+ @Override
+ public boolean verifyExistence(boolean checkMembers) {
+ IContainer p = parent;
+ if (p == null) {
+ p = getWorkspace().getRoot();
+ }
+ IResource handle = p.findMember(getName());
+ return handle != null;
+ }
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ContainerSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ContainerSnapshot.java
new file mode 100644
index 00000000000..eb0be2a2bd9
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ContainerSnapshot.java
@@ -0,0 +1,289 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import java.net.URI;
+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.IResourceFilterDescription;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.undo.snapshot.IContainerSnapshot;
+import org.eclipse.core.resources.undo.snapshot.IResourceSnapshot;
+import org.eclipse.core.resources.undo.snapshot.ResourceSnapshotFactory;
+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.SubMonitor;
+
+/**
+ * ContainerDescription is a lightweight description that describes a container
+ * to be created.
+ *
+ * This class is not intended to be instantiated or used by clients.
+ *
+ * @since 3.20
+ */
+public abstract class ContainerSnapshot extends AbstractResourceSnapshot implements IContainerSnapshot {
+
+ String name;
+
+ URI location;
+
+ IResourceFilterDescription[] filters;
+
+ String defaultCharSet;
+
+ IResourceSnapshot[] members;
+
+ /**
+ * Create a container snapshot from the specified container handle that can be
+ * used to create the container. The returned ContainerSnapshot should represent
+ * any non-existing parents in addition to the specified container.
+ *
+ * @param container the handle of the container to be described
+ * @return a container snapshot describing the container and any non-existing
+ * parents.
+ */
+
+ public static ContainerSnapshot fromContainer(IContainer container) {
+ return fromContainer(container, false);
+ }
+
+ /**
+ * Create a group container snapshot from the specified container handle that
+ * can be used to create the container. The returned ContainerSnapshot should
+ * represent any non-existing parents in addition to the specified container.
+ *
+ * @param container the handle of the container to be described
+ * @return a container description snapshot the container and any non-existing
+ * parents.
+ */
+
+ public static ContainerSnapshot fromVirtualFolderContainer(IContainer container) {
+ return fromContainer(container, true);
+ }
+
+ public static ContainerSnapshot fromContainer(IContainer container, boolean usingVirtualFolder) {
+ IPath fullPath = container.getFullPath();
+ ContainerSnapshot firstCreatedParent = null;
+ ContainerSnapshot currentContainerDescription = null;
+
+ // Does the container exist already? If so, then the parent exists and
+ // we use the normal creation constructor.
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IContainer currentContainer = (IContainer) root.findMember(fullPath);
+ if (currentContainer != null) {
+ return (ContainerSnapshot) ResourceSnapshotFactory
+ .fromResource(container);
+ }
+
+ // Create container descriptions for any uncreated parents in the given
+ // path.
+ currentContainer = root;
+ for (int i = 0; i < fullPath.segmentCount(); i++) {
+ String currentSegment = fullPath.segment(i);
+ IResource resource = currentContainer.findMember(currentSegment);
+ if (resource != null) {
+ // parent already exists, no need to create a description for it
+ currentContainer = (IContainer) resource;
+ } else if (i == 0) {
+ // parent does not exist and it is a project
+ firstCreatedParent = new ProjectSnapshot(root
+ .getProject(currentSegment));
+ currentContainerDescription = firstCreatedParent;
+ } else {
+ IFolder folderHandle = currentContainer.getFolder(IPath.fromOSString(currentSegment));
+ ContainerSnapshot currentFolder;
+ currentFolder = new FolderSnapshot(folderHandle, usingVirtualFolder);
+ currentContainer = folderHandle;
+ if (currentContainerDescription != null) {
+ currentContainerDescription.addMember(currentFolder);
+ }
+ currentContainerDescription = currentFolder;
+ if (firstCreatedParent == null) {
+ firstCreatedParent = currentFolder;
+ }
+ }
+ }
+ return firstCreatedParent;
+ }
+
+ /**
+ * Create a ContainerDescription with no state.
+ */
+ public ContainerSnapshot() {
+
+ }
+
+ /**
+ * Create a ContainerSnapshot from the specified container handle. Typically
+ * used when the container handle represents a resource that actually exists,
+ * although it will not fail if the resource is non-existent.
+ *
+ * @param container the container to be described
+ */
+ public ContainerSnapshot(IContainer container) {
+ super(container);
+ this.name = container.getName();
+ if (container.isLinked()) {
+ this.location = container.getLocationURI();
+ }
+ try {
+ if (container.isAccessible()) {
+ defaultCharSet = container.getDefaultCharset(false);
+ IResource[] resourceMembers = container.members();
+ members = new AbstractResourceSnapshot[resourceMembers.length];
+ for (int i = 0; i < resourceMembers.length; i++) {
+ members[i] = (AbstractResourceSnapshot) ResourceSnapshotFactory
+ .fromResource(resourceMembers[i]);
+ }
+ }
+ } catch (CoreException e) {
+ // Eat this exception because it only occurs when the resource
+ // does not exist and we have already checked this.
+ // We do not want to throw exceptions on the simple constructor, as
+ // no one has actually tried to do anything yet.
+ }
+ }
+
+ /**
+ * Create any child resources known by this container snapshot.
+ *
+ * @param parentHandle the handle of the created parent
+ * @param monitor the progress monitor to be used
+ * @throws CoreException
+ */
+ protected final void createChildResources(IContainer parentHandle,
+ IProgressMonitor monitor) throws CoreException {
+ // restore any children
+ if (members != null && members.length > 0) {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, members.length);
+ for (IResourceSnapshot member : members) {
+ if (member instanceof AbstractResourceSnapshot)
+ ((AbstractResourceSnapshot) member).parent = parentHandle;
+ member.createResource(subMonitor.split(1));
+ }
+ }
+ }
+
+ @Override
+ public void recordStateFromHistory(IResource resource, IProgressMonitor mon) throws CoreException {
+ if (members != null) {
+ SubMonitor subMonitor = SubMonitor.convert(mon, ResourceSnapshotMessages.FolderDescription_SavingUndoInfoProgress,
+ members.length);
+ for (IResourceSnapshot member : members) {
+ SubMonitor iterationMonitor = subMonitor.split(1);
+ if (member instanceof FileSnapshot) {
+ IPath path = resource.getFullPath().append(((FileSnapshot) member).name);
+ IFile fileHandle = resource.getWorkspace().getRoot().getFile(path);
+ member.recordStateFromHistory(fileHandle, iterationMonitor);
+ } else if (member instanceof FolderSnapshot) {
+ IPath path = resource.getFullPath().append(((FolderSnapshot) member).name);
+ IFolder folderHandle = resource.getWorkspace().getRoot().getFolder(path);
+ member.recordStateFromHistory(folderHandle, iterationMonitor);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the name of the container described by this ContainerSnapshot.
+ *
+ * @return the name of the container.
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public IResourceSnapshot[] getMembers() {
+ AbstractResourceSnapshot[] clone = new AbstractResourceSnapshot[members.length];
+ System.arraycopy(members, 0, clone, 0, members.length);
+ return clone;
+ }
+
+ /**
+ * Add the specified resource snapshot as a member of this resource description
+ *
+ * @param member the resource snapshot considered a member of this container.
+ */
+ @Override
+ public void addMember(IResourceSnapshot member) {
+ if (members == null) {
+ members = new IResourceSnapshot[] { member };
+ } else {
+ IResourceSnapshot[] expandedMembers = new AbstractResourceSnapshot[members.length + 1];
+ System.arraycopy(members, 0, expandedMembers, 0, members.length);
+ expandedMembers[members.length] = member;
+ members = expandedMembers;
+ }
+ }
+
+ @Override
+ protected void restoreResourceAttributes(IResource resource)
+ throws CoreException {
+ super.restoreResourceAttributes(resource);
+ Assert.isLegal(resource instanceof IContainer);
+ IContainer container = (IContainer) resource;
+ if (defaultCharSet != null) {
+ container.setDefaultCharset(defaultCharSet, null);
+ }
+ }
+
+ /**
+ * Set the location to which this container is linked.
+ *
+ * @param location the location URI, or null
if there is no link
+ */
+ @Override
+ public void setLocation(URI location) {
+ this.location = location;
+ }
+
+ /**
+ * Set the filters to which should be created on this container.
+ *
+ * @param filters the filters
+ */
+ @Override
+ public void setFilters(IResourceFilterDescription[] filters) {
+ this.filters = filters;
+ }
+
+ @Override
+ public boolean verifyExistence(boolean checkMembers) {
+ boolean existence = super.verifyExistence(checkMembers);
+ if (existence) {
+ if (checkMembers) {
+ // restore any children
+ if (members != null && members.length > 0) {
+ for (IResourceSnapshot member : members) {
+ if (!member.verifyExistence(checkMembers)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/FileSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/FileSnapshot.java
new file mode 100644
index 00000000000..4e60c7b65e8
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/FileSnapshot.java
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFileState;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.resources.IWorkspaceRoot;
+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.SubMonitor;
+
+
+/**
+ * FileSnapshot is a lightweight description that describes a file to be
+ * created.
+ *
+ * This class is not intended to be instantiated or used by clients.
+ *
+ * @since 3.20
+ *
+ */
+public class FileSnapshot extends AbstractResourceSnapshot {
+
+ String name;
+
+ URI location;
+
+ String charset;
+
+ private IFileContentSnapshot fileContentDescription;
+
+ /**
+ * Create a FileSnapshot that can be used to later restore the given file. The
+ * file typically already exists, but this constructor will not fail if the file
+ * does not exist.
+ *
+ * @param file the file to be restored.
+ */
+ public FileSnapshot(IFile file) {
+ super(file);
+ this.name = file.getName();
+ try {
+ this.charset = file.getCharset(false);
+ } catch (CoreException e) {
+ // we don't care, a null charset is fine.
+ }
+ if (file.isLinked()) {
+ location = file.getLocationURI();
+ }
+
+ }
+
+ /**
+ * Create a file snapshot from the specified file handle. The handle does not
+ * exist, so no information should be derived from it. If a location path is
+ * specified, this file should represent a link to another location. The content
+ * description describes any state that should be used when the file resource is
+ * created.
+ *
+ * @param file the file to be described
+ * @param linkLocation the location of the file's link, or
+ * null
if the file is not linked
+ * @param fileContentDescription the file content description that can be used
+ * to get information about the file, such as its
+ * initial content
+ */
+ public FileSnapshot(IFile file, URI linkLocation,
+ IFileContentSnapshot fileContentDescription) {
+ super(file);
+ this.name = file.getName();
+ this.location = linkLocation;
+ this.charset = null;
+ this.fileContentDescription = fileContentDescription;
+ }
+
+ @Override
+ public void recordStateFromHistory(IResource resource,
+ IProgressMonitor monitor) throws CoreException {
+ Assert.isLegal(resource.getType() == IResource.FILE);
+
+ if (location != null) {
+ // file is linked, no need to record any history
+ return;
+ }
+ IFileState[] states = ((IFile) resource).getHistory(monitor);
+ if (states.length > 0) {
+ final IFileState state = getMatchingFileState(states);
+ this.fileContentDescription = new IFileContentSnapshot() {
+ @Override
+ public boolean exists() {
+ return state.exists();
+ }
+
+ @Override
+ public InputStream getContents() throws CoreException {
+ return state.getContents();
+ }
+
+ @Override
+ public String getCharset() throws CoreException {
+ return state.getCharset();
+ }
+ };
+ }
+ }
+
+ @Override
+ public IResource createResourceHandle() {
+ IWorkspaceRoot workspaceRoot = parent.getWorkspace().getRoot();
+ IPath fullPath = parent.getFullPath().append(name);
+ return workspaceRoot.getFile(fullPath);
+ }
+
+ @Override
+ public void createExistentResourceFromHandle(IResource resource, IProgressMonitor mon) throws CoreException {
+
+ Assert.isLegal(resource instanceof IFile);
+ if (resource.exists()) {
+ return;
+ }
+ IFile fileHandle = (IFile) resource;
+ SubMonitor subMonitor = SubMonitor.convert(mon, 200);
+ subMonitor.setTaskName(ResourceSnapshotMessages.FileDescription_NewFileProgress);
+ try {
+ if (location != null) {
+ fileHandle.createLink(location, IResource.ALLOW_MISSING_LOCAL, subMonitor.split(200));
+ } else {
+ InputStream contents = new ByteArrayInputStream(
+ ResourceSnapshotMessages.FileDescription_ContentsCouldNotBeRestored
+ .getBytes());
+ // Retrieve the contents from the file content
+ // description. Other file state attributes, such as timestamps,
+ // have already been retrieved from the original IResource
+ // object and are restored in #restoreResourceAttributes
+ if (fileContentDescription != null
+ && fileContentDescription.exists()) {
+ contents = fileContentDescription.getContents();
+ }
+ fileHandle.create(contents, false, subMonitor.split(100));
+ fileHandle.setCharset(charset, subMonitor.split(100));
+ }
+ } catch (CoreException e) {
+ if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) {
+ fileHandle.refreshLocal(IResource.DEPTH_ZERO, null);
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ if (location != null) {
+ return super.isValid();
+ }
+ return super.isValid() && fileContentDescription != null
+ && fileContentDescription.exists();
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /*
+ * Get the file state that matches this file snapshot. The local time stamp is
+ * used to try to find a matching file state. If none can be found, the most
+ * recent copy of the file state is used.
+ */
+ private IFileState getMatchingFileState(IFileState[] states) {
+ for (IFileState state : states) {
+ if (localTimeStamp == state.getModificationTime()) {
+ return state;
+ }
+ }
+ return states[0];
+
+ }
+
+ @Override
+ protected void restoreResourceAttributes(IResource resource)
+ throws CoreException {
+ super.restoreResourceAttributes(resource);
+ Assert.isLegal(resource instanceof IFile);
+ IFile file = (IFile) resource;
+ if (charset != null) {
+ file.setCharset(charset, null);
+ }
+ }
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/FolderSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/FolderSnapshot.java
new file mode 100644
index 00000000000..2b094bbd956
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/FolderSnapshot.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ ******************************************************************************/
+
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import java.net.URI;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceFilterDescription;
+import org.eclipse.core.resources.IWorkspaceRoot;
+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.SubMonitor;
+
+/**
+ * FolderDescription is a lightweight snapshot that describes a folder to be
+ * created.
+ *
+ * This class is not intended to be instantiated or used by clients.
+ *
+ * @since 3.20
+ *
+ */
+public class FolderSnapshot extends ContainerSnapshot {
+
+ private boolean virtual = false;
+
+ /**
+ * Create a FolderSnapshot from the specified folder handle. Typically used when
+ * the folder handle represents a resource that actually exists, although it
+ * will not fail if the resource is non-existent.
+ *
+ * @param folder the folder to be described
+ * @param virtual the folder is a virtual folder
+ */
+ public FolderSnapshot(IFolder folder, boolean virtual) {
+ super(folder);
+ this.virtual = virtual;
+ }
+
+ /**
+ * Create a FolderSnapshot from the specified folder handle. If the folder to be
+ * created should be linked to a different location, specify the location.
+ *
+ * @param folder the folder to be described
+ * @param linkLocation the location to which the folder is linked, or
+ * null
if it is not linked
+ */
+ public FolderSnapshot(IFolder folder, URI linkLocation) {
+ super(folder);
+ this.name = folder.getName();
+ this.location = linkLocation;
+ }
+
+ @Override
+ public IResource createResourceHandle() {
+ IWorkspaceRoot workspaceRoot = getWorkspace().getRoot();
+ IPath folderPath = parent.getFullPath().append(name);
+ return workspaceRoot.getFolder(folderPath);
+ }
+
+ @Override
+ public void createExistentResourceFromHandle(IResource resource, IProgressMonitor mon) throws CoreException {
+ Assert.isLegal(resource instanceof IFolder);
+ if (resource.exists()) {
+ return;
+ }
+ IFolder folderHandle = (IFolder) resource;
+ SubMonitor subMonitor = SubMonitor.convert(mon, 300);
+ subMonitor.setTaskName(ResourceSnapshotMessages.FolderDescription_NewFolderProgress);
+ if (filters != null) {
+ SubMonitor loopMonitor = subMonitor.split(100).setWorkRemaining(filters.length);
+ for (IResourceFilterDescription filter : filters) {
+ folderHandle.createFilter(filter.getType(), filter.getFileInfoMatcherDescription(), 0,
+ loopMonitor.split(1));
+ }
+ }
+ subMonitor.setWorkRemaining(200);
+ if (location != null) {
+ folderHandle.createLink(location, IResource.ALLOW_MISSING_LOCAL, subMonitor.split(100));
+ } else {
+ folderHandle.create(virtual ? IResource.VIRTUAL : 0, true, subMonitor.split(100));
+ }
+ createChildResources(folderHandle, subMonitor.split(100));
+ }
+}
\ No newline at end of file
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/IFileContentSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/IFileContentSnapshot.java
new file mode 100644
index 00000000000..69fc87c0ebe
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/IFileContentSnapshot.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import java.io.InputStream;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * IFileContentSnapshot is a description of a file's content.
+ *
+ * This class is not intended to be instantiated or used by clients.
+ *
+ * @since 3.20
+ *
+ */
+public interface IFileContentSnapshot {
+ /**
+ * Returns an open input stream on the contents of the file described. The
+ * client is responsible for closing the stream when finished.
+ *
+ * @return an input stream containing the contents of the file
+ * @throws CoreException
+ * any CoreException encountered retrieving the contents
+ */
+ InputStream getContents() throws CoreException;
+
+ /**
+ * Returns whether this file content description still exists. If it does
+ * not exist, it will be unable to produce the contents.
+ *
+ * @return true
if this description exists, and
+ * false
if it does not
+ */
+ boolean exists();
+
+ /**
+ * Returns the name of a charset encoding to be used when decoding the
+ * contents into characters. Returns null
if a charset
+ * has not been explicitly specified.
+ *
+ * @return the name of a charset, or null
+ * @throws CoreException
+ * any CoreException encountered while determining the character
+ * set
+ *
+ */
+ String getCharset() throws CoreException;
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/MarkerSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/MarkerSnapshot.java
new file mode 100644
index 00000000000..716fe68287c
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/MarkerSnapshot.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ ******************************************************************************/
+
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import java.util.Map;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.undo.snapshot.IMarkerSnapshot;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * MarkerSnapshot is a lightweight snapshot of a marker that can be used to
+ * describe a marker to be created or updated.
+ *
+ * This class is not intended to be instantiated or used by clients.
+ *
+ * @since 3.20
+ *
+ */
+public class MarkerSnapshot implements IMarkerSnapshot {
+ String type;
+
+ Map attributes;
+
+ IResource resource;
+
+ /**
+ *
+ * Create a marker snapshot from the specified marker.
+ *
+ * @param marker the marker to be described
+ * @throws CoreException
+ */
+ public MarkerSnapshot(IMarker marker) throws CoreException {
+ this.type = marker.getType();
+ this.attributes = marker.getAttributes();
+ this.resource = marker.getResource();
+
+ }
+
+ /**
+ * Create a marker snapshot from the specified marker type, attributes, and
+ * resource.
+ *
+ * @param type the type of marker to be created.
+ * @param attributes the attributes to be assigned to the marker
+ * @param resource the resource on which the marker should be created
+ */
+ public MarkerSnapshot(String type, Map attributes, IResource resource) {
+ this.type = type;
+ this.attributes = attributes;
+ this.resource = resource;
+ }
+
+ /**
+ * Create a marker from the marker description.
+ *
+ * @return the created marker
+ * @throws CoreException
+ */
+ @Override
+ public IMarker createMarker() throws CoreException {
+ IMarker marker = resource.createMarker(type);
+ marker.setAttributes(attributes);
+ return marker;
+ }
+
+ /**
+ * Update an existing marker using the attributes in the marker description.
+ *
+ * @param marker
+ * the marker to be updated
+ * @throws CoreException
+ */
+ @Override
+ public void updateMarker(IMarker marker) throws CoreException {
+ marker.setAttributes(attributes);
+ }
+
+ /**
+ * Return the resource associated with this marker.
+ *
+ * @return the resource associated with this marker
+ */
+ @Override
+ public IResource getResource() {
+ return resource;
+ }
+
+ /**
+ * Return the marker type associated with this marker.
+ *
+ * @return the string marker type of this marker
+ */
+ @Override
+ public String getType() {
+ return type;
+ }
+}
\ No newline at end of file
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ProjectSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ProjectSnapshot.java
new file mode 100644
index 00000000000..59623f9180a
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ProjectSnapshot.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ ******************************************************************************/
+
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+
+/**
+ * ProjectSnapshot is a lightweight snapshot that describes a project to be
+ * created.
+ *
+ * This class is not intended to be instantiated or used by clients.
+ *
+ * @since 3.20
+ *
+ */
+public class ProjectSnapshot extends ContainerSnapshot {
+
+ private IProjectDescription projectDescription;
+ private boolean openOnCreate = true;
+
+ /**
+ * Create a project snapshot from a specified project.
+ *
+ * @param project The project to be described. The project must exist.
+ */
+ public ProjectSnapshot(IProject project) {
+ super(project);
+ Assert.isLegal(project.exists());
+ if (project.isOpen()) {
+ try {
+ this.projectDescription = project.getDescription();
+ } catch (CoreException e) {
+ // Eat this exception because it only occurs when the project
+ // is not accessible and we have already checked this. We
+ // don't want to propagate the CoreException into the
+ // constructor
+ // API.
+ }
+ } else {
+ openOnCreate = false;
+ }
+ }
+
+ /**
+ * Create a project snapshot from a specified IProjectDescription. Used when the
+ * project does not yet exist.
+ *
+ * @param projectDescription the project description for the future project
+ */
+ public ProjectSnapshot(IProjectDescription projectDescription) {
+ super();
+ this.projectDescription = projectDescription;
+ }
+
+ @Override
+ public IResource createResourceHandle() {
+ return ResourcesPlugin.getWorkspace().getRoot().getProject(getName());
+ }
+
+ @Override
+ public void createExistentResourceFromHandle(IResource resource,
+ IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 200);
+ Assert.isLegal(resource instanceof IProject);
+ if (resource.exists()) {
+ return;
+ }
+ IProject projectHandle = (IProject) resource;
+ subMonitor.setTaskName(ResourceSnapshotMessages.FolderDescription_NewFolderProgress);
+ if (projectDescription == null) {
+ projectHandle.create(subMonitor.split(100));
+ } else {
+ projectHandle.create(projectDescription, subMonitor.split(100));
+ }
+
+ if (openOnCreate) {
+ projectHandle.open(IResource.NONE, subMonitor.split(100));
+ }
+ }
+
+ @Override
+ public String getName() {
+ if (projectDescription != null) {
+ return projectDescription.getName();
+ }
+ return super.getName();
+ }
+
+ @Override
+ public boolean verifyExistence(boolean checkMembers) {
+ // We can only check members if the project is open.
+ IProject projectHandle = (IProject) createResourceHandle();
+ if (projectHandle.isAccessible()) {
+ return super.verifyExistence(checkMembers);
+ }
+ return super.verifyExistence(false);
+ }
+}
\ No newline at end of file
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ResourceSnapshotMessages.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ResourceSnapshotMessages.java
new file mode 100644
index 00000000000..368396ccc21
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ResourceSnapshotMessages.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+package org.eclipse.core.internal.resources.undo.snapshot;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Helper class to get NLSed messages.
+ *
+ * @since 3.20
+ *
+ */
+public final class ResourceSnapshotMessages extends NLS {
+
+ private static final String BUNDLE_NAME= ResourceSnapshotMessages.class.getName();
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, ResourceSnapshotMessages.class);
+ }
+
+ private ResourceSnapshotMessages() {
+ // Do not instantiate
+ }
+
+ public static String FileDescription_NewFileProgress;
+ public static String FileDescription_ContentsCouldNotBeRestored;
+ public static String FolderDescription_NewFolderProgress;
+ public static String FolderDescription_SavingUndoInfoProgress;
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ResourceSnapshotMessages.properties b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ResourceSnapshotMessages.properties
new file mode 100644
index 00000000000..ef207f7d672
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/undo/snapshot/ResourceSnapshotMessages.properties
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright (c) 2006, 2023 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
+###############################################################################
+
+FileDescription_NewFileProgress=Creating new file...
+FileDescription_ContentsCouldNotBeRestored=Unexpected error. File contents could not be restored from local history during undo/redo.
+FolderDescription_NewFolderProgress=Creating new folder...
+FolderDescription_SavingUndoInfoProgress=Saving folder info...
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IContainerSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IContainerSnapshot.java
new file mode 100644
index 00000000000..e5dfe0f2b73
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IContainerSnapshot.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+package org.eclipse.core.resources.undo.snapshot;
+
+import java.net.URI;
+import org.eclipse.core.resources.IResourceFilterDescription;
+
+/**
+ * IContainerSnapshot is a lightweight description that describes a container to
+ * be created.
+ *
+ * This class is not intended to be instantiated by clients.
+ *
+ * @since 3.20
+ */
+public interface IContainerSnapshot extends IResourceSnapshot {
+
+ /**
+ * Get a list of snapshots of members of this container
+ *
+ * @return a list of snapshots
+ */
+ public IResourceSnapshot[] getMembers();
+
+ /**
+ * Add the specified resource description as a member of this resource
+ * description
+ *
+ * @param member the resource description considered a member of this container.
+ */
+ public void addMember(IResourceSnapshot member);
+
+ /**
+ * Set the location to which this container is linked.
+ *
+ * @param linkLocation the location URI, or null
if there is no
+ * link
+ */
+ public void setLocation(URI linkLocation);
+
+ /**
+ * Set the filters to which should be created on this container.
+ *
+ * @param filterList the filters
+ */
+ public void setFilters(IResourceFilterDescription[] filterList);
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IMarkerSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IMarkerSnapshot.java
new file mode 100644
index 00000000000..1ceb6c723ad
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IMarkerSnapshot.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+package org.eclipse.core.resources.undo.snapshot;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * IMarkerSnapshot is a lightweight snapshot of a marker for the purposes of
+ * undoing.
+ *
+ * This class is not intended to be instantiated by clients.
+ *
+ * @since 3.20
+ */
+public interface IMarkerSnapshot {
+
+ /**
+ * Create a marker from the marker description.
+ *
+ * @return the created marker
+ * @throws CoreException
+ */
+ public IMarker createMarker() throws CoreException;
+
+ /**
+ * Update an existing marker using the attributes in the marker description.
+ *
+ * @param marker the marker to be updated
+ * @throws CoreException
+ */
+ public void updateMarker(IMarker marker) throws CoreException;
+
+ /**
+ * Return the resource associated with this marker.
+ *
+ * @return the resource associated with this marker
+ */
+ public IResource getResource();
+
+ /**
+ * Return the marker type associated with this marker.
+ *
+ * @return the string marker type of this marker
+ */
+ public String getType();
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IResourceSnapshot.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IResourceSnapshot.java
new file mode 100644
index 00000000000..79f8daa1ee2
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/IResourceSnapshot.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ ******************************************************************************/
+
+package org.eclipse.core.resources.undo.snapshot;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * IResourceSnapshot is a lightweight snapshot that describes the common
+ * attributes of a resource to be created.
+ *
+ * This class is not intended to be extended by clients.
+ *
+ * @since 3.20
+ *
+ */
+public interface IResourceSnapshot {
+
+ /**
+ * Create a resource handle that can be used to create a resource from this
+ * resource description. This handle can be used to create the actual
+ * resource, or to describe the creation to a resource delta factory.
+ *
+ * @return the resource handle that can be used to create a resource from
+ * this description
+ */
+ public IResource createResourceHandle();
+
+ /**
+ * Get the name of this resource.
+ *
+ * @return the name of the Resource
+ */
+ public String getName();
+
+ /**
+ * Create an existent resource from this resource description.
+ *
+ * @param monitor
+ * the progress monitor to use
+ * @return a resource that has the attributes of this resource description
+ * @throws CoreException if creation failed
+ */
+ public IResource createResource(IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Given a resource handle, create an actual resource with the attributes of
+ * the receiver resource description.
+ *
+ * @param resource
+ * the resource handle
+ * @param monitor
+ * the progress monitor to be used when creating the resource
+ * @throws CoreException if creation failed
+ */
+ public void createExistentResourceFromHandle(IResource resource,
+ IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Return a boolean indicating whether this resource description has enough
+ * information to create a resource.
+ *
+ * @return true
if the resource can be created, and
+ * false
if it does not have enough information
+ */
+ public boolean isValid();
+
+ /**
+ * Record the appropriate state of this resource description using
+ * any available resource history.
+ *
+ * @param resource
+ * the resource whose state is to be recorded.
+ * @param monitor
+ * the progress monitor to be used
+ * @throws CoreException in case of error
+ */
+ public void recordStateFromHistory(IResource resource,
+ IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Return a boolean indicating whether this description represents an
+ * existent resource.
+ *
+ * @param checkMembers
+ * Use true
if members should also exist in order
+ * for this description to be considered existent. A value of
+ * false
indicates that the existence of members
+ * does not matter.
+ *
+ * @return a boolean indicating whether this description represents an
+ * existent resource.
+ */
+ public boolean verifyExistence(boolean checkMembers);
+}
diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/ResourceSnapshotFactory.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/ResourceSnapshotFactory.java
new file mode 100644
index 00000000000..7ffe66d9cc7
--- /dev/null
+++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/undo/snapshot/ResourceSnapshotFactory.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2023 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
+ * Red Hat Inc - Adapted from classes in org.eclipse.ui.ide.undo and org.eclipse.ui.internal.ide.undo
+ *******************************************************************************/
+package org.eclipse.core.resources.undo.snapshot;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Map;
+import org.eclipse.core.internal.resources.undo.snapshot.ContainerSnapshot;
+import org.eclipse.core.internal.resources.undo.snapshot.FileSnapshot;
+import org.eclipse.core.internal.resources.undo.snapshot.FolderSnapshot;
+import org.eclipse.core.internal.resources.undo.snapshot.IFileContentSnapshot;
+import org.eclipse.core.internal.resources.undo.snapshot.MarkerSnapshot;
+import org.eclipse.core.internal.resources.undo.snapshot.ProjectSnapshot;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * ResourceSnapshotFactory provides utility methods for creating snapshots of
+ * resources or markers.
+ *
+ * @since 3.20
+ */
+public class ResourceSnapshotFactory {
+ /**
+ * Create a resource snapshot given the specified resource. The resource is
+ * assumed to exist.
+ *
+ * @param resource the resource from which a description should be created
+ * @return the resource description
+ */
+ public static IResourceSnapshot fromResource(IResource resource) {
+ if (resource.getType() == IResource.PROJECT) {
+ return new ProjectSnapshot((IProject) resource);
+ } else if (resource.getType() == IResource.FOLDER) {
+ return new FolderSnapshot((IFolder) resource, resource.isVirtual());
+ } else if (resource.getType() == IResource.FILE) {
+ return new FileSnapshot((IFile) resource);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Create a project snapshot from a specified IProjectDescription. Used when the
+ * project does not yet exist.
+ *
+ * @param projectDescription the project description for the future project
+ */
+ public static IContainerSnapshot fromProjectDescription(IProjectDescription projectDescription) {
+ return new ProjectSnapshot(projectDescription);
+ }
+
+ /**
+ * Create a container description from the specified container handle that can
+ * be used to create the container. The returned ContainerDescription should
+ * represent any non-existing parents in addition to the specified container.
+ *
+ * @param container the handle of the container to be described
+ * @return a container description describing the container and any non-existing
+ * parents.
+ */
+ public static IContainerSnapshot fromContainer(IContainer container) {
+ return ContainerSnapshot.fromContainer(container);
+ }
+
+ /**
+ * Create a group container description from the specified container handle that
+ * can be used to create the container. The returned ContainerDescription should
+ * represent any non-existing parents in addition to the specified container.
+ *
+ * @param container the handle of the container to be described
+ * @return a container description describing the container and any non-existing
+ * parents.
+ */
+ public static IContainerSnapshot fromVirtualFolderContainer(IContainer container) {
+ return ContainerSnapshot.fromContainer(container, true);
+ }
+
+ /**
+ * Create a file snapshot from the specified file handle. The handle does not
+ * exist, so no information should be derived from it. If a location path is
+ * specified, this file should represent a link to another location. The content
+ * description describes any state that should be used when the file resource is
+ * created.
+ *
+ * @param file the file to be described
+ * @param linkLocation the location of the file's link, or null
if
+ * the file is not linked
+ * @param contents an input stream representing the contents of the file
+ */
+ public static IResourceSnapshot fromFileDetails(IFile file, URI linkLocation, InputStream contents) {
+ return new FileSnapshot(file, linkLocation, createFileContentDescription(file, contents));
+ }
+
+ /**
+ *
+ * Create a marker snapshot from the specified marker.
+ *
+ * @param marker the marker to be described
+ * @throws CoreException
+ */
+ public static IMarkerSnapshot fromMarker(IMarker marker) throws CoreException {
+ return new MarkerSnapshot(marker);
+ }
+
+ /**
+ * Create a marker snapshot from the specified marker type, attributes, and
+ * resource.
+ *
+ * @param type the type of marker to be created.
+ * @param attributes the attributes to be assigned to the marker
+ * @param resource the resource on which the marker should be created
+ */
+ public static IMarkerSnapshot fromMarkerDetails(String type, Map attributes, IResource resource) {
+ return new MarkerSnapshot(type, attributes, resource);
+ }
+
+ /*
+ * Create a file state that represents the desired contents and attributes of
+ * the file to be created. Used to mimic file history when a resource is first
+ * created.
+ */
+ private static IFileContentSnapshot createFileContentDescription(final IFile file, final InputStream contents) {
+ return new IFileContentSnapshot() {
+ @Override
+ public InputStream getContents() {
+ if (contents != null) {
+ return contents;
+ }
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ @Override
+ public String getCharset() {
+ try {
+ return file.getCharset(false);
+ } catch (CoreException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean exists() {
+ return true;
+ }
+ };
+ }
+}