22
33import java .io .*;
44import java .nio .file .*;
5+ import java .nio .file .attribute .BasicFileAttributes ;
56import java .util .*;
67import java .util .concurrent .ConcurrentHashMap ;
8+ import java .util .concurrent .atomic .AtomicBoolean ;
79import java .util .function .Consumer ;
810
911import static java .nio .file .StandardWatchEventKinds .*;
@@ -50,52 +52,51 @@ class DirectoryEntry {
5052 directoryPath = directory .toPath ();
5153
5254 if (loadingOption == ByteLoadingOption .WATCHLOAD ){
53- /* load top level directory */ {
54- final File [] listFiles = Objects .requireNonNullElse (directory .listFiles (),new File [0 ]);
55- for (final File file : listFiles ) // initial population
56- if (file .isFile ())
55+ if (!isWalkthrough ){ // load top level only
56+ for (final File file : Objects .requireNonNullElse (directory .listFiles (), new File [0 ])) // initial population
57+ if (!file .isDirectory ()) // File#isFile does not work
5758 try {
5859 preloadedFiles .put (
5960 getContext (adapter .getName (file )),
6061 new FileEntry (file , adapter , ByteLoadingOption .WATCHLOAD , true )
6162 );
62- }catch (final RuntimeException ignored ){ }
63+ }catch (final UncheckedIOException ignored ){ }
6364 try { // create watch service for top level directory
64- createWatchService (directoryPath , createWatchServiceConsumer ());
65+ createWatchService (directoryPath , createWatchServiceConsumer (directoryPath ));
6566 }catch (final IOException e ){
6667 throw new UncheckedIOException (e );
6768 }
68- }
69- if (isWalkthrough ){ /* load sub directories */
69+ }else { // load top and sub levels
7070 try {
71- Files .walk (directoryPath ).filter (path -> path .toFile ().isDirectory ()).forEach (path -> {
72- final File p2f = path .toFile ();
73- final String rel = directoryPath .relativize (path ).toString ();
71+ Files .walkFileTree (directoryPath , new SimpleFileVisitor <>() {
72+ @ Override
73+ public final FileVisitResult preVisitDirectory (final Path path , final BasicFileAttributes attrs ) throws IOException {
74+ createWatchService (path , createWatchServiceConsumer (path ));
75+ return FileVisitResult .CONTINUE ;
76+ }
77+
78+ @ Override
79+ public final FileVisitResult visitFile (final Path path , final BasicFileAttributes attrs ){
80+ final File file = path .toFile ();
81+ final String relative = directoryPath .relativize (path .getParent ()).toString (); // attach the relative path (parent) to the adapted file name
7482
75- final File [] listFile = Objects .requireNonNullElse (p2f .listFiles (),new File [0 ]);
76- for (final File file : listFile ) // initial population
7783 try {
7884 preloadedFiles .put (
79- ( rel . isEmpty () || rel . equals ( "/" ) || rel . equals ( " \\ " ) ? "" : getContext ( rel )) + getContext ( adapter .getName (file )),
80- new FileEntry (file ,adapter ,loadingOption ,true )
85+ joinContext ( relative , adapter .getName (file )),
86+ new FileEntry (file , adapter , loadingOption , true )
8187 );
82- }catch (final RuntimeException ignored ){ }
88+ }catch (final UncheckedIOException ignored ){ }
8389
84- // create watch service for directory
85- try {
86- createWatchService (path , createWatchServiceConsumer ());
87- }catch (final IOException e ){
88- throw new UncheckedIOException (e );
90+ return FileVisitResult .CONTINUE ;
8991 }
90-
9192 });
9293 }catch (final IOException e ){
9394 throw new UncheckedIOException (e );
9495 }
9596 }
9697 }else if (loadingOption == ByteLoadingOption .PRELOAD ){
97- /* load top level directory */ {
98- final File [] listFiles = Objects .requireNonNullElse (directory .listFiles (),new File [0 ]);
98+ if (! isWalkthrough ){ // load top level only
99+ final File [] listFiles = Objects .requireNonNullElse (directory .listFiles (), new File [0 ]);
99100 for (final File file : listFiles ) // initial population
100101 if (!file .isDirectory ())
101102 try {
@@ -104,21 +105,23 @@ class DirectoryEntry {
104105 new FileEntry (file , adapter , ByteLoadingOption .PRELOAD )
105106 );
106107 }catch (final UncheckedIOException ignored ){ }
107- }
108- if (isWalkthrough ){ /* load sub directories */
108+ }else { // load top and sub levels
109109 try {
110- Files .walk (directoryPath ).filter (path -> path .toFile ().isDirectory ()).forEach (path -> {
111- final File p2f = path .toFile ();
112- final String relative = directoryPath .relativize (path ).toString ();
110+ Files .walkFileTree (directoryPath , new SimpleFileVisitor <>() {
111+ @ Override
112+ public final FileVisitResult visitFile (final Path path , final BasicFileAttributes attrs ){
113+ final File file = path .toFile ();
114+ final String relative = directoryPath .relativize (path .getParent ()).toString (); // attach the relative path (parent) to the adapted file name
113115
114- final File [] listFiles = Objects .requireNonNullElse (p2f .listFiles (), new File [0 ]);
115- for (final File file : listFiles ) // populate sub files
116116 try {
117117 preloadedFiles .put (
118- (relative . isEmpty () || relative . equals ( "/" ) || relative . equals ( " \\ " ) ? "" : getContext ( relative )) + getContext ( adapter .getName (file )),
118+ joinContext (relative , adapter .getName (file )),
119119 new FileEntry (file , adapter , ByteLoadingOption .PRELOAD )
120120 );
121121 }catch (final RuntimeException ignored ){ }
122+
123+ return FileVisitResult .CONTINUE ;
124+ }
122125 });
123126 }catch (final IOException e ){
124127 throw new UncheckedIOException (e );
@@ -128,60 +131,64 @@ class DirectoryEntry {
128131 }
129132
130133
131- private final Map <Path ,Thread > watchService = new ConcurrentHashMap <>();
134+ private final Map <Path ,AtomicBoolean > watchService = new ConcurrentHashMap <>();
132135
133136 private void createWatchService (final Path path , final Consumer <WatchEvent <?>> consumer ) throws IOException {
134137 final WatchService service = FileSystems .getDefault ().newWatchService ();
135138 path .register (service ,ENTRY_CREATE ,ENTRY_DELETE ,ENTRY_MODIFY );
136139
137- final Thread th =
140+ final AtomicBoolean stop = new AtomicBoolean (false );
141+
138142 new Thread (() -> {
139143 WatchKey key ;
140144 try {
141145 while ((key = service .take ()) != null ){
142- for (WatchEvent <?> event : key .pollEvents ()){
146+ for (WatchEvent <?> event : key .pollEvents ())
143147 consumer .accept (event );
144- key .reset ();
145- }
148+ key .reset ();
149+ if (stop .get ())
150+ break ;
146151 }
147152 }catch (final InterruptedException ignored ){ }
148- });
149-
150- th .start ();
153+ }).start ();
151154
152- watchService .put (path ,th );
155+ watchService .put (path ,stop );
153156 }
154157
155- @ SuppressWarnings ("StatementWithEmptyBody" )
156- private Consumer <WatchEvent <?>> createWatchServiceConsumer (){
158+ @ SuppressWarnings ({ "StatementWithEmptyBody" , "SpellCheckingInspection" } )
159+ private Consumer <WatchEvent <?>> createWatchServiceConsumer (final Path path ){
157160 return (WatchEvent <?> event ) -> {
158161 try {
159- final Path target = ( Path ) event .context ();
160- final File file = target .toFile ();
162+ final Path relTarg = directoryPath . resolve (( Path ) event .context ()); // only the file name (this method is flawed!)
163+ final File relFile = relTarg .toFile (); // only the file name (this method if flawed!)
161164 final WatchEvent .Kind <?> type = event .kind ();
162165
163- final String relative = getContext (directoryPath .relativize (target ).toString ());
164- final String context = (relative .isEmpty () || relative .equals ("/" ) || relative .equals ("\\ " ) ? "" : getContext (relative )) + getContext (adapter .getName (file ));
166+ final String top2sub = getContext (directoryPath .relativize (path ).toString ()); // the relative path between the top level directory and sub directory
167+ final String context = joinContext (top2sub ,adapter .getName (relFile )); // the file key
168+ final File file = new File (directoryPath + joinContext (top2sub ,relFile .getName ())); // the actual referable file
169+ final Path target = file .toPath ();
165170
166- if (file .isFile ())
171+ if (! file .isDirectory ()) // File#isFile does not work
167172 if (type == ENTRY_CREATE )
168173 preloadedFiles .put (context , new FileEntry (file ,adapter ,ByteLoadingOption .WATCHLOAD ,true ));
169174 else if (type == ENTRY_DELETE )
170175 preloadedFiles .remove (context );
171176 else if (type == ENTRY_MODIFY )
172- preloadedFiles .get (context ).reloadBytes ();
177+ Objects . requireNonNull ( preloadedFiles .get (context ) ).reloadBytes ();
173178 else ; // prevent ambiguous else with below
174- else
179+ else if ( isWalkthrough ) // only add/remove if walkthrough
175180 if (type == ENTRY_CREATE )
176181 try {
177- createWatchService (target , createWatchServiceConsumer ());
182+ createWatchService (target , createWatchServiceConsumer (target ));
178183 }catch (final IOException ignored ){ }
179184 else if (type == ENTRY_DELETE ){
180- watchService .get (target ). stop ( );
181- watchService .remove (target );
185+ Objects . requireNonNull ( watchService .get (relTarg )). set ( true );
186+ watchService .remove (relTarg );
182187 }
183188
184- }catch (final ClassCastException ignored ){ }
189+ preloadedFiles .get (context ).reloadBytes ();
190+
191+ }catch (final ClassCastException | NullPointerException ignored ){ }
185192 };
186193 }
187194
@@ -293,13 +300,23 @@ public final boolean isWalkthrough(){
293300
294301//
295302
303+ // leading slash only
296304 private static String getContext (final String path ){
297305 final String linSlash = path .replace ("\\ " ,"/" );
298306 if (linSlash .equals ("/" )) return "/" ;
299307 final String seSlash = (!linSlash .startsWith ("/" ) ? "/" : "" ) + linSlash + (!linSlash .endsWith ("/" ) ? "/" : "" );
300308 return seSlash .substring (0 ,seSlash .length ()-1 );
301309 }
302310
311+ private static String joinContext (final String ... paths ){
312+ final StringBuilder OUT = new StringBuilder ();
313+ for (final String path : paths ){
314+ final String context = getContext (path );
315+ OUT .append (context .isEmpty () || context .equals ("/" ) || context .equals ("\\ " ) ? "" : context );
316+ }
317+ return OUT .toString ();
318+ }
319+
303320//
304321
305322
0 commit comments