Skip to content

Commit 3007284

Browse files
EcljpseB0Tjukzi
authored andcommitted
Provide API for IFile.createOrReplace(...)
A typical used pattern to replace a File content is ``` if (file.exists()) { file.setContents(ByteArrayInputStream, ...); } else { file.create(ByteArrayInputStream, ...); } } ``` This can be replaced by convenience createOrReplace(). For performance reasons it takes the content as byte array like java.nio.file.Files.write(Path, byte[], OpenOption...). An example consumer is the performance hotspot in JDT to write .class files during build: org.eclipse.jdt.internal.core.builder.AbstractImageBuilder.writeClassFileContents(ClassFile, IFile, String, boolean, SourceFile)
1 parent a59ce1e commit 3007284

File tree

5 files changed

+154
-63
lines changed

5 files changed

+154
-63
lines changed

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ private void prepareWrite(IFile target, IFileInfo fileInfo, int updateFlags, boo
12751275
throw new ResourceException(IResourceStatus.NOT_FOUND_LOCAL, target.getFullPath(), message, null);
12761276
}
12771277
} else {
1278-
if (fileInfo.exists()) {
1278+
if (!BitMask.isSet(updateFlags, IResource.REPLACE) && fileInfo.exists()) {
12791279
String message = NLS.bind(Messages.localstore_resourceExists, target.getFullPath());
12801280
throw new ResourceException(IResourceStatus.EXISTS_LOCAL, target.getFullPath(), message, null);
12811281
}

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/File.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public void create(InputStream content, int updateFlags, IProgressMonitor monito
126126
final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
127127
try {
128128
workspace.prepareOperation(rule, subMonitor.newChild(1));
129-
checkCreatable();
129+
checkCreatable(updateFlags);
130130
workspace.beginOperation(true);
131131
IFileStore store = getStore();
132132
IFileInfo localInfo = create(updateFlags, subMonitor.newChild(40), store);
@@ -172,7 +172,7 @@ public void create(byte[] content, int updateFlags, IProgressMonitor monitor) th
172172
final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
173173
try {
174174
workspace.prepareOperation(rule, subMonitor.newChild(1));
175-
checkCreatable();
175+
checkCreatable(updateFlags);
176176
workspace.beginOperation(true);
177177
IFileStore store = getStore();
178178
IFileInfo localInfo = create(updateFlags, subMonitor.newChild(40), store);
@@ -202,8 +202,10 @@ public void create(byte[] content, int updateFlags, IProgressMonitor monitor) th
202202
}
203203
}
204204

205-
private void checkCreatable() throws CoreException {
206-
checkDoesNotExist();
205+
private void checkCreatable(int updateFlags) throws CoreException {
206+
if ((updateFlags & IResource.REPLACE) == 0) {
207+
checkDoesNotExist();
208+
}
207209
Container parent = (Container) getParent();
208210
ResourceInfo info = parent.getResourceInfo(false, false);
209211
parent.checkAccessible(getFlags(info));
@@ -230,7 +232,7 @@ private IFileInfo create(int updateFlags, SubMonitor subMonitor, IFileStore stor
230232
}
231233
}
232234
} else {
233-
if (localInfo.exists()) {
235+
if (!BitMask.isSet(updateFlags, IResource.REPLACE) && localInfo.exists()) {
234236
// return an appropriate error message for case variant collisions
235237
if (!Workspace.caseSensitive) {
236238
String name = getLocalManager().getLocalName(store);

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Workspace.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import org.eclipse.core.internal.refresh.RefreshManager;
6161
import org.eclipse.core.internal.resources.ComputeProjectOrder.Digraph;
6262
import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexOrder;
63+
import org.eclipse.core.internal.utils.BitMask;
6364
import org.eclipse.core.internal.utils.Messages;
6465
import org.eclipse.core.internal.utils.Policy;
6566
import org.eclipse.core.internal.utils.StringPoolJob;
@@ -1392,7 +1393,7 @@ public ResourceInfo createResource(IResource resource, boolean phantom) throws C
13921393
* be immediately made derived, hidden and/or team private
13931394
*/
13941395
public ResourceInfo createResource(IResource resource, int updateFlags) throws CoreException {
1395-
ResourceInfo info = createResource(resource, null, false, false, false);
1396+
ResourceInfo info = createResource(resource, null, false, BitMask.isSet(updateFlags, IResource.REPLACE), false);
13961397
if ((updateFlags & IResource.DERIVED) != 0)
13971398
info.set(M_DERIVED);
13981399
if ((updateFlags & IResource.TEAM_PRIVATE) != 0)

resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IFile.java

Lines changed: 111 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.InputStream;
2121
import java.io.Reader;
2222
import java.net.URI;
23+
import java.util.Objects;
2324
import org.eclipse.core.internal.utils.FileUtil;
2425
import org.eclipse.core.runtime.CoreException;
2526
import org.eclipse.core.runtime.IAdaptable;
@@ -303,6 +304,13 @@ public interface IFile extends IResource, IEncodedStorage, IAdaptable {
303304
* with a value of <code>true</code> immediately after creating the resource.
304305
* </p>
305306
* <p>
307+
* The {@link IResource#KEEP_HISTORY} update flag indicates that the history of this file is kept if any
308+
* </p>
309+
* <p>
310+
* The {@link IResource#REPLACE} update flag indicates that this resource is overridden if already existing.
311+
*
312+
* </p>
313+
* <p>
306314
* The {@link IResource#TEAM_PRIVATE} update flag indicates that this resource
307315
* should immediately be set as a team private resource. Specifying this flag
308316
* is equivalent to atomically calling {@link IResource#setTeamPrivateMember(boolean)}
@@ -330,7 +338,9 @@ public interface IFile extends IResource, IEncodedStorage, IAdaptable {
330338
* @param source an input stream containing the initial contents of the file,
331339
* or <code>null</code> if the file should be marked as not local
332340
* @param updateFlags bit-wise or of update flag constants
333-
* ({@link IResource#FORCE}, {@link IResource#DERIVED}, and {@link IResource#TEAM_PRIVATE})
341+
* ({@link IResource#FORCE}, {@link IResource#DERIVED},
342+
* {@link IResource#KEEP_HISTORY}, {@link IResource#REPLACE},
343+
* and {@link IResource#TEAM_PRIVATE})
334344
* @param monitor a progress monitor, or <code>null</code> if progress
335345
* reporting is not desired
336346
* @exception CoreException if this method fails. Reasons include:
@@ -390,7 +400,9 @@ public default void create(byte[] content, boolean force, boolean derived, IProg
390400
*
391401
* @param content the content as byte array
392402
* @param createFlags bit-wise or of flag constants ({@link IResource#FORCE},
393-
* {@link IResource#DERIVED}, and
403+
* {@link IResource#DERIVED},
404+
* {@link IResource#KEEP_HISTORY},
405+
* {@link IResource#REPLACE}, and
394406
* {@link IResource#TEAM_PRIVATE})
395407
* @param monitor a progress monitor, or <code>null</code> if progress
396408
* reporting is not desired
@@ -402,81 +414,124 @@ public default void create(byte[] content, int createFlags, IProgressMonitor mon
402414
}
403415

404416
/**
405-
* Creates a new file resource as a member of this handle's parent resource.
406-
* The file's contents will be located in the file specified by the given
407-
* file system path. The given path must be either an absolute file system
408-
* path, or a relative path whose first segment is the name of a workspace path
409-
* variable.
417+
* Creates the file and sets the file content. If the file already exists in
418+
* workspace its content is replaced. Shortcut for calling
419+
* {@link #create(byte[], int, IProgressMonitor)} with flags depending on the
420+
* boolean parameters given and {@link IResource#REPLACE}.
421+
*
422+
* @param content the new content bytes. Must not be null.
423+
* @param force a flag controlling how to deal with resources that are not
424+
* in sync with the local file system
425+
* @param derived Specifying this flag is equivalent to atomically calling
426+
* {@link IResource#setDerived(boolean)} immediately after
427+
* creating the resource or non-atomically setting the
428+
* derived flag before setting the content of an already
429+
* existing file
430+
* @param keepHistory a flag indicating whether or not store the current
431+
* contents in the local history if the file did already
432+
* exist
433+
* @param monitor a progress monitor, or <code>null</code> if progress
434+
* reporting is not desired
435+
* @throws CoreException if this method fails or is canceled.
436+
* @since 3.21
437+
*/
438+
public default void createOrReplace(byte[] content, boolean force, boolean derived, boolean keepHistory,
439+
IProgressMonitor monitor) throws CoreException {
440+
Objects.requireNonNull(content);
441+
create(content, (force ? IResource.FORCE : 0) | (derived ? IResource.DERIVED : 0)
442+
| (keepHistory ? IResource.KEEP_HISTORY : 0) | IResource.REPLACE, monitor);
443+
}
444+
445+
/**
446+
* Creates a new file resource as a member of this handle's parent resource. The
447+
* file's contents will be located in the file specified by the given file
448+
* system path. The given path must be either an absolute file system path, or a
449+
* relative path whose first segment is the name of a workspace path variable.
410450
* <p>
411451
* The {@link IResource#ALLOW_MISSING_LOCAL} update flag controls how this
412452
* method deals with cases where the local file system file to be linked does
413453
* not exist, or is relative to a workspace path variable that is not defined.
414-
* If {@link IResource#ALLOW_MISSING_LOCAL} is specified, the operation will succeed
415-
* even if the local file is missing, or the path is relative to an undefined
416-
* variable. If {@link IResource#ALLOW_MISSING_LOCAL} is not specified, the operation
417-
* will fail in the case where the local file system file does not exist or the
418-
* path is relative to an undefined variable.
454+
* If {@link IResource#ALLOW_MISSING_LOCAL} is specified, the operation will
455+
* succeed even if the local file is missing, or the path is relative to an
456+
* undefined variable. If {@link IResource#ALLOW_MISSING_LOCAL} is not
457+
* specified, the operation will fail in the case where the local file system
458+
* file does not exist or the path is relative to an undefined variable.
419459
* </p>
420460
* <p>
421-
* The {@link IResource#REPLACE} update flag controls how this
422-
* method deals with cases where a resource of the same name as the
423-
* prospective link already exists. If {@link IResource#REPLACE}
424-
* is specified, then the existing linked resource's location is replaced
425-
* by localLocation's value. This does <b>not</b>
426-
* cause the underlying file system contents of that resource to be deleted.
427-
* If {@link IResource#REPLACE} is not specified, this method will
428-
* fail if an existing resource exists of the same name.
461+
* The {@link IResource#REPLACE} update flag controls how this method deals with
462+
* cases where a resource of the same name as the prospective link already
463+
* exists. If {@link IResource#REPLACE} is specified, then the existing linked
464+
* resource's location is replaced by localLocation's value. This does
465+
* <b>not</b> cause the underlying file system contents of that resource to be
466+
* deleted. If {@link IResource#REPLACE} is not specified, this method will fail
467+
* if an existing resource exists of the same name.
429468
* </p>
430469
* <p>
431-
* The {@link IResource#HIDDEN} update flag indicates that this resource
432-
* should immediately be set as a hidden resource. Specifying this flag
433-
* is equivalent to atomically calling {@link IResource#setHidden(boolean)}
434-
* with a value of <code>true</code> immediately after creating the resource.
470+
* The {@link IResource#HIDDEN} update flag indicates that this resource should
471+
* immediately be set as a hidden resource. Specifying this flag is equivalent
472+
* to atomically calling {@link IResource#setHidden(boolean)} with a value of
473+
* <code>true</code> immediately after creating the resource.
435474
* </p>
436475
* <p>
437476
* Update flags other than those listed above are ignored.
438477
* </p>
439478
* <p>
440-
* This method synchronizes this resource with the local file system at the given
441-
* location.
479+
* This method synchronizes this resource with the local file system at the
480+
* given location.
442481
* </p>
443482
* <p>
444-
* This method changes resources; these changes will be reported
445-
* in a subsequent resource change event, including an indication
446-
* that the file has been added to its parent.
483+
* This method changes resources; these changes will be reported in a subsequent
484+
* resource change event, including an indication that the file has been added
485+
* to its parent.
447486
* </p>
448487
* <p>
449-
* This method is long-running; progress and cancellation are provided
450-
* by the given progress monitor.
488+
* This method is long-running; progress and cancellation are provided by the
489+
* given progress monitor.
451490
* </p>
452491
*
453492
* @param localLocation a file system path where the file should be linked
454-
* @param updateFlags bit-wise or of update flag constants
455-
* ({@link IResource#ALLOW_MISSING_LOCAL}, {@link IResource#REPLACE} and {@link IResource#HIDDEN})
456-
* @param monitor a progress monitor, or <code>null</code> if progress
457-
* reporting is not desired
458-
* @exception CoreException if this method fails. Reasons include:
459-
* <ul>
460-
* <li> This resource already exists in the workspace.</li>
461-
* <li> The workspace contains a resource of a different type
462-
* at the same path as this resource.</li>
463-
* <li> The parent of this resource does not exist.</li>
464-
* <li> The parent of this resource is not an open project</li>
465-
* <li> The name of this resource is not valid (according to
466-
* <code>IWorkspace.validateName</code>).</li>
467-
* <li> The corresponding location in the local file system does not exist, or
468-
* is relative to an undefined variable, and <code>ALLOW_MISSING_LOCAL</code> is
469-
* not specified.</li>
470-
* <li> The corresponding location in the local file system is occupied
471-
* by a directory (as opposed to a file).</li>
472-
* <li> Resource changes are disallowed during certain types of resource change
473-
* event notification. See <code>IResourceChangeEvent</code> for more details.</li>
474-
* <li>The team provider for the project which contains this folder does not permit
475-
* linked resources.</li>
476-
* <li>This folder's project contains a nature which does not permit linked resources.</li>
477-
* </ul>
493+
* @param updateFlags bit-wise or of update flag constants
494+
* ({@link IResource#ALLOW_MISSING_LOCAL},
495+
* {@link IResource#REPLACE} and {@link IResource#HIDDEN})
496+
* @param monitor a progress monitor, or <code>null</code> if progress
497+
* reporting is not desired
498+
* @exception CoreException if this method fails. Reasons include:
499+
* <ul>
500+
* <li>This resource already exists in the
501+
* workspace.</li>
502+
* <li>The workspace contains a resource
503+
* of a different type at the same path as
504+
* this resource.</li>
505+
* <li>The parent of this resource does
506+
* not exist.</li>
507+
* <li>The parent of this resource is not
508+
* an open project</li>
509+
* <li>The name of this resource is not
510+
* valid (according to
511+
* <code>IWorkspace.validateName</code>).</li>
512+
* <li>The corresponding location in the
513+
* local file system does not exist, or is
514+
* relative to an undefined variable, and
515+
* <code>ALLOW_MISSING_LOCAL</code> is not
516+
* specified.</li>
517+
* <li>The corresponding location in the
518+
* local file system is occupied by a
519+
* directory (as opposed to a file).</li>
520+
* <li>Resource changes are disallowed
521+
* during certain types of resource change
522+
* event notification. See
523+
* <code>IResourceChangeEvent</code> for
524+
* more details.</li>
525+
* <li>The team provider for the project
526+
* which contains this folder does not
527+
* permit linked resources.</li>
528+
* <li>This folder's project contains a
529+
* nature which does not permit linked
530+
* resources.</li>
531+
* </ul>
478532
* @exception OperationCanceledException if the operation is canceled.
479-
* Cancelation can occur even if no progress monitor is provided.
533+
* Cancelation can occur even if no
534+
* progress monitor is provided.
480535
* @see IResource#isLinked()
481536
* @see IResource#ALLOW_MISSING_LOCAL
482537
* @see IResource#REPLACE

resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@
4646
import java.util.List;
4747
import org.eclipse.core.resources.IContainer;
4848
import org.eclipse.core.resources.IFile;
49+
import org.eclipse.core.resources.IFileState;
4950
import org.eclipse.core.resources.IFolder;
5051
import org.eclipse.core.resources.IProject;
5152
import org.eclipse.core.resources.IResource;
5253
import org.eclipse.core.resources.IResourceChangeEvent;
5354
import org.eclipse.core.resources.IResourceDelta;
5455
import org.eclipse.core.resources.IResourceStatus;
56+
import org.eclipse.core.resources.IWorkspaceDescription;
5557
import org.eclipse.core.runtime.CoreException;
5658
import org.eclipse.core.runtime.IPath;
5759
import org.eclipse.core.runtime.IProgressMonitor;
@@ -481,6 +483,37 @@ public void testCreateBytes() throws CoreException {
481483
assertEquals("notDerived", derived.readString());
482484
}
483485

486+
@Test
487+
public void testCreateOrUpdateDerived() throws CoreException {
488+
/* set local history policies */
489+
IWorkspaceDescription description = getWorkspace().getDescription();
490+
description.setMaxFileStates(4);
491+
getWorkspace().setDescription(description);
492+
493+
IFile derived = projects[0].getFile("derived.txt");
494+
createInWorkspace(projects[0]);
495+
removeFromWorkspace(derived);
496+
for (int i = 0; i < 16; i++) {
497+
boolean setDerived = i % 2 == 0;
498+
boolean deleteBefore = (i >> 1) % 2 == 0;
499+
boolean keepHistory = (i >> 2) % 2 == 0;
500+
if (deleteBefore) {
501+
derived.delete(false, null);
502+
}
503+
assertEquals(!deleteBefore, derived.exists());
504+
FussyProgressMonitor monitor = new FussyProgressMonitor();
505+
derived.createOrReplace(("updateOrCreate" + i).getBytes(), false, setDerived, keepHistory, monitor);
506+
monitor.assertUsedUp();
507+
assertEquals(setDerived, derived.isDerived());
508+
assertFalse(derived.isTeamPrivateMember());
509+
assertTrue(derived.exists());
510+
511+
IFileState[] history1 = derived.getHistory(null);
512+
derived.createOrReplace(("update" + i).getBytes(), false, false, keepHistory, null);
513+
IFileState[] history2 = derived.getHistory(null);
514+
assertEquals(keepHistory ? 1 : 0, history2.length - history1.length);
515+
}
516+
}
484517
@Test
485518
public void testDeltaOnCreateDerived() throws CoreException {
486519
IFile derived = projects[0].getFile("derived.txt");

0 commit comments

Comments
 (0)