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

org.robolectric.shadows.ShadowFileObserver Maven / Gradle / Ivy

package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.Q;

import android.os.FileObserver;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.concurrent.GuardedBy;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;

/**
 * A shadow implementation of FileObserver that uses java.nio.file.WatchService.
 *
 * 

Currently only supports MODIFY, DELETE and CREATE (CREATE will encompass also events that * would normally register as MOVED_FROM, and DELETE will encompass also events that would normally * register as MOVED_TO). Other event types will be silently ignored. */ @Implements(FileObserver.class) public class ShadowFileObserver { @RealObject private FileObserver realFileObserver; private final WatchService watchService; private final Map watchedDirectories = new HashMap<>(); private final Map watchedKeys = new HashMap<>(); private WatchEvent.Kind[] watchEvents = new WatchEvent.Kind[0]; @GuardedBy("this") private WatcherRunnable watcherRunnable = null; public ShadowFileObserver() { try { this.watchService = FileSystems.getDefault().newWatchService(); } catch (IOException ioException) { throw new RuntimeException(ioException); } } @Override @Implementation protected void finalize() throws Throwable { stopWatching(); } private void setMask(int mask) { Set> watchEventsSet = new HashSet<>(); if ((mask & FileObserver.MODIFY) != 0) { watchEventsSet.add(StandardWatchEventKinds.ENTRY_MODIFY); } if ((mask & FileObserver.DELETE) != 0) { watchEventsSet.add(StandardWatchEventKinds.ENTRY_DELETE); } if ((mask & FileObserver.CREATE) != 0) { watchEventsSet.add(StandardWatchEventKinds.ENTRY_CREATE); } watchEvents = watchEventsSet.toArray(new WatchEvent.Kind[0]); } private void addFile(File file) { List list = new ArrayList<>(1); list.add(file); addFiles(list); } private void addFiles(List files) { // Break all watched files into their directories. for (File file : files) { Path path = file.toPath(); if (Files.isDirectory(path)) { WatchedDirectory watchedDirectory = new WatchedDirectory(path); watchedDirectories.put(path.toString(), watchedDirectory); } else { Path directory = path.getParent(); String filename = path.getFileName().toString(); WatchedDirectory watchedDirectory = watchedDirectories.get(directory.toString()); if (watchedDirectory == null) { watchedDirectory = new WatchedDirectory(directory); } watchedDirectory.addFile(filename); watchedDirectories.put(directory.toString(), watchedDirectory); } } } @Implementation protected void __constructor__(String path, int mask) { setMask(mask); addFile(new File(path)); } @Implementation(minSdk = Q) protected void __constructor__(List files, int mask) { setMask(mask); addFiles(files); } /** * Represents a directory to watch, including specific files in that directory (or the entire * directory contents if no file is specified). */ private class WatchedDirectory { @GuardedBy("this") private WatchKey watchKey = null; private final Path dirPath; private final Set watchedFiles = new HashSet<>(); WatchedDirectory(Path dirPath) { this.dirPath = dirPath; } void addFile(String filename) { watchedFiles.add(filename); } synchronized void register() throws IOException { unregister(); this.watchKey = dirPath.register(watchService, watchEvents); watchedKeys.put(watchKey, dirPath); } synchronized void unregister() { if (this.watchKey != null) { watchedKeys.remove(watchKey); watchKey.cancel(); this.watchKey = null; } } } @Implementation protected synchronized void startWatching() throws IOException { // If we're already watching, startWatching is a no-op. if (watcherRunnable != null) { return; } // If we don't have any supported events to watch for, don't do anything. if (watchEvents.length == 0) { return; } for (WatchedDirectory watchedDirectory : watchedDirectories.values()) { watchedDirectory.register(); } watcherRunnable = new WatcherRunnable(realFileObserver, watchedDirectories, watchedKeys, watchService); Thread thread = new Thread(watcherRunnable, "ShadowFileObserver"); thread.start(); } @Implementation protected void stopWatching() { for (WatchedDirectory watchedDirectory : watchedDirectories.values()) { watchedDirectory.unregister(); } synchronized (this) { if (watcherRunnable != null) { watcherRunnable.stop(); watcherRunnable = null; } } } private static int fileObserverEventFromWatcherEvent(WatchEvent.Kind kind) { if (kind == StandardWatchEventKinds.ENTRY_CREATE) { return FileObserver.CREATE; } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { return FileObserver.DELETE; } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { return FileObserver.MODIFY; } return 0; } /** Runnable implementation that processes all events for keys queued to the watcher. */ private static class WatcherRunnable implements Runnable { @GuardedBy("this") private boolean shouldStop = false; private final FileObserver realFileObserver; private final Map watchedDirectories; private final Map watchedKeys; private final WatchService watchService; public WatcherRunnable( FileObserver realFileObserver, Map watchedDirectories, Map watchedKeys, WatchService watchService) { this.realFileObserver = realFileObserver; this.watchedDirectories = watchedDirectories; this.watchedKeys = watchedKeys; this.watchService = watchService; } public synchronized void stop() { this.shouldStop = true; } public synchronized boolean shouldContinue() { return !shouldStop; } @SuppressWarnings("unchecked") private WatchEvent castToPathWatchEvent(WatchEvent untypedWatchEvent) { return (WatchEvent) untypedWatchEvent; } @Override public void run() { while (shouldContinue()) { // wait for key to be signalled WatchKey key; try { key = watchService.take(); } catch (InterruptedException x) { return; } Path dir = watchedKeys.get(key); if (dir != null) { WatchedDirectory watchedDirectory = watchedDirectories.get(dir.toString()); List> events = key.pollEvents(); for (WatchEvent event : events) { WatchEvent.Kind kind = event.kind(); // Ignore OVERFLOW events if (kind == StandardWatchEventKinds.OVERFLOW) { continue; } WatchEvent ev = castToPathWatchEvent(event); Path fileName = ev.context().getFileName(); if (watchedDirectory.watchedFiles.isEmpty()) { realFileObserver.onEvent( fileObserverEventFromWatcherEvent(kind), fileName.toString()); } else { for (String watchedFile : watchedDirectory.watchedFiles) { if (fileName.toString().equals(watchedFile)) { realFileObserver.onEvent( fileObserverEventFromWatcherEvent(kind), fileName.toString()); } } } } } boolean valid = key.reset(); if (!valid) { return; } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy