Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.16.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.16.0</version>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* BSD 2-Clause License
*
* Copyright (c) 2023, Swat.engineering
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package engineering.swat.watch.impl.mac;

import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;

import org.checkerframework.checker.nullness.qual.Nullable;

/**
* <p>
* Handler for native events, intended to be used in a {@link NativeEventStream}
* callback to construct {@link WatchEvent}s (and propagate them for downstream
* consumption).
* </p>
*
* <p>
* In each invocation, the types of {@code kind} and {@code context} depend
* specifically on the given native event: they're {@code Kind<Path>} and
* {@code Path} for non-overflows, but they're {@code Kind<Object>} and
* {@code Object} for overflows. This precision is needed to construct
* {@link WatchEvent}s, where the types of {@code kind} and {@code context} need
* to be correlated. Note: {@link java.util.function.BiConsumer} doesn't give
* the required precision (i.e., its type parameters are initialized only once
* for all invocations).
* </p>
*/
@FunctionalInterface
public interface NativeEventHandler {
<T> void handle(Kind<T> kind, @Nullable T context);
}
286 changes: 286 additions & 0 deletions src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
/*
* BSD 2-Clause License
*
* Copyright (c) 2023, Swat.engineering
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package engineering.swat.watch.impl.mac;

import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.FILE_EVENTS;
import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.NO_DEFER;
import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.WATCH_ROOT;
import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_CREATED;
import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_INODE_META_MOD;
import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_MODIFIED;
import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_REMOVED;
import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.MUST_SCAN_SUB_DIRS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Path;

import org.checkerframework.checker.nullness.qual.Nullable;

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.mac.CoreFoundation;
import com.sun.jna.platform.mac.CoreFoundation.CFArrayRef;
import com.sun.jna.platform.mac.CoreFoundation.CFIndex;
import com.sun.jna.platform.mac.CoreFoundation.CFStringRef;

import engineering.swat.watch.impl.mac.apis.DispatchObjects;
import engineering.swat.watch.impl.mac.apis.DispatchQueue;
import engineering.swat.watch.impl.mac.apis.FileSystemEvents;
import engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCallback;

// Note: This file is designed to be the only place in this package where JNA is
// used and/or the native APIs are invoked. If the need to do so arises outside
// this file, consider extending this file to offer the required services
// without exposing JNA and/or the native APIs.

/**
* <p>
* Stream of native events for a path, issued by macOS. It's a facade-like
* object that hides the low-level native APIs behind a higher-level interface.
* </p>
*
* <p>
* Note: Methods {@link #open()} and {@link #close()} synchronize on this object
* to avoid races. The synchronization overhead is expected to be negligible, as
* these methods are expected to be rarely invoked.
* </p>
*/
public class NativeEventStream implements Closeable {

// Native APIs
private static final CoreFoundation CF = CoreFoundation.INSTANCE;
private static final DispatchObjects DO = DispatchObjects.INSTANCE;
private static final DispatchQueue DQ = DispatchQueue.INSTANCE;
private static final FileSystemEvents FSE = FileSystemEvents.INSTANCE;

Check warning on line 84 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L81-L84

Added lines #L81 - L84 were not covered by tests

// Native memory (automatically deallocated when set to `null`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who takes care of that deallocation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is a bit confusing. The main point it was supposed to convey is that the callback field is needed to keep a live reference to the callback (otherwise, it's prematurely GC'ed). Comment rephrased.

(To also answer the question: stream and queue are deallocated explicitly in method close() through invocations of FSE.FSEventStreamRelease and DO.dispatch_release. See also another comment below.)

private @Nullable FSEventStreamCallback callback;
private @Nullable Pointer stream;
private @Nullable Pointer queue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they should be volatile, as their value will get ready by multiple threads, and you don't want thread-local caches. Since they could point towards bad pointers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reads/writes to these fields are inside synchronized blocks. Comment added to clarify.


private final Path path;
private final NativeEventHandler handler;
private volatile boolean closed;

public NativeEventStream(Path path, NativeEventHandler handler) throws IOException {
this.path = path.toRealPath(); // Resolve symbolic links
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we also store the original requested path? So that our events generated are in the context of the original path, and not the resolved path?

or otherwise, we should make it an argument requirement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All paths returned to consumers of the stream are relative to the root of the watch scope (so they can be used as contexts in WatchEvents). However, macOS always seems to be using real paths in events, so to be able to relativize, the real path of the root is needed.

For instance, the original path could be this (i.e., new TestDirectory().getTestDirectory()):

/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313

But, the paths in the incoming events are these:

/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313/a.txt
/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313/b.txt
/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313/d.txt

So, we cannot relativize to the original path. Instead, we need to compute path.toRealPath():

/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313

Then, we can relativize to path.toRealPath():

a.txt
b.txt
d.txt

this.handler = handler;
this.closed = true;
}

Check warning on line 99 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L95-L99

Added lines #L95 - L99 were not covered by tests

public synchronized void open() {
if (!closed) {
throw new IllegalStateException("Stream is already open");

Check warning on line 103 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L103

Added line #L103 was not covered by tests
} else {
closed = false;

Check warning on line 105 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L105

Added line #L105 was not covered by tests
}

// Allocate native memory. (Checker Framework: The local variables are
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these CF comments needed? CF should understand assignment like this, it knows that createCallback does not return a nullable result?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CF is a bit erratic about what it does, and doesn't, understand. (E.g., changing the order of the assignment to stream and queue confuses CF.) Anyway, I rewrote it a bit and removed the comment.

// `@NonNull` copies of the `@Nullable` fields.)
var callback = this.callback = createCallback();
var stream = this.stream = createFSEventStream(callback);
var queue = this.queue = createDispatchQueue();

Check warning on line 112 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L110-L112

Added lines #L110 - L112 were not covered by tests

// Start the stream
FSE.FSEventStreamSetDispatchQueue(stream, queue);
FSE.FSEventStreamStart(stream);
}

Check warning on line 117 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L115-L117

Added lines #L115 - L117 were not covered by tests

private FSEventStreamCallback createCallback() {
return new FSEventStreamCallback() {

Check warning on line 120 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L120

Added line #L120 was not covered by tests
@Override
public void callback(Pointer streamRef, Pointer clientCallBackInfo,
long numEvents, Pointer eventPaths, Pointer eventFlags, Pointer eventIds) {
// This function is called each time native events are issued by
// macOS. The purpose of this function is to perform the minimal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but it reads like we should make sure that this function does not consume a lot of time? As it's a native callback?

If that is true, I think we should publish the event to a ConcurrentQueue, as we don't know how much work happens inside of the handler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative solution as discussed: make the class package private.

// amount of processing to hide the native APIs from downstream
// consumers, who are offered native events via `handler`.

var paths = eventPaths.getStringArray(0, (int) numEvents);
var flags = eventFlags.getIntArray(0, (int) numEvents);

Check warning on line 130 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L129-L130

Added lines #L129 - L130 were not covered by tests

for (var i = 0; i < numEvents; i++) {
var context = path.relativize(Path.of(paths[i]));

Check warning on line 133 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L133

Added line #L133 was not covered by tests

// Note: Multiple "physical" native events might be
// coalesced into a single "logical" native event, so the
// following series of checks should be if-statements
// (instead of if/else-statements).
if (any(flags[i], ITEM_CREATED.mask)) {
handler.handle(ENTRY_CREATE, context);

Check warning on line 140 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L140

Added line #L140 was not covered by tests
}
if (any(flags[i], ITEM_REMOVED.mask)) {
handler.handle(ENTRY_DELETE, context);

Check warning on line 143 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L143

Added line #L143 was not covered by tests
}
if (any(flags[i], ITEM_MODIFIED.mask | ITEM_INODE_META_MOD.mask)) {
handler.handle(ENTRY_MODIFY, context);

Check warning on line 146 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L146

Added line #L146 was not covered by tests
}
if (any(flags[i], MUST_SCAN_SUB_DIRS.mask)) {
handler.handle(OVERFLOW, null);

Check warning on line 149 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L149

Added line #L149 was not covered by tests
}
}
}

Check warning on line 152 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L152

Added line #L152 was not covered by tests

private boolean any(int bits, int mask) {
return (bits & mask) != 0;
}
};
}

private Pointer createFSEventStream(FSEventStreamCallback callback) {
try (
var pathsToWatch = new Strings(path.toString());

Check warning on line 162 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L162

Added line #L162 was not covered by tests
) {
var allocator = CF.CFAllocatorGetDefault();
var context = Pointer.NULL;
var sinceWhen = FSE.FSEventsGetCurrentEventId();
var latency = 0.15;
var flags = NO_DEFER.mask | WATCH_ROOT.mask | FILE_EVENTS.mask;
return FSE.FSEventStreamCreate(allocator, callback, context, pathsToWatch.toCFArray(), sinceWhen, latency, flags);

Check warning on line 169 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L164-L169

Added lines #L164 - L169 were not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead of all the local variables, it can also be written as:

return FSE.FSEventStreamCreate(
     CF.CFAllocatorGetDefault(),
     Pointer.NULL,
....

}
}

private Pointer createDispatchQueue() {
var label = "engineering.swat.watch";
var attr = Pointer.NULL;
return DQ.dispatch_queue_create(label, attr);

Check warning on line 176 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L174-L176

Added lines #L174 - L176 were not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: inline the parameters

}

// -- Closeable --

@Override
public synchronized void close() {
if (closed) {
throw new IllegalStateException("Stream is already closed");

Check warning on line 184 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L184

Added line #L184 was not covered by tests
} else {
closed = true;

Check warning on line 186 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L186

Added line #L186 was not covered by tests
}

// Stop the stream
if (stream != null) {
var streamNonNull = stream; // Checker Framework: `@NonNull` copy of `@Nullable` field
FSE.FSEventStreamStop(streamNonNull);
FSE.FSEventStreamSetDispatchQueue(streamNonNull, Pointer.NULL);
FSE.FSEventStreamInvalidate(streamNonNull);
FSE.FSEventStreamRelease(streamNonNull);

Check warning on line 195 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L191-L195

Added lines #L191 - L195 were not covered by tests
}
if (queue != null) {
DO.dispatch_release(queue);

Check warning on line 198 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L198

Added line #L198 was not covered by tests
}

// Deallocate native memory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't this leave it to the GC/destroy to deallocate? isn't there an explicit function in the JNA framework we can call to cleanup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment was misplaced. The actual deallocation of the stream and the queue happens in FSE.FSEventStreamRelease and DO.dispatch_release on the lines above; the deallocation of the callback does happen by setting it to null, though (so it can then be GC'ed). Moved comment.

callback = null;
stream = null;
queue = null;
}

Check warning on line 205 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L202-L205

Added lines #L202 - L205 were not covered by tests
}

/**
* Array of strings in native memory, needed to create a new native event stream
* (i.e., the {@code pathsToWatch} argument of {@code FSEventStreamCreate} is an
* array of strings).
*/
class Strings implements AutoCloseable {

// Native APIs
private static final CoreFoundation CF = CoreFoundation.INSTANCE;

Check warning on line 216 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L216

Added line #L216 was not covered by tests

// Native memory
private final CFStringRef[] strings;
private final CFArrayRef array;

private volatile boolean closed = false;

Check warning on line 222 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L222

Added line #L222 was not covered by tests

public Strings(String... strings) {

Check warning on line 224 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L224

Added line #L224 was not covered by tests
// Allocate native memory
this.strings = createCFStrings(strings);
this.array = createCFArray(this.strings);
}

Check warning on line 228 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L226-L228

Added lines #L226 - L228 were not covered by tests

public CFArrayRef toCFArray() {
if (closed) {
throw new IllegalStateException("Strings are already deallocated");

Check warning on line 232 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L232

Added line #L232 was not covered by tests
} else {
return array;

Check warning on line 234 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L234

Added line #L234 was not covered by tests
}
}

private static CFStringRef[] createCFStrings(String[] pathsToWatch) {
var n = pathsToWatch.length;

Check warning on line 239 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L239

Added line #L239 was not covered by tests

var strings = new CFStringRef[n];

Check warning on line 241 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L241

Added line #L241 was not covered by tests
for (int i = 0; i < n; i++) {
strings[i] = CFStringRef.createCFString(pathsToWatch[i]);

Check warning on line 243 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L243

Added line #L243 was not covered by tests
}
return strings;

Check warning on line 245 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L245

Added line #L245 was not covered by tests
}

private static CFArrayRef createCFArray(CFStringRef[] strings) {
var n = strings.length;
var size = Native.getNativeSize(CFStringRef.class);

Check warning on line 250 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L249-L250

Added lines #L249 - L250 were not covered by tests

// Create a temporary array of pointers to the strings (automatically
// freed when `values` goes out of scope)
var values = new Memory(n * size);

Check warning on line 254 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L254

Added line #L254 was not covered by tests
for (int i = 0; i < n; i++) {
values.setPointer(i * size, strings[i].getPointer());

Check warning on line 256 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L256

Added line #L256 was not covered by tests
}

// Create a permanent array based on the temporary array
var alloc = CF.CFAllocatorGetDefault();
var numValues = new CFIndex(n);
var callBacks = Pointer.NULL;
return CF.CFArrayCreate(alloc, values, numValues, callBacks);

Check warning on line 263 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L260-L263

Added lines #L260 - L263 were not covered by tests
}

// -- AutoCloseable --

@Override
public void close() {
if (closed) {
throw new IllegalStateException("Strings are already deallocated");

Check warning on line 271 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L271

Added line #L271 was not covered by tests
} else {
closed = true;

Check warning on line 273 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L273

Added line #L273 was not covered by tests
}

// Deallocate native memory
for (var s : strings) {
if (s != null) {
s.release();

Check warning on line 279 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L279

Added line #L279 was not covered by tests
}
}
if (array != null) {
array.release();

Check warning on line 283 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L283

Added line #L283 was not covered by tests
}
}

Check warning on line 285 in src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java#L285

Added line #L285 was not covered by tests
}
Loading