diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java index 8a94d8ace4d..5cc21b19a76 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java @@ -1275,7 +1275,7 @@ private void prepareWrite(IFile target, IFileInfo fileInfo, int updateFlags, boo throw new ResourceException(IResourceStatus.NOT_FOUND_LOCAL, target.getFullPath(), message, null); } } else { - if (!BitMask.isSet(updateFlags, IResource.REPLACE) && fileInfo.exists()) { + if (fileInfo.exists()) { String message = NLS.bind(Messages.localstore_resourceExists, target.getFullPath()); throw new ResourceException(IResourceStatus.EXISTS_LOCAL, target.getFullPath(), message, null); } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java index c699cae990a..b5a2ed5cd22 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java @@ -126,7 +126,7 @@ public void create(InputStream content, int updateFlags, IProgressMonitor monito final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); try { workspace.prepareOperation(rule, subMonitor.newChild(1)); - checkCreatable(updateFlags); + checkCreatable(); workspace.beginOperation(true); IFileStore store = getStore(); IFileInfo localInfo = create(updateFlags, subMonitor.newChild(40), store); @@ -172,7 +172,7 @@ public void create(byte[] content, int updateFlags, IProgressMonitor monitor) th final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); try { workspace.prepareOperation(rule, subMonitor.newChild(1)); - checkCreatable(updateFlags); + checkCreatable(); workspace.beginOperation(true); IFileStore store = getStore(); IFileInfo localInfo = create(updateFlags, subMonitor.newChild(40), store); @@ -202,10 +202,8 @@ public void create(byte[] content, int updateFlags, IProgressMonitor monitor) th } } - private void checkCreatable(int updateFlags) throws CoreException { - if ((updateFlags & IResource.REPLACE) == 0) { - checkDoesNotExist(); - } + private void checkCreatable() throws CoreException { + checkDoesNotExist(); Container parent = (Container) getParent(); ResourceInfo info = parent.getResourceInfo(false, false); parent.checkAccessible(getFlags(info)); @@ -232,7 +230,7 @@ private IFileInfo create(int updateFlags, SubMonitor subMonitor, IFileStore stor } } } else { - if (!BitMask.isSet(updateFlags, IResource.REPLACE) && localInfo.exists()) { + if (localInfo.exists()) { // return an appropriate error message for case variant collisions if (!Workspace.caseSensitive) { String name = getLocalManager().getLocalName(store); @@ -433,6 +431,10 @@ public void setContents(InputStream content, int updateFlags, IProgressMonitor m checkAccessible(getFlags(info)); workspace.beginOperation(true); IFileInfo fileInfo = getStore().fetchInfo(); + if (BitMask.isSet(updateFlags, IResource.DERIVED)) { + // update of derived flag during IFile.write: + info.set(ICoreConstants.M_DERIVED); + } internalSetContents(content, fileInfo, updateFlags, false, subMonitor.newChild(99)); } catch (OperationCanceledException e) { workspace.getWorkManager().operationCanceled(); @@ -461,6 +463,10 @@ public void setContents(byte[] content, int updateFlags, IProgressMonitor monito checkAccessible(getFlags(info)); workspace.beginOperation(true); IFileInfo fileInfo = getStore().fetchInfo(); + if (BitMask.isSet(updateFlags, IResource.DERIVED)) { + // update of derived flag during IFile.write: + info.set(ICoreConstants.M_DERIVED); + } internalSetContents(content, fileInfo, updateFlags, false, subMonitor.newChild(99)); } catch (OperationCanceledException e) { workspace.getWorkManager().operationCanceled(); diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java index dda165e24b6..2f8338c43eb 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java @@ -304,13 +304,6 @@ public interface IFile extends IResource, IEncodedStorage, IAdaptable { * with a value of true immediately after creating the resource. *

*

- * The {@link IResource#KEEP_HISTORY} update flag indicates that the history of this file is kept if any - *

- *

- * The {@link IResource#REPLACE} update flag indicates that this resource is overridden if already existing. - * - *

- *

* The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource * should immediately be set as a team private resource. Specifying this flag * is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)} @@ -338,9 +331,7 @@ public interface IFile extends IResource, IEncodedStorage, IAdaptable { * @param source an input stream containing the initial contents of the file, * or null if the file should be marked as not local * @param updateFlags bit-wise or of update flag constants - * ({@link IResource#FORCE}, {@link IResource#DERIVED}, - * {@link IResource#KEEP_HISTORY}, {@link IResource#REPLACE}, - * and {@link IResource#TEAM_PRIVATE}) + * ({@link IResource#FORCE}, {@link IResource#DERIVED}, and {@link IResource#TEAM_PRIVATE}) * @param monitor a progress monitor, or null if progress * reporting is not desired * @exception CoreException if this method fails. Reasons include: @@ -400,9 +391,7 @@ public default void create(byte[] content, boolean force, boolean derived, IProg * * @param content the content as byte array * @param createFlags bit-wise or of flag constants ({@link IResource#FORCE}, - * {@link IResource#DERIVED}, - * {@link IResource#KEEP_HISTORY}, - * {@link IResource#REPLACE}, and + * {@link IResource#DERIVED}, and * {@link IResource#TEAM_PRIVATE}) * @param monitor a progress monitor, or null if progress * reporting is not desired @@ -416,17 +405,19 @@ public default void create(byte[] content, int createFlags, IProgressMonitor mon /** * Creates the file and sets the file content. If the file already exists in * workspace its content is replaced. Shortcut for calling - * {@link #create(byte[], int, IProgressMonitor)} with flags depending on the - * boolean parameters given and {@link IResource#REPLACE}. + * {@link #setContents(byte[], boolean, boolean, IProgressMonitor)} or + * {@link #create(byte[], boolean, boolean, IProgressMonitor)} if the file does + * not {@link #exists()}. * * @param content the new content bytes. Must not be null. * @param force a flag controlling how to deal with resources that are not * in sync with the local file system * @param derived Specifying this flag is equivalent to atomically calling * {@link IResource#setDerived(boolean)} immediately after - * creating the resource or non-atomically setting the - * derived flag before setting the content of an already - * existing file + * creating the resource or atomically setting the derived + * flag before setting the content of an already existing + * file if derived==true. A value of false will not update + * the derived flag of an existing file. * @param keepHistory a flag indicating whether or not store the current * contents in the local history if the file did already * exist @@ -438,8 +429,14 @@ public default void create(byte[] content, int createFlags, IProgressMonitor mon public default void write(byte[] content, boolean force, boolean derived, boolean keepHistory, IProgressMonitor monitor) throws CoreException { Objects.requireNonNull(content); - create(content, (force ? IResource.FORCE : 0) | (derived ? IResource.DERIVED : 0) - | (keepHistory ? IResource.KEEP_HISTORY : 0) | IResource.REPLACE, monitor); + if (exists()) { + int updateFlags = (derived ? IResource.DERIVED : IResource.NONE) + | (force ? IResource.FORCE : IResource.NONE) + | (keepHistory ? IResource.KEEP_HISTORY : IResource.NONE); + setContents(content, updateFlags, monitor); + } else { + create(content, force, derived, monitor); + } } /** diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java index fbb0e53ca69..91ea4c35313 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java @@ -44,6 +44,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFileState; @@ -53,7 +54,9 @@ import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceDescription; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; @@ -497,23 +500,60 @@ public void testWrite() throws CoreException { boolean setDerived = i % 2 == 0; boolean deleteBefore = (i >> 1) % 2 == 0; boolean keepHistory = (i >> 2) % 2 == 0; + boolean oldDerived1 = false; if (deleteBefore) { derived.delete(false, null); + } else { + oldDerived1 = derived.isDerived(); } assertEquals(!deleteBefore, derived.exists()); FussyProgressMonitor monitor = new FussyProgressMonitor(); + AtomicInteger changeCount = new AtomicInteger(); + ResourcesPlugin.getWorkspace().addResourceChangeListener(event -> changeCount.incrementAndGet()); derived.write(("updateOrCreate" + i).getBytes(), false, setDerived, keepHistory, monitor); + assertEquals("not atomic", 1, changeCount.get()); monitor.assertUsedUp(); - assertEquals(setDerived, derived.isDerived()); + if (deleteBefore) { + assertEquals(setDerived, derived.isDerived()); + } else { + assertEquals(oldDerived1 || setDerived, derived.isDerived()); + } assertFalse(derived.isTeamPrivateMember()); assertTrue(derived.exists()); IFileState[] history1 = derived.getHistory(null); + changeCount.set(0); derived.write(("update" + i).getBytes(), false, false, keepHistory, null); + boolean oldDerived2 = derived.isDerived(); + assertEquals(oldDerived2, derived.isDerived()); + assertEquals("not atomic", 1, changeCount.get()); IFileState[] history2 = derived.getHistory(null); - assertEquals(keepHistory ? 1 : 0, history2.length - history1.length); + assertEquals((keepHistory && !oldDerived2) ? 1 : 0, history2.length - history1.length); } } + + @Test + public void testWriteRule() throws CoreException { + IFile resource = projects[0].getFile("derived.txt"); + createInWorkspace(projects[0]); + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + resource.delete(false, null); + AtomicInteger changeCount = new AtomicInteger(); + ResourcesPlugin.getWorkspace().addResourceChangeListener(event -> changeCount.incrementAndGet()); + workspace.run(pm -> { + resource.write(("create").getBytes(), false, false, false, null); + }, workspace.getRuleFactory().createRule(resource), IWorkspace.AVOID_UPDATE, null); + assertTrue(resource.exists()); + assertEquals("not atomic", 1, changeCount.get()); + // test that modifyRule can be used for IFile.write() if the file already exits: + changeCount.set(0); + workspace.run(pm -> { + resource.write(("replace").getBytes(), false, false, false, null); + }, workspace.getRuleFactory().modifyRule(resource), IWorkspace.AVOID_UPDATE, null); + assertTrue(resource.exists()); + assertEquals("not atomic", 1, changeCount.get()); + } + @Test public void testDeltaOnCreateDerived() throws CoreException { IFile derived = projects[0].getFile("derived.txt");