@Override
 public void run() {
   for (Map.Entry<String, ConfigFileInfo> entry : watchedFileMap.entrySet()) {
     String filePath = entry.getKey();
     ConfigFileInfo configFileInfo = entry.getValue();
     try {
       File file = new File(filePath);
       long lastModified = file.lastModified();
       Preconditions.checkArgument(lastModified > 0L);
       if (lastModified != configFileInfo.lastModifiedTimestampMillis) {
         configFileInfo.lastModifiedTimestampMillis = lastModified;
         ByteSource byteSource = Files.asByteSource(file);
         HashCode newContentHash = byteSource.hash(HASH_FUNCTION);
         if (!newContentHash.equals(configFileInfo.contentHash)) {
           configFileInfo.contentHash = newContentHash;
           LOG.info("File {} was modified at {}, notifying watchers.", filePath, lastModified);
           byte[] newContents = byteSource.read();
           for (Function<byte[], Void> watchers : configFileInfo.changeWatchers) {
             try {
               watchers.apply(newContents);
             } catch (Exception e) {
               LOG.error(
                   "Exception in watcher callback for {}, ignoring. New file contents were: {}",
                   filePath,
                   new String(newContents, Charsets.UTF_8),
                   e);
             }
           }
         } else {
           LOG.info(
               "File {} was modified at {} but content hash is unchanged.",
               filePath,
               lastModified);
         }
       } else {
         LOG.debug("File {} not modified since {}", filePath, lastModified);
       }
     } catch (Exception e) {
       // We catch and log exceptions related to the update of any specific file, but
       // move on so others aren't affected. Issues can happen for example if the watcher
       // races with an external file replace operation; in that case, the next run should
       // pick up the update.
       // TODO: Consider adding a metric to track this so we can alert on failures.
       LOG.error("Config update check failed for {}", filePath, e);
     }
   }
 }
  /**
   * Adds a watch on the specified file. The file must exist, otherwise a FileNotFoundException is
   * returned. If the file is deleted after a watch is established, the watcher will log errors but
   * continue to monitor it, and resume watching if it is recreated.
   *
   * @param filePath path to the file to watch.
   * @param onUpdate function to call when a change is detected to the file. The entire contents of
   *     the file will be passed in to the function. Note that onUpdate will be called once before
   *     this call completes, which facilities initial load of data. This callback is executed
   *     synchronously on the watcher thread - it is important that the function be non-blocking.
   */
  public synchronized void addWatch(String filePath, Function<byte[], Void> onUpdate)
      throws IOException {
    MorePreconditions.checkNotBlank(filePath);
    Preconditions.checkNotNull(onUpdate);

    // Read the file and make the initial onUpdate call.
    File file = new File(filePath);
    ByteSource byteSource = Files.asByteSource(file);
    onUpdate.apply(byteSource.read());

    // Add the file to our map if it isn't already there, and register the new change watcher.
    ConfigFileInfo configFileInfo = watchedFileMap.get(filePath);
    if (configFileInfo == null) {
      configFileInfo = new ConfigFileInfo(file.lastModified(), byteSource.hash(HASH_FUNCTION));
      watchedFileMap.put(filePath, configFileInfo);
    }
    configFileInfo.changeWatchers.add(onUpdate);
  }