text: // Copyright 2016 The Bazel Authors. All rights reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  // http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  #include <CoreServices/CoreServices.h>
  #include <jni.h>
  #include <pthread.h>
  #include <stdlib.h>
  
  #include <list>
  #include <string>
  
  namespace {
  
  // A structure to pass around the FSEvents info and the list of paths.
  struct JNIEventsDiffAwareness {
    // FSEvents run loop (thread)
    CFRunLoopRef runLoop;
  
    // FSEvents stream reference (reference to the listened stream)
    FSEventStreamRef stream;
  
    // If true, fsevents dropped events so we don't know what changed exactly.
    bool everything_changed;
  
    // List of paths that have been changed since last polling.
    std::list<std::string> paths;
  
    // Mutex to protect concurrent accesses to paths and everything_changed.
    pthread_mutex_t mutex;
  
    JNIEventsDiffAwareness() : everything_changed(false) {
      pthread_mutex_init(&mutex, nullptr);
    }
  
    ~JNIEventsDiffAwareness() { pthread_mutex_destroy(&mutex); }
  };
  
  // Callback called when an event is reported by the FSEvents API
  void FsEventsDiffAwarenessCallback(ConstFSEventStreamRef streamRef,
                                     void *clientCallBackInfo, size_t numEvents,
                                     void *eventPaths,
                                     const FSEventStreamEventFlags eventFlags[],
                                     const FSEventStreamEventId eventIds[]) {
    char **paths = static_cast<char **>(eventPaths);
  
    JNIEventsDiffAwareness *info =
        static_cast<JNIEventsDiffAwareness *>(clientCallBackInfo);
    pthread_mutex_lock(&(info->mutex));
    for (int i = 0; i < numEvents; i++) {
      if ((eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) != 0) {
        // Either we lost events or they were coalesced. Assume everything changed
        // and give up, which matches the fsevents documentation in that the
        // caller is expected to rescan the directory contents on its own.
        info->everything_changed = true;
        break;
      } else if ((eventFlags[i] & kFSEventStreamEventFlagItemIsDir) != 0 &&
          (eventFlags[i] & kFSEventStreamEventFlagItemRenamed) != 0) {
        // A directory was renamed. When this happens, fsevents may or may not
        // give us individual events about which files changed underneath, which
        // means we have to rescan the directories in order to know what changed.
        //
        // The problem is that we cannot rescan the source of the move to discover
        // which files "disappeared"... so we have no choice but to rescan
        // everything. Well, in theory, we could try to track directory inodes and
        // using those to guess which files within them moved... but that'd be way
        // too much complexity for this rather-uncommon use case.
        info->everything_changed = true;
        break;
      } else {
        info->paths.push_back(std::string(paths[i]));
      }
    }
    pthread_mutex_unlock(&(info->mutex));
  }
  
  extern "C" JNIEXPORT void JNICALL
  Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_create(
      JNIEnv *env, jobject fsEventsDiffAwareness, jobjectArray paths,
      jdouble latency) {
    // Create a FSEventStreamContext to pass around (env, fsEventsDiffAwareness)
    JNIEventsDiffAwareness *info = new JNIEventsDiffAwareness();
  
    FSEventStreamContext context;
    context.version = 0;
    context.info = static_cast<void *>(info);
    context.retain = NULL;
    context.release = NULL;
    context.copyDescription = NULL;
  
    // Create an CFArrayRef of CFStringRef from the Java array of String
    jsize length = env->GetArrayLength(paths);
    CFStringRef *pathsArray = new CFStringRef[length];
    for (int i = 0; i < length; i++) {
      jstring path = (jstring)env->GetObjectArrayElement(paths, i);
      const char *pathCStr = env->GetStringUTFChars(path, NULL);
      pathsArray[i] =
          CFStringCreateWithCString(NULL, pathCStr, kCFStringEncodingUTF8);
      env->ReleaseStringUTFChars(path, pathCStr);
    }
    CFArrayRef pathsToWatch =
        CFArrayCreate(NULL, (const void **)pathsArray, 1, NULL);
    delete[] pathsArray;
    info->stream = FSEventStreamCreate(
        NULL, &FsEventsDiffAwarenessCallback, &context, pathsToWatch,
        kFSEventStreamEventIdSinceNow, static_cast<CFAbsoluteTime>(latency),
        kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents);
  
    // Save the info pointer to FSEventsDiffAwareness#nativePointer
    jbyteArray array = env->NewByteArray(sizeof(info));
    env->SetByteArrayRegion(array, 0, sizeof(info),
                            reinterpret_cast<const jbyte *>(&info));
    jclass clazz = env->GetObjectClass(fsEventsDiffAwareness);
    jfieldID fid = env->GetFieldID(clazz, "nativePointer", "J");
    env->SetLongField(fsEventsDiffAwareness, fid, reinterpret_cast<jlong>(info));
  }
  
  JNIEventsDiffAwareness *GetInfo(JNIEnv *env, jobject fsEventsDiffAwareness) {
    jclass clazz = env->GetObjectClass(fsEventsDiffAwareness);
    jfieldID fid = env->GetFieldID(clazz, "nativePointer", "J");
    jlong field = env->GetLongField(fsEventsDiffAwareness, fid);
    return reinterpret_cast<JNIEventsDiffAwareness *>(field);
  }
  
  }  // namespace
  
  extern "C" JNIEXPORT void JNICALL
  Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_run(
      JNIEnv *env, jobject fsEventsDiffAwareness, jobject listening) {
    JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness);
    info->runLoop = CFRunLoopGetCurrent();
    FSEventStreamScheduleWithRunLoop(info->stream, info->runLoop,
                                     kCFRunLoopDefaultMode);
    FSEventStreamStart(info->stream);
  
    jclass countDownLatchClass = env->GetObjectClass(listening);
    jmethodID countDownMethod =
        env->GetMethodID(countDownLatchClass, "countDown", "()V");
    env->CallVoidMethod(listening, countDownMethod);
    CFRunLoopRun();
  }
  
  extern "C" JNIEXPORT jobjectArray JNICALL
  Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_poll(
      JNIEnv *env, jobject fsEventsDiffAwareness) {
    JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness);
    pthread_mutex_lock(&(info->mutex));
  
    jobjectArray result;
    if (info->everything_changed) {
      result = NULL;
    } else {
      jclass classString = env->FindClass("java/lang/String");
      result = env->NewObjectArray(info->paths.size(), classString, NULL);
      int i = 0;
      for (auto it = info->paths.begin(); it != info->paths.end(); it++, i++) {
        env->SetObjectArrayElement(result, i, env->NewStringUTF(it->c_str()));
      }
    }
  
    info->everything_changed = false;
    info->paths.clear();
  
    pthread_mutex_unlock(&(info->mutex));
    return result;
  }
  
  extern "C" JNIEXPORT void JNICALL
  Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_doClose(
      JNIEnv *env, jobject fsEventsDiffAwareness) {
    JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness);
    CFRunLoopStop(info->runLoop);
    FSEventStreamStop(info->stream);
    FSEventStreamUnscheduleFromRunLoop(info->stream, info->runLoop,
                                       kCFRunLoopDefaultMode);
    FSEventStreamInvalidate(info->stream);
    FSEventStreamRelease(info->stream);
    delete info;
  }
  