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

org.grails.io.watch.WatchServiceDirectoryWatcher Maven / Gradle / Ivy

package org.grails.io.watch;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of a {@link AbstractDirectoryWatcher} that uses {@link java.nio.WatchService}.
 * This implementation is used for Java 7 and later.
 * @author Craig Andrews
 * @since 2.4
 * @see WatchServiceDirectoryWatcher
 * @see DirectoryWatcher
 */
class WatchServiceDirectoryWatcher extends AbstractDirectoryWatcher {

	private static final Logger LOG = LoggerFactory.getLogger(WatchServiceDirectoryWatcher.class);
    private Map> watchKeyToExtensionsMap = new ConcurrentHashMap>();
    private Set individualWatchedFiles = new HashSet();

	private final WatchService watchService;

    @SuppressWarnings("unchecked")
    private static  WatchEvent cast(WatchEvent event) {
        return (WatchEvent)event;
    }

	public WatchServiceDirectoryWatcher(){
		try {
			watchService = FileSystems.getDefault().newWatchService();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void run() {
        while (active) {
			try {
				WatchKey watchKey = watchService.poll(sleepTime, TimeUnit.MILLISECONDS);
				if(watchKey!=null){
					List> watchEvents = watchKey.pollEvents();
					for(WatchEvent watchEvent : watchEvents){
						WatchEvent.Kind kind = watchEvent.kind();
		                if (kind == StandardWatchEventKinds.OVERFLOW) {
		                	// TODO how is this supposed to be handled? I think the best thing to do is ignore it, but I'm not positive
		                	LOG.warn("WatchService Overflow occurred");
		                    continue;
		                }
						WatchEvent pathWatchEvent = cast(watchEvent);
						Path name = pathWatchEvent.context();
						Path dir = (Path) watchKey.watchable();
						Path child = dir.resolve(name).toAbsolutePath();
						File childFile = child.toFile();
						if(individualWatchedFiles.contains(child) || individualWatchedFiles.contains(child.normalize())){
							if(kind == StandardWatchEventKinds.ENTRY_CREATE){
								fireOnNew(childFile);
							}else if(kind == StandardWatchEventKinds.ENTRY_MODIFY){
								fireOnChange(childFile);
							}else if(kind == StandardWatchEventKinds.ENTRY_DELETE){
								// do nothing... there's no way to communicate deletions
							}
						}else{
							List fileExtensions = watchKeyToExtensionsMap.get(watchKey);
							if(fileExtensions==null){
								// this event didn't match a file in individualWatchedFiles so it's a not an individual file we're interested in
								// this event also didn't match a directory that we're interested in (if it did, fileExtentions wouldn't be null)
								// so it must be event for a file we're not interested in. An example of how this can happen is:
								// there's a directory with files in it like this:
								// /images/a.png
								// /images/b.png
								// by using the addWatchFile method, /images/a.png is watched.
								// Now, /images/b.png is changed. Because java.nio.file.WatchService watches directories, it gets a WatchEvent
								// for /images/b.png. But we aren't interested in that.
								LOG.debug("WatchService received an event for a file/directory that it's not interested in.");
							}else{
								if(kind==StandardWatchEventKinds.ENTRY_CREATE){
									// new directory created, so watch its contents
									addWatchDirectory(child,fileExtensions);
									if(childFile.isDirectory() && childFile.exists()) {
										final File[] files = childFile.listFiles();
										if(files != null) {
											for (File newFile : files) {
												if(isValidFileToMonitor(newFile, fileExtensions)) {
													fireOnNew(newFile);
												}
											}
										}
									}
								}
								if(isValidFileToMonitor(childFile,fileExtensions)){
									if(kind == StandardWatchEventKinds.ENTRY_CREATE){
										fireOnNew(childFile);
									}else if(kind == StandardWatchEventKinds.ENTRY_MODIFY){
										fireOnChange(childFile);
									}else if(kind == StandardWatchEventKinds.ENTRY_DELETE){
										// do nothing... there's no way to communicate deletions
									}
								}
							}
						}
					}
					watchKey.reset();
				}
			} catch (InterruptedException e) {
				// ignore
			}
        }
        try {
			watchService.close();
		} catch (IOException e) {
			LOG.debug("Exception while closing watchService", e);
		}
	}

	@Override
	public void addWatchFile(File fileToWatch) {
		if(!isValidFileToMonitor(fileToWatch, Arrays.asList("*"))) return;
		try {
            if(!fileToWatch.exists()) return;
			Path pathToWatch = fileToWatch.toPath().toAbsolutePath();
			individualWatchedFiles.add(pathToWatch);
			pathToWatch.getParent().register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void addWatchDirectory(File dir, final List fileExtensions) {
		Path dirPath = dir.toPath();
		addWatchDirectory(dirPath, fileExtensions);
	}

	private void addWatchDirectory(Path dir, final List fileExtensions) {
		if(!isValidDirectoryToMonitor(dir.toFile())){
			return;
		}
		try {
			//add the subdirectories too
			Files.walkFileTree(dir, new SimpleFileVisitor() {
	            @Override
	            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
	                throws IOException
	            {
	            	if(!isValidDirectoryToMonitor(dir.toFile())){
	            		return FileVisitResult.SKIP_SUBTREE;
	            	}
	    			WatchKey watchKey = dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
	    			final List originalFileExtensions = watchKeyToExtensionsMap.get(watchKey);
	    			if(originalFileExtensions==null){
	    				watchKeyToExtensionsMap.put(watchKey, fileExtensions);
	    			}else{
	    				final HashSet newFileExtensions = new HashSet(originalFileExtensions);
	    				newFileExtensions.addAll(fileExtensions);
	    				watchKeyToExtensionsMap.put(watchKey, Collections.unmodifiableList(new ArrayList(newFileExtensions)));
	    			}
	                return FileVisitResult.CONTINUE;
	            }
	        });
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy