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

com.google.gwt.dev.resource.impl.ChangedFileAccumulator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Google Inc.
 *
 * 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.
 */
package com.google.gwt.dev.resource.impl;

import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;

import java.io.File;
import java.io.IOException;
import java.nio.file.ClosedWatchServiceException;
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.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;

/**
 * Listens for and accumulates file changes in a recursive directory tree even when new files are
 * changed in new directories.
 * 

* Since it contains an internal thread it can't be garbage collected until it has been explicitly * shutdown. *

* Multiple instances can listen to the same directories at the same time. */ public class ChangedFileAccumulator { /** * A runnable that polls for watch events (in a blocking manner). File changes are recorded * and listeners are attached to new directories. */ private class WatchEventPoller implements Runnable { @Override public void run() { try { while (true) { WatchKey watchKey; try { watchKey = watchService.take(); } catch (InterruptedException e) { // Shutdown has been requested. return; } catch (ClosedWatchServiceException e) { // Shutdown has been requested. return; } synchronized (changedFiles) { Path containingDirectory = pathsByWatchKey.get(watchKey); for (WatchEvent watchEvent : watchKey.pollEvents()) { WatchEvent.Kind eventKind = watchEvent.kind(); if (eventKind == StandardWatchEventKinds.OVERFLOW) { setFailed( new RuntimeException("Changes occurred faster than they could be recorded.")); return; } if (!(watchEvent.context() instanceof Path)) { continue; } Path changedFileName = (Path) watchEvent.context(); Path changedPath = containingDirectory.resolve(changedFileName); // Maybe listen to newly created directories. if (eventKind == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(changedPath)) { // A new directory and some contained files can be created faster than watches can // be attached to new directories. So it is necessary to look for files in what are // believed to be "newly created" directories and consider those files changed. try { recursivelyRegisterListeners( changedPath, true /* considerPreexistingFilesChanged */); } catch (IOException e) { setFailed(e); return; } } // Record changed files. changedFiles.add(changedPath.toFile().getAbsoluteFile()); } // Ensures that future change events will be seen. if (!watchKey.reset()) { pathsByWatchKey.remove(watchKey); } } } } catch (RuntimeException e) { setFailed(e); } } } private final Set changedFiles = Collections.synchronizedSet(Sets. newHashSet()); private Thread changePollerThread; private Exception changePollerException; private final Map pathsByWatchKey = Maps.newHashMap(); private final WatchService watchService; public ChangedFileAccumulator(Path rootDirectory) throws IOException { watchService = FileSystems.getDefault().newWatchService(); recursivelyRegisterListeners(rootDirectory, false /* considerPreexistingFilesChanged */); startChangePoller(); } /** * Returns a sorted copy of the list of files that have changed since the last time changed files * were requested. */ public List getAndClearChangedFiles() throws ExecutionException { if (isFailed()) { throw new ExecutionException(changePollerException); } synchronized (changedFiles) { List sortedChangedFiles = Lists.newArrayList(changedFiles); changedFiles.clear(); Collections.sort(sortedChangedFiles); return sortedChangedFiles; } } public boolean isFailed() { return changePollerException != null; } public void shutdown() { changePollerThread.interrupt(); try { watchService.close(); } catch (IOException e) { changePollerException = e; // Am already trying to stop listening, there's nothing to be done about this failure. } changedFiles.clear(); pathsByWatchKey.clear(); } private void recursivelyRegisterListeners(final Path directory, final boolean considerPreexistingFilesChanged) throws IOException { Files.walkFileTree(directory, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path currentDirectory, BasicFileAttributes attrs) throws IOException { WatchKey watchKey = currentDirectory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); // If the recursive directory scan has gone in a loop because of symlinks. if (pathsByWatchKey.containsKey(watchKey)) { return FileVisitResult.SKIP_SUBTREE; } pathsByWatchKey.put(watchKey, currentDirectory); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (attrs.isSymbolicLink()) { recursivelyRegisterListeners(Files.readSymbolicLink(file), considerPreexistingFilesChanged); return FileVisitResult.CONTINUE; } if (considerPreexistingFilesChanged) { changedFiles.add(file.toFile()); } return FileVisitResult.CONTINUE; } }); } private void setFailed(Exception caughtException) { this.changePollerException = caughtException; shutdown(); } private void startChangePoller() { changePollerThread = new Thread(new WatchEventPoller()); // Don't prevent JVM shutdown. changePollerThread.setDaemon(true); changePollerThread.start(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy