diff --git a/src/main/java/net/openhft/chronicle/map/FilePerKeyMap.java b/src/main/java/net/openhft/chronicle/map/FilePerKeyMap.java index b38f6e854..fcc60f61e 100644 --- a/src/main/java/net/openhft/chronicle/map/FilePerKeyMap.java +++ b/src/main/java/net/openhft/chronicle/map/FilePerKeyMap.java @@ -29,6 +29,7 @@ import java.util.stream.Stream; public class FilePerKeyMap implements Map { private final Path dirPath; private final Map lastModifiedByProgram = new ConcurrentHashMap<>(); + private final Map lastUpdate = new ConcurrentHashMap<>(); private final List> listeners = new ArrayList<>(); private final FPMWatcher fileFpmWatcher = new FPMWatcher(); @@ -147,7 +148,7 @@ public class FilePerKeyMap implements Map { String existingValue = null; if(Files.exists(path)) { try { - existingValue = Files.readAllLines(path).stream().collect(Collectors.joining()); + existingValue = Files.readAllLines(path).stream().collect(Collectors.joining("\n")); } catch (IOException e) { throw new RuntimeException(e); } @@ -242,21 +243,24 @@ public class FilePerKeyMap implements Map { try { key = watcher.take(); for (WatchEvent event : key.pollEvents()) { - // get event type WatchEvent.Kind kind = event.kind(); // get file name WatchEvent ev = (WatchEvent) event; Path fileName = ev.context(); - String mapKey = fileName.toString(); + if(mapKey.startsWith(".")){ + //this avoids temporary files being added to the map + continue; + } if (kind == StandardWatchEventKinds.OVERFLOW) { continue; } else if (kind == StandardWatchEventKinds.ENTRY_CREATE) { Path p = dirPath.resolve(fileName); String mapVal = getFileContents(p); + lastUpdate.put(p.toFile(), mapVal); if(isProgrammaticUpdate(p.toFile())){ fireEvent(new FPMEvent(FPMEvent.EventType.NEW, true, mapKey,mapVal)); }else { @@ -264,15 +268,25 @@ public class FilePerKeyMap implements Map { } } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { Path p = dirPath.resolve(fileName); + lastUpdate.remove(p.toFile()); if(isProgrammaticUpdate(p.toFile())){ fireEvent(new FPMEvent(FPMEvent.EventType.DELETE, true, mapKey, null)); - lastModifiedByProgram.remove(p.toFile()); }else { fireEvent(new FPMEvent(FPMEvent.EventType.DELETE, false, mapKey, null)); } } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { Path p = dirPath.resolve(fileName); String mapVal = getFileContents(p); + String lastVal = null; + if(mapVal != null) { + lastVal = lastUpdate.put(p.toFile(), mapVal); + } + + if(lastVal != null && lastVal.equals(mapVal)){ + //Nothing has changed don't fire an event + continue; + } + if(isProgrammaticUpdate(p.toFile())){ fireEvent(new FPMEvent(FPMEvent.EventType.UPDATE, true, mapKey,mapVal)); }else { @@ -287,7 +301,7 @@ public class FilePerKeyMap implements Map { } } } catch (IOException e) { - throw new AssertionError(e); + throw new RuntimeException(e); } } } diff --git a/src/test/java/net/openhft/chronicle/map/FilePerKeyMapTest.java b/src/test/java/net/openhft/chronicle/map/FilePerKeyMapTest.java index 56c75ae50..47434f081 100644 --- a/src/test/java/net/openhft/chronicle/map/FilePerKeyMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/FilePerKeyMapTest.java @@ -1,11 +1,10 @@ package net.openhft.chronicle.map; +import org.junit.BeforeClass; import org.junit.Test; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.function.Consumer; import static org.junit.Assert.*; @@ -13,17 +12,18 @@ import static org.junit.Assert.*; * JUnit test class to support {@link FilePerKeyMapTest} */ public class FilePerKeyMapTest { + private static FilePerKeyMap map; + @BeforeClass + public static void createMap(){ + map = new FilePerKeyMap("/tmp/filepermaptests"); + //just in case it hasn't been cleared up last time + map.clear(); + } /** * Testing all the methods of the map with simple tests. */ @Test public void testMapMethods() { - //There are no entries in the map so null should be returned - FilePerKeyMap map = new FilePerKeyMap("/tmp/filepermaptests"); - - //just in case it hasn't been cleared up last time - map.clear(); - String val = map.put("one", "test1"); assertEquals(val, null); @@ -103,25 +103,11 @@ public class FilePerKeyMapTest { } } - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - map.clear(); - map.close(); - } @Test public void perfTest(){ - //There are no entries in the map so null should be returned - FilePerKeyMap map = new FilePerKeyMap("/tmp/filepermaptests"); - - //just in case it hasn't been cleared up last time - map.clear(); - StringBuilder sb = new StringBuilder(); for (int i = 0; i < 2_000_000; i++) { sb.append('x'); @@ -139,32 +125,46 @@ public class FilePerKeyMapTest { for (int i = 0; i < iterations; i++) { map.put("big file", value); } - System.out.println("Time to update "+ iterations + " iterations " + (System.currentTimeMillis()-time)); + System.out.println("Time to update " + iterations + " iterations " + (System.currentTimeMillis() - time)); map.clear(); - map.close(); } @Test public void eventTest() { - //There are no entries in the map so null should be returned - FilePerKeyMap map = new FilePerKeyMap("/tmp/filepermaptests"); + final List events = new ArrayList<>(); - //just in case it hasn't been cleared up last time - map.clear(); - - map.registerForEvents(e -> System.out.println(e)); + Consumer fpmEventConsumer = (FPMEvent e) -> { + System.out.println(e); + events.add(e); + }; + map.registerForEvents(fpmEventConsumer); map.put("one", "one"); + map.put("one", "two"); + map.remove("one"); map.put("one", "one"); try { - Thread.sleep(3000); + //Allow all events to be played through + Thread.sleep(1000); + //Check the events + assertEquals(4, events.size()); + + assertEquals(FPMEvent.EventType.NEW,events.get(0).getEventType()); + assertEquals(FPMEvent.EventType.UPDATE, events.get(1).getEventType()); + assertEquals(FPMEvent.EventType.DELETE, events.get(2).getEventType()); + assertEquals(FPMEvent.EventType.NEW, events.get(3).getEventType()); + + assertEquals(true, events.get(0).isProgrammatic()); + assertEquals(true, events.get(1).isProgrammatic()); + assertEquals(true, events.get(2).isProgrammatic()); + assertEquals(true, events.get(3).isProgrammatic()); } catch (InterruptedException e) { e.printStackTrace(); } + map.unregisterForEvents(fpmEventConsumer); map.clear(); - map.close(); } }