Commits

Dmytro Kovalchuk committed d09c184 Draft

It's now possible to attach node change listeners to registry parts

  • Participants
  • Parent commits b0190cb

Comments (0)

Files changed (10)

File registry/pom.xml

       <artifactId>easymock</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
     <description>Registry module introduces concept of virtual file system to store all system configuration</description>
 </project>

File registry/src/main/java/net/anatolich/registry/AbstractNode.java

 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 

File registry/src/main/java/net/anatolich/registry/NodeEventSupport.java

  */
 package net.anatolich.registry;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
+ * Utility class to provide all job behind node structure change events.
  *
  * @author Anatolich <anatolich@anatolich.net>
  */
 class NodeEventSupport {
-    
-    public void fireNodeAddedEvent(AbstractNode node){
-    }
-    
-    public void fireNodeRemovedEvent(AbstractNode node){
-    }
-    
-    public void fireNodePositionChanged(AbstractNode node, int oldPosition, int newPosition){
-    }
-    
-    public void addRegistryChangeListener(NodeChangeListener listener){
-    }
-    
-    public void addRegistryChangeListener(NodeChangeListener listener, Path path){
-    }
-    
-    public void removeRegistryChangeListener(NodeChangeListener listener){
+
+    private static final Logger log = LoggerFactory.getLogger(NodeEventSupport.class);
+    private Map<NodeChangeListener, Path> listeners;
+
+    public NodeEventSupport() {
+        this(Collections.EMPTY_MAP);
     }
 
+    NodeEventSupport(Map<NodeChangeListener, Path> listenersMap) {
+        listeners = new HashMap<>();
+        listeners.putAll(listenersMap);
+    }
+
+    public void fireNodeAddedEvent(AbstractNode node, Path path) {
+        NodeChangeEvent event = new NodeChangeEvent(node, node.getParent());
+        fireChangeEvent(path, event);
+    }
+
+    public void fireNodeRemovedEvent(AbstractNode node, Path path) {
+        NodeChangeEvent event = new NodeChangeEvent(node, node.getParent());
+        fireChangeEvent(path, event);
+    }
+
+    public void fireNodePositionChanged(AbstractNode node, Path path, int oldPosition, int newPosition) {
+        NodeChangeEvent event = new NodeChangeEvent(node, node.getParent(), oldPosition, newPosition);
+        fireChangeEvent(path, event);
+    }
+
+    public void addRegistryChangeListener(NodeChangeListener listener) {
+        addRegistryChangeListener(listener, Path.ROOT_PATH);
+    }
+
+    public void addRegistryChangeListener(NodeChangeListener listener, Path path) {
+        if (listeners.containsKey(listener)) {
+            Path oldPath = listeners.get(listener);
+            log.warn("Listener {} was reassigned from path {} to {}", new Object[]{listener, oldPath, path});
+        }
+        listeners.put(listener, path);
+    }
+
+    public void removeRegistryChangeListener(NodeChangeListener listener) {
+        listeners.remove(listener);
+    }
+
+    List<NodeChangeListener> getListenersByPath(Path path) {
+        List<NodeChangeListener> result = new ArrayList<>();
+        Iterator<Entry<NodeChangeListener, Path>> iterator = listeners.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Entry<NodeChangeListener, Path> entry = iterator.next();
+            if (entry.getValue().equals(path)) {
+                result.add(entry.getKey());
+            }
+        }
+        return result;
+    }
+
+    private void fireChangeEvent(Path path, NodeChangeEvent event) {
+        Iterator<Entry<NodeChangeListener, Path>> iterator = listeners.entrySet().iterator();
+        
+        while (iterator.hasNext()) {
+            Entry<NodeChangeListener, Path> entry = iterator.next();
+
+            if (path.isSubpathOf(entry.getValue())) {
+                entry.getKey().nodeAdded(event);
+            }
+
+        }
+    }
 }

File registry/src/main/java/net/anatolich/registry/Path.java

     Iterator<String> pathElementsIterator() {
         return Collections.unmodifiableList(elementList).iterator();
     }
+    
+    boolean isSubpathOf(Path path){
+        return this.pathString.startsWith(path.pathString);
+    }
 
     private String createStringRepresentation() {
         Iterator<String> iterator = elementList.iterator();

File registry/src/main/java/net/anatolich/registry/Registry.java

     public void nodeAdded(NodeChangeEvent event) {
         AbstractNode addedNode = event.getNode();
         listenNode(addedNode);
-        this.registryEventSupport.fireNodeAddedEvent(addedNode);
+        this.registryEventSupport.fireNodeAddedEvent(addedNode, getNodePath(addedNode));
     }
 
     @Override
     public void nodeRemoved(NodeChangeEvent event) {
         AbstractNode removedNode = event.getNode();
         removedNode.removeNodeChangeListener(this);
-        this.registryEventSupport.fireNodeRemovedEvent(removedNode);
+        this.registryEventSupport.fireNodeRemovedEvent(removedNode, null);
     }
 
     @Override

File registry/src/test/java/net/anatolich/registry/DefaultRegistryFixture.java

     private AbstractNode fileSaveMenuItemNode;
     private AbstractNode editMenuNode;
     private AbstractNode editCutMenuItemNode;
-    private AbstractNode editCopyMenuItemNode;
+    private AbstractNode editCopyMenuItemNode;    
 
     public DefaultRegistryFixture() {
         registry = new Registry();

File registry/src/test/java/net/anatolich/registry/NodeEventSupportTest.java

+/*
+ * Copyright 2012 Anatolich <anatolich@anatolich.net>.
+ *
+ * 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 net.anatolich.registry;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.matchers.JUnitMatchers.*;
+
+/**
+ *
+ * @author Anatolich <anatolich@anatolich.net>
+ */
+public class NodeEventSupportTest {
+    
+    @Test
+    public void testGetListenersByPath(){
+        NodeChangeListener rootListener_1 = createMock("rootListener_1", NodeChangeListener.class);
+        NodeChangeListener rootListener_2 = createMock("rootListener_2", NodeChangeListener.class);
+        
+        NodeChangeListener menusListener_1 = createMock("menusListener_1", NodeChangeListener.class);
+        Path menusPath = Path.fromString("/menus");
+        
+        Map<NodeChangeListener, Path> listeners = new HashMap<>();
+        
+        listeners.put(rootListener_1, Path.ROOT_PATH);
+        listeners.put(rootListener_2, Path.ROOT_PATH);
+        listeners.put(menusListener_1, menusPath);
+        
+        NodeEventSupport eventSupport = new NodeEventSupport(listeners);
+        
+        final List<NodeChangeListener> menusListeners = eventSupport.getListenersByPath(menusPath);
+        assertThat("Menus listeners check failed", menusListeners, allOf(hasItems(menusListener_1), not(hasItems(rootListener_1, rootListener_2))));
+        
+        final List<NodeChangeListener> rootListeners = eventSupport.getListenersByPath(Path.ROOT_PATH);
+        
+        assertThat("Root listeners check failed", rootListeners, allOf(hasItems(rootListener_1, rootListener_2), not(hasItems(menusListener_1))));
+        
+    }
+    
+    @Test
+    public void testAddRootListeners(){
+        NodeChangeListener rootListener_1 = createMock("rootListener_1", NodeChangeListener.class);
+        NodeChangeListener rootListener_2 = createMock("rootListener_2", NodeChangeListener.class);
+        
+        NodeEventSupport eventSupport = new NodeEventSupport();
+
+        assertTrue(eventSupport.getListenersByPath(Path.ROOT_PATH).isEmpty());
+        
+        eventSupport.addRegistryChangeListener(rootListener_1);
+        
+        assertThat(eventSupport.getListenersByPath(Path.ROOT_PATH), hasItems(rootListener_1));
+        
+        eventSupport.addRegistryChangeListener(rootListener_2);
+        
+        assertThat(eventSupport.getListenersByPath(Path.ROOT_PATH), hasItems(rootListener_2));
+        
+        eventSupport.addRegistryChangeListener(rootListener_2);
+        
+        assertEquals("Adding the same listener twice should not affect it's size", 2, eventSupport.getListenersByPath(Path.ROOT_PATH).size());        
+                
+    }
+    
+    @Test
+    public void testListenersPathReassignment(){
+        NodeChangeListener changeListener = createMock("nodeChangeListener", NodeChangeListener.class);
+        
+        NodeEventSupport eventSupport = new NodeEventSupport();
+        
+        Path menusPath = Path.fromString("/menus");
+        
+        eventSupport.addRegistryChangeListener(changeListener, Path.ROOT_PATH);
+        eventSupport.addRegistryChangeListener(changeListener, menusPath);
+        
+        assertThat(eventSupport.getListenersByPath(Path.ROOT_PATH), not(hasItem(changeListener)));
+        assertThat(eventSupport.getListenersByPath(menusPath), hasItem(changeListener));        
+        
+    }
+    
+    @Test
+    public void testRemovePathListener(){
+        NodeChangeListener listener_1 = createMock("listener_1", NodeChangeListener.class);
+        NodeChangeListener listener_2 = createMock("listener_2", NodeChangeListener.class);
+        
+        Map<NodeChangeListener, Path> listenersMap = new HashMap<>();
+        listenersMap.put(listener_1, Path.ROOT_PATH);
+        listenersMap.put(listener_2, Path.ROOT_PATH);
+        
+        NodeEventSupport eventSupport = new NodeEventSupport(listenersMap);
+        
+        eventSupport.removeRegistryChangeListener(listener_2);
+                
+        assertThat(eventSupport.getListenersByPath(Path.ROOT_PATH), allOf(hasItem(listener_1), not(hasItem(listener_2))));
+        
+        eventSupport.removeRegistryChangeListener(listener_2);
+        
+        assertThat("Removing non-existing listener must not have effect", 
+                eventSupport.getListenersByPath(Path.ROOT_PATH), allOf(hasItem(listener_1), not(hasItem(listener_2))));
+        
+        eventSupport.removeRegistryChangeListener(listener_1);
+        
+        assertThat("Removing non-existing listener must not have effect", 
+                eventSupport.getListenersByPath(Path.ROOT_PATH), not(hasItems(listener_1, listener_2)));
+        
+    }
+    
+    @Test
+    public void testEventFiring(){
+        NodeChangeListener l1 = createStrictMock("l1", NodeChangeListener.class);
+        NodeChangeListener l2 = createStrictMock("l2", NodeChangeListener.class);
+        NodeChangeListener l3 = createStrictMock("l3", NodeChangeListener.class);
+        
+        AbstractNode n1 = new AbstractNode("n1", null, 0) {};
+        AbstractNode n2 = new AbstractNode("n2", null, 0) {};
+        AbstractNode n3 = new AbstractNode("n3", null, 0) {};
+        
+        n1.addChild(n2);
+        n1.addChild(n3);
+        
+        Path p1 = Path.ROOT_PATH;
+        Path p2 = Path.fromString("/n2");
+        Path p3 = Path.fromString("/n3");
+        
+        NodeEventSupport eventSupport = new NodeEventSupport();
+        eventSupport.addRegistryChangeListener(l1, p1);
+        eventSupport.addRegistryChangeListener(l2, p2);
+        eventSupport.addRegistryChangeListener(l3, p3);
+        
+        l1.nodeAdded(anyObject(NodeChangeEvent.class));
+        l2.nodeAdded(anyObject(NodeChangeEvent.class));
+        
+        replay(l1, l2, l3);
+        
+        eventSupport.fireNodeAddedEvent(n2, p2);
+        
+        verify(l1, l2, l3);
+                
+    }
+    
+}

File registry/src/test/java/net/anatolich/registry/PathTest.java

     public void testRootPath(){
         assertEquals(Path.ROOT_PATH, Path.fromString("/"));
     }
+    
+    @Test
+    public void testIsSubpathOf(){
+        Path path = Path.fromString("/usr/share/icons/hicolor/scalable/apps");
+        
+        Path usrShare = Path.fromString("/usr/share");
+        
+        Path usrShareIcons = Path.fromString("/usr/share/icons");
+        
+        Path otherThemePath = Path.fromString("/usr/share/icons/gnome");
+        
+        assertTrue("Path is correct subpath of itself", path.isSubpathOf(path));
+        assertTrue(path.isSubpathOf(usrShare));
+        assertTrue(path.isSubpathOf(usrShareIcons));
+        assertFalse(path.isSubpathOf(otherThemePath));
+    }
 }

File registry/src/test/java/net/anatolich/registry/RegistryFixture.java

+/*
+ * Copyright 2012 Anatolich <anatolich@anatolich.net>.
+ *
+ * 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 net.anatolich.registry;
+
+import net.anatolich.registry.RegistryTest.MenuItemNode;
+import net.anatolich.registry.RegistryTest.MenuNode;
+
+/**
+ * Contains fixture for registry holding simple menu hierarchy.
+ * <pre>
+ * /
+ * -- menus
+ *   -- file
+ *     -- save
+ *     -- open
+ *   -- edit
+ *     -- cut
+ *     -- copy
+ * </pre>
+ *
+ * @author Anatolich <anatolich@anatolich.net>
+ */
+final class RegistryFixture {
+
+    private AbstractNode menusNode;
+    private AbstractNode fileMenuNode;
+    private AbstractNode fileOpenMenuItemNode;
+    private AbstractNode fileSaveMenuItemNode;
+    private AbstractNode editMenuNode;
+    private AbstractNode editCutMenuItemNode;
+    private AbstractNode editCopyMenuItemNode;
+    private Path menuPath;
+    private Path fileMenuPath;
+    private Path editMenuPath;
+    private Path fileOpenMenuItemPath;
+    private Path editCutMenuItemPath;
+    private Path fileSaveMenuItemPath;
+    private Path editCopyMenuItemPath;
+
+    public RegistryFixture() {
+
+        fileSaveMenuItemNode = new MenuItemNode("save", 100);
+        fileOpenMenuItemNode = new MenuItemNode("open", 200);
+        fileMenuNode = new MenuNode("file", 100);
+
+        fileMenuNode.addChild(fileOpenMenuItemNode);
+        fileMenuNode.addChild(fileSaveMenuItemNode);
+
+        editCutMenuItemNode = new MenuItemNode("cut", 100);
+        editCopyMenuItemNode = new MenuItemNode("copy", 200);
+        editMenuNode = new MenuNode("edit", 200);
+
+        editMenuNode.addChild(editCutMenuItemNode);
+        editMenuNode.addChild(editCopyMenuItemNode);
+        menusNode = new MenuNode("menu", 0);
+
+        menusNode.addChild(fileMenuNode);
+        menusNode.addChild(editMenuNode);
+        
+        menuPath = Path.fromString("/menu");
+        fileMenuPath = Path.fromString("/menu/file");
+        fileSaveMenuItemPath = Path.fromString("/menu/file/save");
+        fileOpenMenuItemPath = Path.fromString("/menu/file/open");
+        
+        editMenuPath = Path.fromString("/menu/edit");
+        editCutMenuItemPath = Path.fromString("/menu/edit/cut");
+        editCopyMenuItemPath = Path.fromString("/menu/edit/copy");
+    }
+
+    /**
+     * @return the editMenuNode
+     */
+    public AbstractNode getEditMenuNode() {
+        return editMenuNode;
+    }
+
+    /**
+     * @return the editCopyMenuItemNode
+     */
+    public AbstractNode getEditCopyMenuItemNode() {
+        return editCopyMenuItemNode;
+    }
+
+    /**
+     * @return the fileSaveMenuItemNode
+     */
+    public AbstractNode getFileSaveMenuItemNode() {
+        return fileSaveMenuItemNode;
+    }
+
+    /**
+     * @return the fileMenuNode
+     */
+    public AbstractNode getFileMenuNode() {
+        return fileMenuNode;
+    }
+
+    /**
+     * @return the fileOpenMenuItemNode
+     */
+    public AbstractNode getFileOpenMenuItemNode() {
+        return fileOpenMenuItemNode;
+    }
+
+    /**
+     * @return the menusNode
+     */
+    public AbstractNode getMenusNode() {
+        return menusNode;
+    }
+
+    /**
+     * @return the editCutMenuItemNode
+     */
+    public AbstractNode getEditCutMenuItemNode() {
+        return editCutMenuItemNode;
+    }
+
+    public Path getMenuPath() {
+        return menuPath;
+    }
+
+    public Path getFileMenuPath() {
+        return fileMenuPath;
+    }
+
+    public Path getEditMenuPath() {
+        return editMenuPath;
+    }
+
+    public Path getFileOpenMenuItemPath() {
+        return fileOpenMenuItemPath;
+    }
+
+    public Path getEditCutMenuItemPath() {
+        return editCutMenuItemPath;
+    }
+
+    public Path getFileSaveMenuItemPath() {
+        return fileSaveMenuItemPath;
+    }
+
+    public Path getEditCopyMenuItemPath() {
+        return editCopyMenuItemPath;
+    }
+}

File registry/src/test/java/net/anatolich/registry/RegistryTest.java

 
     @Test
     public void testNotificationFiringOnAddingNode() {
+        RegistryFixture fixture = new RegistryFixture();
+        
         NodeEventSupport eventSupport = createMock(NodeEventSupport.class);
         Registry reg = new Registry(eventSupport);
 
+        AbstractNode menuNode = fixture.getMenusNode();
+        AbstractNode fileMenuNode = fixture.getFileMenuNode();
         AbstractNode addedNode = new MenuItemNode("new", 0);
+        Path menuPath = fixture.getMenuPath();
+        Path addedNodePath = Path.fromString("/menu/file/new");
 
-        eventSupport.fireNodeAddedEvent(defaultRegistryFixture.getMenusNode());
-        eventSupport.fireNodeAddedEvent(addedNode);
-        expectLastCall().anyTimes();
+        eventSupport.fireNodeAddedEvent(menuNode, menuPath);
+        eventSupport.fireNodeAddedEvent(addedNode, addedNodePath);
+        
         replay(eventSupport);
 
-        reg.rootNode().addChild(defaultRegistryFixture.getMenusNode());
-        defaultRegistryFixture.getFileMenuNode().addChild(addedNode);
+        reg.rootNode().addChild(menuNode);
+        fileMenuNode.addChild(addedNode);
 
         verify(eventSupport);
     }
     @Test
     public void testNotificationFiringOnRemovingNode() {
         NodeEventSupport eventSupport = createMock(NodeEventSupport.class);
+        RegistryFixture fixture = new RegistryFixture();
+        
         Registry reg = new Registry(eventSupport);
 
         AbstractNode addedNode = new MenuItemNode("new", 0);
 
-        eventSupport.fireNodeAddedEvent(anyObject(AbstractNode.class));
+        eventSupport.fireNodeAddedEvent(anyObject(AbstractNode.class), anyObject(Path.class));
         expectLastCall().anyTimes();
 
-        eventSupport.fireNodeRemovedEvent(addedNode);
+        eventSupport.fireNodeRemovedEvent(addedNode, null);
 
         replay(eventSupport);
 
-        reg.rootNode().addChild(defaultRegistryFixture.getMenusNode());
-        defaultRegistryFixture.getFileMenuNode().addChild(addedNode);
+        reg.rootNode().addChild(fixture.getMenusNode());
+        fixture.getFileMenuNode().addChild(addedNode);
 
-        defaultRegistryFixture.getFileMenuNode().removeChild(addedNode);
+        fixture.getFileMenuNode().removeChild(addedNode);
 
         verify(eventSupport);
     }