diff --git a/src/IKVM.Java/local/sun/nio/fs/DotNetFileSystem.java b/src/IKVM.Java/local/sun/nio/fs/DotNetFileSystem.java index 00dfbeccab..9be3a662ba 100644 --- a/src/IKVM.Java/local/sun/nio/fs/DotNetFileSystem.java +++ b/src/IKVM.Java/local/sun/nio/fs/DotNetFileSystem.java @@ -206,13 +206,293 @@ public boolean matches(Path path) { }; } - public UserPrincipalLookupService getUserPrincipalLookupService() { + public UserPrincipalLookupService getUserPrincipalLookupService() + { throw new UnsupportedOperationException(); } + static final class NetWatchService implements WatchService + { + static final WatchEvent overflowEvent = new WatchEvent() { + public Object context() { + return null; + } + public int count() { + return 1; + } + public WatchEvent.Kind kind() { + return StandardWatchEventKinds.OVERFLOW; + } + }; + private static final WatchKey CLOSED = new WatchKey() { + public boolean isValid() { return false; } + public List> pollEvents() { return null; } + public boolean reset() { return false; } + public void cancel() { } + public Watchable watchable() { return null; } + }; + private boolean closed; + private final ArrayList keys = new ArrayList<>(); + private final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + + public synchronized void close() + { + if (!closed) + { + closed = true; + for (NetWatchKey key : keys) + { + key.close(); + } + enqueue(CLOSED); + } + } + + private WatchKey checkClosed(WatchKey key) + { + if (key == CLOSED) + { + enqueue(CLOSED); + throw new ClosedWatchServiceException(); + } + return key; + } + + public WatchKey poll() + { + return checkClosed(queue.poll()); + } + + public WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException + { + return checkClosed(queue.poll(timeout, unit)); + } + + public WatchKey take() throws InterruptedException + { + return checkClosed(queue.take()); + } + + void enqueue(WatchKey key) + { + for (;;) + { + try + { + queue.put(key); + return; + } + catch (InterruptedException _) + { + } + } + } + + private final class NetWatchKey implements WatchKey + { + private final DotNetPath path; + private FileSystemWatcher fsw; + private ArrayList> list = new ArrayList<>(); + private HashSet modified = new HashSet<>(); + private boolean signaled; + + NetWatchKey(DotNetPath path) + { + this.path = path; + } + + synchronized void init(final boolean create, final boolean delete, final boolean modify, final boolean overflow, final boolean subtree) + { + if (fsw != null) + { + // we could reuse the FileSystemWatcher, but for now we just recreate it + // (and we run the risk of missing some events while we're doing that) + fsw.Dispose(); + fsw = null; + } + fsw = new FileSystemWatcher(path.path); + if (create) + { + fsw.add_Created(new FileSystemEventHandler(new FileSystemEventHandler.Method() { + public void Invoke(Object sender, FileSystemEventArgs e) { + addEvent(createEvent(e), null); + } + })); + } + if (delete) + { + fsw.add_Deleted(new FileSystemEventHandler(new FileSystemEventHandler.Method() { + public void Invoke(Object sender, FileSystemEventArgs e) { + addEvent(createEvent(e), null); + } + })); + } + if (modify) + { + fsw.add_Changed(new FileSystemEventHandler(new FileSystemEventHandler.Method() { + public void Invoke(Object sender, FileSystemEventArgs e) { + synchronized (NetWatchKey.this) { + if (modified.contains(e.get_Name())) { + // we already have an ENTRY_MODIFY event pending + return; + } + } + addEvent(createEvent(e), e.get_Name()); + } + })); + } + fsw.add_Error(new ErrorEventHandler(new ErrorEventHandler.Method() { + public void Invoke(Object sender, ErrorEventArgs e) { + if (e.GetException() instanceof cli.System.ComponentModel.Win32Exception + && ((cli.System.ComponentModel.Win32Exception)e.GetException()).get_ErrorCode() == -2147467259) { + // the directory we were watching was deleted + cancelledByError(); + } else if (overflow) { + addEvent(overflowEvent, null); + } + } + })); + if (subtree) + { + fsw.set_IncludeSubdirectories(true); + } + fsw.set_EnableRaisingEvents(true); + } + + WatchEvent createEvent(final FileSystemEventArgs e) + { + return new WatchEvent() { + public Path context() { + return new DotNetPath((DotNetFileSystem)path.getFileSystem(), e.get_Name()); + } + public int count() { + return 1; + } + public WatchEvent.Kind kind() { + switch (e.get_ChangeType().Value) { + case WatcherChangeTypes.Created: + return StandardWatchEventKinds.ENTRY_CREATE; + case WatcherChangeTypes.Deleted: + return StandardWatchEventKinds.ENTRY_DELETE; + default: + return StandardWatchEventKinds.ENTRY_MODIFY; + } + } + }; + } + + void cancelledByError() + { + cancel(); + synchronized (this) + { + if (!signaled) + { + signaled = true; + enqueue(this); + } + } + } + + synchronized void addEvent(WatchEvent event, String modified) + { + list.add(event); + if (modified != null) + { + this.modified.add(modified); + } + if (!signaled) + { + signaled = true; + enqueue(this); + } + } + + public synchronized boolean isValid() + { + return fsw != null; + } + + public synchronized List> pollEvents() + { + ArrayList> r = list; + list = new ArrayList<>(); + modified.clear(); + return r; + } + + public synchronized boolean reset() + { + if (fsw == null) + { + return false; + } + if (signaled) + { + if (list.size() == 0) + { + signaled = false; + } + else + { + enqueue(this); + } + } + return true; + } + + void close() + { + if (fsw != null) + { + fsw.Dispose(); + fsw = null; + } + } + + public void cancel() + { + synchronized (NetWatchService.this) + { + keys.remove(this); + close(); + } + } + + public Watchable watchable() + { + return path; + } + } + + synchronized WatchKey register(DotNetPath path, boolean create, boolean delete, boolean modify, boolean overflow, boolean subtree) + { + if (closed) + { + throw new ClosedWatchServiceException(); + } + NetWatchKey existing = null; + for (NetWatchKey key : keys) + { + if (key.watchable().equals(path)) + { + existing = key; + break; + } + } + if (existing == null) + { + existing = new NetWatchKey(path); + keys.add(existing); + } + existing.init(create, delete, modify, overflow, subtree); + return existing; + } + } + public WatchService newWatchService() throws IOException { if (cli.IKVM.Runtime.RuntimeUtil.get_IsWindows()) { - return new DotNetWatchService(this); + return new NetWatchService(); } else { // FileSystemWatcher implementation on .NET for Unix consumes way too many inotify queues return new PollingWatchService(); diff --git a/src/IKVM.Java/local/sun/nio/fs/DotNetPath.java b/src/IKVM.Java/local/sun/nio/fs/DotNetPath.java index c4cbade4a9..d10b3464c9 100644 --- a/src/IKVM.Java/local/sun/nio/fs/DotNetPath.java +++ b/src/IKVM.Java/local/sun/nio/fs/DotNetPath.java @@ -25,6 +25,7 @@ package sun.nio.fs; import com.sun.nio.file.ExtendedWatchEventModifier; +import com.sun.nio.file.SensitivityWatchEventModifier; import java.io.File; import java.io.IOException; import java.net.URI; @@ -510,26 +511,75 @@ public Path toRealPath(LinkOption... options) throws IOException private static native String toRealPathImpl(String path); - public WatchKey register(WatchService watcher, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) throws IOException { - watcher.getClass(); - - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - boolean subtree = false; - for (WatchEvent.Modifier modifier : modifiers) { - if (modifier == ExtendedWatchEventModifier.FILE_TREE) { - subtree = true; - break; - } + public WatchKey register(WatchService watcher, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) throws IOException + { + if (!(watcher instanceof DotNetFileSystem.NetWatchService)) + { + // null check + watcher.getClass(); + throw new ProviderMismatchException(); + } + boolean create = false; + boolean delete = false; + boolean modify = false; + boolean overflow = false; + boolean subtree = false; + for (WatchEvent.Kind kind : events) + { + if (kind == StandardWatchEventKinds.ENTRY_CREATE) + { + create = true; + } + else if (kind == StandardWatchEventKinds.ENTRY_DELETE) + { + delete = true; + } + else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) + { + modify = true; + } + else if (kind == StandardWatchEventKinds.OVERFLOW) + { + overflow = true; + } + else + { + // null check + kind.getClass(); + throw new UnsupportedOperationException(); + } + } + if (!create && !delete && !modify) + { + throw new IllegalArgumentException(); + } + for (WatchEvent.Modifier modifier : modifiers) + { + if (modifier == ExtendedWatchEventModifier.FILE_TREE) + { + subtree = true; + } + else if (modifier instanceof SensitivityWatchEventModifier) + { + // ignore } - + else + { + // null check + modifier.getClass(); + throw new UnsupportedOperationException(); + } + } + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { sm.checkRead(path); - if (subtree) { + if (subtree) + { sm.checkRead(path + cli.System.IO.Path.DirectorySeparatorChar + '-'); } } - - return ((AbstractWatchService)watcher).register(this, events, modifiers); + return ((DotNetFileSystem.NetWatchService)watcher).register(this, create, delete, modify, overflow, subtree); } public int compareTo(Path other) @@ -589,4 +639,4 @@ static DotNetPath from(Path path) } return (DotNetPath)path; } -} +} \ No newline at end of file diff --git a/src/IKVM.Java/local/sun/nio/fs/DotNetWatchService.java b/src/IKVM.Java/local/sun/nio/fs/DotNetWatchService.java deleted file mode 100644 index c143f4d536..0000000000 --- a/src/IKVM.Java/local/sun/nio/fs/DotNetWatchService.java +++ /dev/null @@ -1,83 +0,0 @@ -package sun.nio.fs; - -import java.io.Closeable; -import java.io.IOException; -import java.lang.IllegalArgumentException; -import java.nio.file.ClosedWatchServiceException; -import java.nio.file.Path; -import java.nio.file.ProviderMismatchException; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.util.concurrent.ConcurrentHashMap; - -final class DotNetWatchService extends AbstractWatchService { - private final DotNetFileSystem fs; - private final ConcurrentHashMap keys = new ConcurrentHashMap(); - - DotNetWatchService(DotNetFileSystem fs) { - this.fs = fs; - } - - @Override - void implClose() - throws IOException { - for (DotNetWatchKey key : keys.values()) { - key.close(); - } - - keys.clear(); - } - - @Override - WatchKey register(Path path, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) - throws IOException { - if (!isOpen()) { - throw new ClosedWatchServiceException(); - } - if (!(path instanceof DotNetPath)) { - path.getClass(); - throw new ProviderMismatchException(); - } - final DotNetPath dir = (DotNetPath) path; - - final DotNetWatchKey key = keys.computeIfAbsent(dir, c -> new DotNetWatchKey(c)); - register0(fs, key, dir, events, (Object[]) modifiers); - return key; - } - - final class DotNetWatchKey extends AbstractWatchKey implements Closeable { - private final DotNetPath dir; - private Object state; - - DotNetWatchKey(DotNetPath dir) { - super(dir, DotNetWatchService.this); - this.dir = dir; - } - - @Override - public void cancel() { - if (DotNetWatchService.this.keys.remove(dir, this)) { - close(); - } - } - - @Override - public void close() { - close0(this); - } - - @Override - public boolean isValid() { - return state != null; - } - - void error() { - cancel(); - signal(); - } - } - - static native void close0(Object key); - - static native void register0(Object fs, Object key, Object dir, Object[] events, Object... modifiers); -} \ No newline at end of file diff --git a/src/IKVM.Runtime/Accessors/Com/Sun/Nio/File/ExtendedWatchEventModifierAccessor.cs b/src/IKVM.Runtime/Accessors/Com/Sun/Nio/File/ExtendedWatchEventModifierAccessor.cs deleted file mode 100644 index a2519ae653..0000000000 --- a/src/IKVM.Runtime/Accessors/Com/Sun/Nio/File/ExtendedWatchEventModifierAccessor.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace IKVM.Runtime.Accessors.Com.Sun.Nio.File -{ -#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false - - internal sealed class ExtendedWatchEventModifierAccessor : Accessor - { - private FieldAccessor fileTree; - - public object FILE_TREE => GetField(ref fileTree, nameof(FILE_TREE)).GetValue(); - - public ExtendedWatchEventModifierAccessor(AccessorTypeResolver resolver) - : base(resolver, "com.sun.nio.file.ExtendedWatchEventModifier") - { - } - } - -#endif -} diff --git a/src/IKVM.Runtime/Accessors/Com/Sun/Nio/File/SensitivityWatchEventModifierAccessor.cs b/src/IKVM.Runtime/Accessors/Com/Sun/Nio/File/SensitivityWatchEventModifierAccessor.cs deleted file mode 100644 index e283113064..0000000000 --- a/src/IKVM.Runtime/Accessors/Com/Sun/Nio/File/SensitivityWatchEventModifierAccessor.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace IKVM.Runtime.Accessors.Com.Sun.Nio.File -{ - -#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false - - internal class SensitivityWatchEventModifierAccessor : Accessor - { - public SensitivityWatchEventModifierAccessor(AccessorTypeResolver resolver) - : base(resolver, "com.sun.nio.file.SensitivityWatchEventModifier") - { - } - - public bool Is(object self) => Type.IsInstanceOfType(self); - } - -#endif - -} diff --git a/src/IKVM.Runtime/Accessors/Java/Nio/File/StandardWatchEventKindsAccessor.cs b/src/IKVM.Runtime/Accessors/Java/Nio/File/StandardWatchEventKindsAccessor.cs deleted file mode 100644 index 9401216b35..0000000000 --- a/src/IKVM.Runtime/Accessors/Java/Nio/File/StandardWatchEventKindsAccessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace IKVM.Runtime.Accessors.Java.Nio.File -{ - -#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false - - internal sealed class StandardWatchEventKindsAccessor : Accessor - { - private FieldAccessor entryCreate; - private FieldAccessor entryDelete; - private FieldAccessor entryModify; - private FieldAccessor overflow; - - public object ENTRY_CREATE => GetField(ref entryCreate, nameof(ENTRY_CREATE)).GetValue(); - - public object ENTRY_DELETE => GetField(ref entryDelete, nameof(ENTRY_DELETE)).GetValue(); - - public object ENTRY_MODIFY => GetField(ref entryModify, nameof(ENTRY_MODIFY)).GetValue(); - - public object OVERFLOW => GetField(ref overflow, nameof(OVERFLOW)).GetValue(); - - public StandardWatchEventKindsAccessor(AccessorTypeResolver resolver) - : base(resolver, "java.nio.file.StandardWatchEventKinds") - { - } - } - -#endif - -} \ No newline at end of file diff --git a/src/IKVM.Runtime/Accessors/Sun/Nio/Fs/DotNetWatchServiceKeyAccessor.cs b/src/IKVM.Runtime/Accessors/Sun/Nio/Fs/DotNetWatchServiceKeyAccessor.cs deleted file mode 100644 index 6e561c826c..0000000000 --- a/src/IKVM.Runtime/Accessors/Sun/Nio/Fs/DotNetWatchServiceKeyAccessor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace IKVM.Runtime.Accessors.Sun.Nio.Fs -{ - -#if FIRST_PASS == false && EXPORTER == false && IMPORTER == false - - internal class DotNetWatchServiceKeyAccessor : Accessor - { - private MethodAccessor> error0; - private MethodAccessor> signalEvent0; - private MethodAccessor> signalEvent1; - private FieldAccessor state; - private Type voidType; - private Type watchEventKind; - - internal FieldAccessor State => GetField(ref state, "state"); - - private Type Void => voidType ??= typeof(void); - - private Type WatchEventKind => watchEventKind ??= Resolve("java.nio.file.WatchEvent+Kind"); - - public DotNetWatchServiceKeyAccessor(AccessorTypeResolver resolver) - : base(resolver, "sun.nio.fs.DotNetWatchService+DotNetWatchKey") - { - } - - public void error(object self) - => (error0 ??= GetMethod(ref error0, nameof(error), Void)).Invoker(self); - - public void signalEvent(object self) - => (signalEvent0 ??= GetMethod(ref signalEvent0, nameof(signalEvent), Void)).Invoker(self); - - public void signalEvent(object self, object kind, object context) - => (signalEvent1 ??= GetMethod(ref signalEvent1, nameof(signalEvent), Void, WatchEventKind, typeof(object))).Invoker(self, kind, context); - } - -#endif - -} diff --git a/src/IKVM.Runtime/IKVM.Runtime.csproj b/src/IKVM.Runtime/IKVM.Runtime.csproj index 8d947a6f28..28b954dd32 100644 --- a/src/IKVM.Runtime/IKVM.Runtime.csproj +++ b/src/IKVM.Runtime/IKVM.Runtime.csproj @@ -44,6 +44,11 @@ + + + + + diff --git a/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetWatchService.cs b/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetWatchService.cs deleted file mode 100644 index b325be8f6c..0000000000 --- a/src/IKVM.Runtime/Java/Externs/sun/nio/fs/DotNetWatchService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using IKVM.Runtime.Accessors.Sun.Nio.Fs; -using usnfs = IKVM.Runtime.Util.Sun.Nio.Fs; - -namespace IKVM.Java.Externs.sun.nio.fs -{ - internal static partial class DotNetWatchService - { - - public static void close0(object key) - { -#if FIRST_PASS - throw new NotImplementedException(); -#else - usnfs.DotNetWatchService.Close(key); -#endif - } - - public static void register0(object fs, object key, object dir, object[] events, params object[] modifiers) - { -#if FIRST_PASS - throw new NotImplementedException(); -#else - usnfs.DotNetWatchService.Register(fs, key, dir, events, modifiers); -#endif - } - - } - -} \ No newline at end of file diff --git a/src/IKVM.Runtime/Util/Sun/Nio/Fs/DotNetWatchService.cs b/src/IKVM.Runtime/Util/Sun/Nio/Fs/DotNetWatchService.cs deleted file mode 100644 index 2b3916f593..0000000000 --- a/src/IKVM.Runtime/Util/Sun/Nio/Fs/DotNetWatchService.cs +++ /dev/null @@ -1,174 +0,0 @@ -using IKVM.Runtime.Accessors.Com.Sun.Nio.File; -using IKVM.Runtime.Accessors.Java.Nio.File; -using IKVM.Runtime.Accessors.Sun.Nio.Fs; -using IKVM.Runtime.Extensions; -using System; -using System.ComponentModel; -using System.IO; - -namespace IKVM.Runtime.Util.Sun.Nio.Fs -{ - -#if !FIRST_PASS - - internal static class DotNetWatchService - { - private static DotNetPathAccessor dotNetPath; - private static DotNetWatchServiceKeyAccessor dotNetWatchServiceKey; - private static ExtendedWatchEventModifierAccessor extendedWatchEventModifier; - private static SensitivityWatchEventModifierAccessor sensitivityWatchEventModifier; - private static StandardWatchEventKindsAccessor standardWatchEventKinds; - - private static DotNetPathAccessor DotNetPath => JVM.BaseAccessors.Get(ref dotNetPath); - - private static DotNetWatchServiceKeyAccessor DotNetWatchServiceKey => JVM.BaseAccessors.Get(ref dotNetWatchServiceKey); - - private static ExtendedWatchEventModifierAccessor ExtendedWatchEventModifier => JVM.BaseAccessors.Get(ref extendedWatchEventModifier); - - private static SensitivityWatchEventModifierAccessor SensitivityWatchEventModifier => JVM.BaseAccessors.Get(ref sensitivityWatchEventModifier); - - private static StandardWatchEventKindsAccessor StandardWatchEventKinds => JVM.BaseAccessors.Get(ref standardWatchEventKinds); - - internal static void Close(object key) - { - if (DotNetWatchServiceKey.State.ExchangeValue(key, null) is not FileSystemWatcher fsw) - { - return; - } - - fsw.Dispose(); - } - - internal static void Register(object fs, object key, object dir, object[] events, object[] modifiers) - => Register(fs, key, DotNetPath.GetPath(dir), events, modifiers); - - internal static void Register(object fs, object key, string dir, object[] events, object[] modifiers) - { - // we could reuse the FileSystemWatcher, but for now we just recreate it - // (and we run the risk of missing some events while we're doing that) - Close(key); - FileSystemWatcher watcher = null; - try - { - watcher = new FileSystemWatcher(dir); - WatchServiceHandler handler = new WatchServiceHandler(fs, key); - bool valid = false; - foreach (var @event in events) - { - if (@event == StandardWatchEventKinds.OVERFLOW) - { - handler.Overflow = true; - } - else - { - valid = true; - if (@event == StandardWatchEventKinds.ENTRY_CREATE) - { - watcher.Created += handler.EventHandler; - } - else if (@event == StandardWatchEventKinds.ENTRY_DELETE) - { - watcher.Deleted += handler.EventHandler; - } - else if (@event == StandardWatchEventKinds.ENTRY_MODIFY) - { - watcher.Changed += handler.EventHandler; - } - else - { - @event.GetType(); - throw new global::java.lang.UnsupportedOperationException(); - } - } - } - - if (!valid) - { - throw new global::java.lang.IllegalArgumentException(); - } - - watcher.Error += handler.ErrorHandler; - foreach (var modifier in modifiers) - { - if (modifier == ExtendedWatchEventModifier.FILE_TREE) - { - watcher.IncludeSubdirectories = true; - } - else if (SensitivityWatchEventModifier.Is(modifier)) - { - // Ignore - } - else - { - modifier.GetType(); - throw new global::java.lang.UnsupportedOperationException(); - } - } - - watcher.EnableRaisingEvents = true; - DotNetWatchServiceKey.State.SetValue(key, watcher); - watcher = null; - } - catch (Exception e) when (e is ArgumentException or FileNotFoundException) - { - throw new global::java.io.FileNotFoundException(); - } - finally - { - watcher?.Dispose(); - } - } - - private class WatchServiceHandler - { - private readonly object fs; - private readonly object key; - - public readonly ErrorEventHandler ErrorHandler; - - public readonly FileSystemEventHandler EventHandler; - - public bool Overflow { get; set; } - - public WatchServiceHandler(object fs, object key) - { - this.fs = fs; - this.key = key; - ErrorHandler = OnError; - EventHandler = OnEvent; - } - - private void OnError(object sender, ErrorEventArgs e) - { - const int E_FAIL = unchecked((int)0x80004005); - - if (e.GetException() is Win32Exception win32Exception - && win32Exception.ErrorCode == E_FAIL) - { - DotNetWatchServiceKey.error(key); - } - else if (Overflow) - { - DotNetWatchServiceKey.signalEvent(key, - StandardWatchEventKinds.OVERFLOW, null); - } - } - - private void OnEvent(object sender, FileSystemEventArgs e) - { - DotNetWatchServiceKey.signalEvent(key, - e.ChangeType switch - { - WatcherChangeTypes.Created => StandardWatchEventKinds.ENTRY_CREATE, - WatcherChangeTypes.Deleted => StandardWatchEventKinds.ENTRY_DELETE, - _ => StandardWatchEventKinds.ENTRY_MODIFY - }, DotNetPath.Init(fs, e.Name)); - } - - } - - } - -#endif - -}