All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.swoval.files.apple.FileEventMonitors Maven / Gradle / Ivy

There is a newer version: 2.1.12
Show newest version
package com.swoval.files.apple;

import com.swoval.concurrent.ThreadFactory;
import com.swoval.files.apple.FileEventMonitors.Handle;
import com.swoval.files.apple.FileEventMonitors.Handles;
import com.swoval.files.apple.Flags.Create;
import com.swoval.functional.Consumer;
import com.swoval.runtime.NativeLoader;
import com.swoval.runtime.ShutdownHooks;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class FileEventMonitors {
  private FileEventMonitors() {}

  /**
   * A handle for a file event stream that can be cancelled using {@link
   * FileEventMonitor#stopStream}.
   */
  public interface Handle {}

  /**
   * Provides an invalid handle that indicates that either registration of a stream was unsucessful
   * or the stream has been made redundant by a stream for a different path that covers the path for
   * which the stream was created.
   */
  public static class Handles {
    private Handles() {}

    public static final Handle INVALID =
        new Handle() {
          @Override
          public String toString() {
            return "INVALID";
          }
        };
  }

  public static FileEventMonitor get(
      final Consumer eventConsumer, final Consumer streamConsumer)
      throws InterruptedException {
    return new FileEventMonitorImpl(eventConsumer, streamConsumer);
  }
}

class FileEventMonitorImpl implements FileEventMonitor {
  private long handle = -1;
  private final Thread loopThread;
  private final ExecutorService callbackExecutor =
      Executors.newSingleThreadExecutor(
          new ThreadFactory("com.swoval.files.apple.FileEventsMonitor.callback"));
  private final AtomicBoolean closed = new AtomicBoolean(false);
  private final int shutdownHookId;
  private final Runnable closeRunnable =
      new Runnable() {
        @Override
        public void run() {
          if (closed.compareAndSet(false, true)) {
            ShutdownHooks.removeHook(shutdownHookId);
            stopLoop(handle);
            loopThread.interrupt();
            callbackExecutor.shutdownNow();
            try {
              loopThread.join(5000);
              callbackExecutor.awaitTermination(5, TimeUnit.SECONDS);
              close(handle);
            } catch (final InterruptedException e) {
              e.printStackTrace(System.err);
            }
          }
        }
      };

  FileEventMonitorImpl(
      final Consumer eventConsumer, final Consumer streamConsumer)
      throws InterruptedException {
    final CountDownLatch initLatch = new CountDownLatch(1);
    final Consumer wrappedEventConsumer = new WrappedConsumer<>(eventConsumer);
    final Consumer wrappedStreamConsumer = new WrappedConsumer<>(streamConsumer);
    loopThread =
        new Thread("com.swoval.files.apple.FileEventsMonitor.runloop") {
          @Override
          public void run() {
            handle = init(wrappedEventConsumer, wrappedStreamConsumer);
            initLatch.countDown();
            loop(handle);
          }
        };
    loopThread.start();
    initLatch.await(5, TimeUnit.SECONDS);
    assert (handle != -1);
    shutdownHookId = ShutdownHooks.addHook(1, closeRunnable);
  }

  private class NativeHandle implements Handle {
    private final int handle;

    NativeHandle(final int handle) {
      this.handle = handle;
    }

    @Override
    public String toString() {
      return "NativeHandle(" + handle + ")";
    }
  }

  private static native void loop(long handle);

  private static native void close(long handle);

  private static native long init(Consumer consumer, Consumer pathConsumer);

  private static native int createStream(String path, double latency, int flags, long handle);

  private static native void stopLoop(long handle);

  private static native void stopStream(long handle, int streamHandle);

  static {
    try {
      NativeLoader.loadPackaged();
    } catch (IOException | UnsatisfiedLinkError e) {
      System.err.println("Couldn't load native library " + e);
      throw new ExceptionInInitializerError(e);
    }
  }

  @Override
  public Handle createStream(final Path path, final long latency, TimeUnit timeUnit, Create flags)
      throws ClosedFileEventMonitorException {
    if (!closed.get()) {
      final int res =
          createStream(
              path.toString(), timeUnit.toNanos(latency) / 1.0e9, flags.getValue(), handle);
      return res == -1 ? Handles.INVALID : new NativeHandle(res);
    } else {
      throw new ClosedFileEventMonitorException();
    }
  }

  @Override
  public void stopStream(final Handle streamHandle) throws ClosedFileEventMonitorException {
    if (!closed.get()) {
      assert (streamHandle instanceof NativeHandle);
      stopStream(handle, ((NativeHandle) streamHandle).handle);
    } else {
      throw new ClosedFileEventMonitorException();
    }
  }

  @Override
  public void close() {
    closeRunnable.run();
  }

  private class WrappedConsumer implements Consumer {
    private final Consumer consumer;

    WrappedConsumer(final Consumer consumer) {
      this.consumer = consumer;
    }

    @Override
    public void accept(final T t) {
      if (!closed.get()) {
        callbackExecutor.submit(
            new Runnable() {
              @Override
              public void run() {
                try {
                  if (!closed.get()) {
                    consumer.accept(t);
                  }
                } catch (final Exception e) {
                  e.printStackTrace(System.err);
                }
              }
            });
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy