private void scanFilesForChanges() {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("SHA-256");
        }
        catch (NoSuchAlgorithmException e) {
            // This should never happen, all JVM implementations must support SHA-256 according to Oracle docs.
            throw new RuntimeException(e);
        }
        ImmutableSet.Builder<File> modifiedFilesBuilder = ImmutableSet.builder();
        Map<File, FileMetadata> metadataCache = Maps.newHashMap(metadataCacheRef.get());
        for (File file : watchedFiles) {
            try {
                FileMetadata meta = new FileMetadata(file, digest);
                if (!meta.equals(metadataCache.get(file))) {
                    metadataCache.put(file, meta);
                    modifiedFilesBuilder.add(file);
                }
            }
            catch (IOException | SecurityException e) {
                // I/O error, file not found, or access to file not allowed
                log.warn(
                    "Error trying to stat or read file %s: %s: %s",
                    file.toString(),
                    e.getClass().getName(),
                    e.getMessage());
                metadataCache.remove(file);
            }
        }

        // We need to swallow exceptions from the user callback, otherwise an uncaught exception could kill the
        // poller thread.
        Set<File> modifiedFiles = modifiedFilesBuilder.build();
        boolean callbackCalled = false;
        boolean callbackSucceeded = false;
        try {
            if (!modifiedFiles.isEmpty()) {
                callbackCalled = true;
                callback.accept(modifiedFiles);
                callbackSucceeded = true;
            }
            // Only update the metadata cache if all callbacks succeeded.
            metadataCacheRef.set(ImmutableMap.copyOf(metadataCache));
        }
        catch (Exception e) {
            log.warn("Error from user callback: %s: %s", e.getClass().toString(), e.getMessage());
        }
        // Update stats
        if (!modifiedFiles.isEmpty()) {
            stats.fileChangesDetected.getAndAdd(modifiedFiles.size());
        }
        if (callbackCalled) {
            stats.callbacksInvoked.getAndIncrement();
            if (callbackSucceeded) {
                stats.callbacksSucceeded.getAndIncrement();
            }
            else {
                stats.callbacksFailed.getAndIncrement();
            }
        }
        stats.pollCycles.getAndIncrement();
    }
private void scanFilesForChanges ( ) { MessageDigest digest ; try { digest = MessageDigest . getInstance ( "SHA-256" ) ; } catch ( NoSuchAlgorithmException e ) { throw new RuntimeException ( e ) ; } ImmutableSet . Builder < File > modifiedFilesBuilder = ImmutableSet . builder ( ) ; Map < File , FileMetadata > metadataCache = Maps . newHashMap ( metadataCacheRef . get ( ) ) ; for ( File file : watchedFiles ) { try { FileMetadata meta = new FileMetadata ( file , digest ) ; if ( ! meta . equals ( metadataCache . get ( file ) ) ) { metadataCache . put ( file , meta ) ; modifiedFilesBuilder . add ( file ) ; } } catch ( IOException | SecurityException e ) { log . warn ( "Error trying to stat or read file %s: %s: %s" , file . toString ( ) , e . getClass ( ) . getName ( ) , e . getMessage ( ) ) ; metadataCache . remove ( file ) ; } } Set < File > modifiedFiles = modifiedFilesBuilder . build ( ) ; boolean callbackCalled = false ; boolean callbackSucceeded = false ; try { if ( ! modifiedFiles . isEmpty ( ) ) { callbackCalled = true ; callback . accept ( modifiedFiles ) ; callbackSucceeded = true ; } metadataCacheRef . set ( ImmutableMap . copyOf ( metadataCache ) ) ; } catch ( Exception e ) { log . warn ( "Error from user callback: %s: %s" , e . getClass ( ) . toString ( ) , e . getMessage ( ) ) ; } if ( ! modifiedFiles . isEmpty ( ) ) { stats . fileChangesDetected . getAndAdd ( modifiedFiles . size ( ) ) ; } if ( callbackCalled ) { stats . callbacksInvoked . getAndIncrement ( ) ; if ( callbackSucceeded ) { stats . callbacksSucceeded . getAndIncrement ( ) ; } else { stats . callbacksFailed . getAndIncrement ( ) ; } } stats . pollCycles . getAndIncrement ( ) ; }
Scans the watched files for changes. If any changes are detected, calls the user callback with the
set of all files that were modified since the last successful update attempt. Note that when a watched
file is deleted, it will not be considered modified.

Changes are tracked by watching certain file attributes (mtime, ctime, inode) and SHA-256 hashes of file
contents and comparing against last known values.

If a file is deleted or an I/O or permission error occurs while trying to stat or read it, the error is
ignored, but the file's metadata is removed from the metadata cache so next time it is read successfully it
will be considered an update.