From ca2f1ab4a37f0d590d5c116cd1fa7c341c77fad3 Mon Sep 17 00:00:00 2001
From: Marcin Chrzanowski <marcin.j.chrzanowski@gmail.com>
Date: Mon, 30 Dec 2019 22:16:46 +0100
Subject: Implement modular getZoneSet

---
 .../java/pl/edu/mimuw/cloudatlas/agent/Agent.java  |  31 +++-
 .../pl/edu/mimuw/cloudatlas/agent/EventBus.java    |   4 +
 .../cloudatlas/agent/NewApiImplementation.java     | 178 +++++++++++++++++++++
 .../cloudatlas/agent/messages/RMIMessage.java      |  14 --
 .../cloudatlas/agent/messages/RemikMessage.java    |  25 +++
 .../agent/messages/RequestStateMessage.java        |  16 ++
 .../edu/mimuw/cloudatlas/agent/modules/Module.java |   6 +-
 .../pl/edu/mimuw/cloudatlas/agent/modules/RMI.java |  37 -----
 .../edu/mimuw/cloudatlas/agent/modules/Remik.java  |  54 +++++++
 .../java/pl/edu/mimuw/cloudatlas/model/ZMI.java    |   8 +-
 .../java/pl/edu/mimuw/cloudatlas/Container.java    |   5 +
 .../cloudatlas/agent/ApiImplementationTests.java   |   1 -
 .../edu/mimuw/cloudatlas/agent/MockEventBus.java   |  17 ++
 .../agent/NewApiImplementationTests.java           | 171 ++++++++++++++++++++
 14 files changed, 507 insertions(+), 60 deletions(-)
 create mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementation.java
 delete mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RMIMessage.java
 create mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RemikMessage.java
 create mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RequestStateMessage.java
 delete mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/RMI.java
 create mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Remik.java
 create mode 100644 src/test/java/pl/edu/mimuw/cloudatlas/Container.java
 create mode 100644 src/test/java/pl/edu/mimuw/cloudatlas/agent/MockEventBus.java
 create mode 100644 src/test/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementationTests.java

(limited to 'src')

diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/Agent.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/Agent.java
index 392e0a1..52bd395 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/Agent.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/Agent.java
@@ -1,22 +1,45 @@
 package pl.edu.mimuw.cloudatlas.agent;
 
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
+import pl.edu.mimuw.cloudatlas.agent.ApiImplementation;
 import pl.edu.mimuw.cloudatlas.agent.modules.Module;
 import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
 import pl.edu.mimuw.cloudatlas.agent.modules.Qurnik;
-import pl.edu.mimuw.cloudatlas.agent.modules.RMI;
+import pl.edu.mimuw.cloudatlas.agent.modules.Remik;
 import pl.edu.mimuw.cloudatlas.agent.modules.Stanik;
 import pl.edu.mimuw.cloudatlas.agent.modules.TimerScheduler;
+import pl.edu.mimuw.cloudatlas.api.Api;
+import pl.edu.mimuw.cloudatlas.interpreter.Main;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
 
 public class Agent {
+    private static EventBus eventBus;
+
+    public static void runRegistry() {
+        try {
+            ZMI root = Main.createTestHierarchy2();
+            ApiImplementation api = new ApiImplementation(root);
+            Api apiStub =
+                    (Api) UnicastRemoteObject.exportObject(api, 0);
+            Registry registry = LocateRegistry.getRegistry();
+            registry.rebind("Api", apiStub);
+            System.out.println("Agent: api bound");
+        } catch (Exception e) {
+            System.err.println("Agent registry initialization exception:");
+            e.printStackTrace();
+        }
+    }
 
     public static HashMap<ModuleType, Module> initializeModules() {
         HashMap<ModuleType, Module> modules = new HashMap<ModuleType, Module>();
         modules.put(ModuleType.TIMER_SCHEDULER, new TimerScheduler(ModuleType.TIMER_SCHEDULER));
-        modules.put(ModuleType.RMI, new RMI(ModuleType.RMI));
+        modules.put(ModuleType.RMI, new Remik());
         modules.put(ModuleType.STATE, new Stanik());
         modules.put(ModuleType.QUERY, new Qurnik());
         // TODO add modules as we implement them
@@ -61,7 +84,8 @@ public class Agent {
         HashMap<ModuleType, Executor> executors = initializeExecutors(modules);
         ArrayList<Thread> executorThreads = initializeExecutorThreads(executors);
 
-        Thread eventBusThread = new Thread(new EventBus(executors));
+        eventBus = new EventBus(executors);
+        Thread eventBusThread = new Thread(eventBus);
         System.out.println("Initializing event bus");
         eventBusThread.start();
 
@@ -71,5 +95,6 @@ public class Agent {
 
     public static void main(String[] args) {
         runModulesAsThreads();
+        runRegistry();
     }
 }
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/EventBus.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/EventBus.java
index 5b09253..a6d3b2d 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/EventBus.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/EventBus.java
@@ -20,6 +20,10 @@ public class EventBus implements Runnable {
         }
     }
 
+    // Allows for testing with a mock EventBus
+    protected EventBus() {
+    }
+
     EventBus(HashMap<ModuleType, Executor> executors) {
         this.executors = executors;
         setEventBusReference();
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementation.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementation.java
new file mode 100644
index 0000000..4aa5148
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementation.java
@@ -0,0 +1,178 @@
+package pl.edu.mimuw.cloudatlas.agent;
+
+import java.io.PrintStream;
+
+import java.rmi.RemoteException;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.List;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+
+import pl.edu.mimuw.cloudatlas.agent.messages.RequestStateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.ResponseMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.StateMessage;
+import pl.edu.mimuw.cloudatlas.interpreter.Interpreter;
+import pl.edu.mimuw.cloudatlas.interpreter.InterpreterException;
+import pl.edu.mimuw.cloudatlas.interpreter.Main;
+import pl.edu.mimuw.cloudatlas.interpreter.QueryResult;
+import pl.edu.mimuw.cloudatlas.model.Attribute;
+import pl.edu.mimuw.cloudatlas.model.AttributesMap;
+import pl.edu.mimuw.cloudatlas.model.PathName;
+import pl.edu.mimuw.cloudatlas.model.ValueContact;
+import pl.edu.mimuw.cloudatlas.model.Value;
+import pl.edu.mimuw.cloudatlas.model.ValueQuery;
+import pl.edu.mimuw.cloudatlas.model.ValueSet;
+import pl.edu.mimuw.cloudatlas.model.ValueNull;
+import pl.edu.mimuw.cloudatlas.model.Type;
+import pl.edu.mimuw.cloudatlas.model.TypePrimitive;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
+import pl.edu.mimuw.cloudatlas.api.Api;
+
+public class NewApiImplementation implements Api {
+    private EventBus eventBus;
+
+    public NewApiImplementation(EventBus eventBus) {
+        this.eventBus = eventBus;
+    }
+
+    public Set<String> getZoneSet() throws RemoteException {
+        CompletableFuture<ResponseMessage> responseFuture = new CompletableFuture();
+        RequestStateMessage message = new RequestStateMessage("", 0, responseFuture);
+        try {
+            eventBus.addMessage(message);
+            ResponseMessage response = responseFuture.get();
+
+            if (response.getType() == ResponseMessage.Type.STATE) {
+                StateMessage stateMessage = (StateMessage) response;
+                Set<String> zones = new HashSet<String>();
+                collectZoneNames(stateMessage.getZMI(), zones);
+                return zones;
+            } else {
+                System.out.println("ERROR: getZoneSet didn't receive a StateMessage");
+                throw new Exception("Failed to retrieve zone set");
+            }
+        } catch (Exception e) {
+            System.out.println("ERROR: exception caught in getZoneSet");
+            throw new RemoteException(e.getMessage());
+        }
+    }
+
+    private void collectZoneNames(ZMI zone, Set<String> names) {
+        names.add(zone.getPathName().toString());
+        for (ZMI son : zone.getSons()) {
+            collectZoneNames(son, names);
+        }
+    }
+
+    public AttributesMap getZoneAttributeValues(String zoneName) throws RemoteException {
+        /*
+        try {
+            ZMI zmi = root.findDescendant(new PathName(zoneName));
+            return zmi.getAttributes();
+        } catch (ZMI.NoSuchZoneException e) {
+            throw new RemoteException("Zone not found", e);
+        }
+        */
+
+        // placeholder
+        return new AttributesMap();
+    }
+
+    public void installQuery(String name, String queryCode) throws RemoteException {
+        /*
+        Pattern queryNamePattern = Pattern.compile("&[a-zA-Z][\\w_]*");
+        Matcher matcher = queryNamePattern.matcher(name);
+        if (!matcher.matches()) {
+            throw new RemoteException("Invalid query identifier");
+        }
+        try {
+            ValueQuery query = new ValueQuery(queryCode);
+            Attribute attributeName = new Attribute(name);
+            installQueryInHierarchy(root, attributeName, query);
+            executeAllQueries(root);
+        } catch (Exception e) {
+            throw new RemoteException("Failed to install query", e);
+        }
+        */
+    }
+
+    private void installQueryInHierarchy(ZMI zmi, Attribute queryName, ValueQuery query) {
+        /*
+        if (!zmi.getSons().isEmpty()) {
+            zmi.getAttributes().addOrChange(queryName, query);
+            for (ZMI son : zmi.getSons()) {
+                installQueryInHierarchy(son, queryName, query);
+            }
+        }
+        */
+    }
+
+    public void uninstallQuery(String queryName) throws RemoteException {
+        // uninstallQueryInHierarchy(root, new Attribute(queryName));
+    }
+
+    private void uninstallQueryInHierarchy(ZMI zmi, Attribute queryName) {
+        /*
+        if (!zmi.getSons().isEmpty()) {
+            zmi.getAttributes().remove(queryName);
+            for (ZMI son : zmi.getSons()) {
+                uninstallQueryInHierarchy(son, queryName);
+            }
+        }
+        */
+    }
+
+    public void setAttributeValue(String zoneName, String attributeName, Value value) throws RemoteException {
+        /*
+        try {
+            ZMI zmi = root.findDescendant(new PathName(zoneName));
+            zmi.getAttributes().addOrChange(new Attribute(attributeName), value);
+            executeAllQueries(root);
+        } catch (ZMI.NoSuchZoneException e) {
+            throw new RemoteException("Zone not found", e);
+        }
+        */
+    }
+
+    private void executeAllQueries(ZMI zmi) {
+        /*
+        if(!zmi.getSons().isEmpty()) {
+            for(ZMI son : zmi.getSons()) {
+                executeAllQueries(son);
+            }
+
+            Interpreter interpreter = new Interpreter(zmi);
+            for (ValueQuery query : getQueries(zmi)) {
+                try {
+                    List<QueryResult> result = interpreter.interpretProgram(query.getQuery());
+                    for(QueryResult r : result) {
+                        zmi.getAttributes().addOrChange(r.getName(), r.getValue());
+                    }
+                } catch(InterpreterException exception) {}
+            }
+        }
+        */
+    }
+
+    private Set<ValueQuery> getQueries(ZMI zmi) {
+        Set<ValueQuery> querySet = new HashSet<ValueQuery>();
+        /*
+        for (Map.Entry<Attribute, Value> attribute : zmi.getAttributes()) {
+            if (attribute.getValue().getType().getPrimaryType() == Type.PrimaryType.QUERY) {
+                querySet.add((ValueQuery) attribute.getValue());
+            }
+        }
+        */
+
+        return querySet;
+    }
+
+    public void setFallbackContacts(Set<ValueContact> contacts) throws RemoteException {
+        // this.contacts = contacts;
+    }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RMIMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RMIMessage.java
deleted file mode 100644
index a8e3afb..0000000
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RMIMessage.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package pl.edu.mimuw.cloudatlas.agent.messages;
-
-import pl.edu.mimuw.cloudatlas.agent.modules.Module;
-import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
-
-public class RMIMessage extends AgentMessage {
-    public RMIMessage(String messageId, long timestamp) {
-        super(messageId, ModuleType.RMI, timestamp);
-    }
-
-    public void callMe(Module module) throws InterruptedException, Module.InvalidMessageType {
-        module.handleTyped(this);
-    }
-}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RemikMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RemikMessage.java
new file mode 100644
index 0000000..b0300cb
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RemikMessage.java
@@ -0,0 +1,25 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import pl.edu.mimuw.cloudatlas.agent.modules.Module;
+import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
+
+public abstract class RemikMessage extends AgentMessage {
+    public enum Type {
+        REQUEST_STATE
+    }
+
+    private Type type;
+
+    public RemikMessage(String messageId, long timestamp, Type type) {
+        super(messageId, ModuleType.RMI, timestamp);
+        this.type = type;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public void callMe(Module module) throws InterruptedException, Module.InvalidMessageType {
+        module.handleTyped(this);
+    }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RequestStateMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RequestStateMessage.java
new file mode 100644
index 0000000..698aac7
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/RequestStateMessage.java
@@ -0,0 +1,16 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import java.util.concurrent.CompletableFuture;
+
+public class RequestStateMessage extends RemikMessage {
+    CompletableFuture<ResponseMessage> responseFuture;
+
+    public RequestStateMessage(String messageId, long timestamp, CompletableFuture<ResponseMessage> responseFuture) {
+        super(messageId, timestamp, Type.REQUEST_STATE);
+        this.responseFuture = responseFuture;
+    }
+
+    public CompletableFuture<ResponseMessage> getFuture() {
+        return responseFuture;
+    }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java
index ba5e1d1..0a934cb 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java
@@ -4,8 +4,8 @@ import pl.edu.mimuw.cloudatlas.agent.Executor;
 import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.TimerSchedulerMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.QurnikMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.RemikMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.ResponseMessage;
-import pl.edu.mimuw.cloudatlas.agent.messages.RMIMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.StanikMessage;
 
 /*
@@ -37,8 +37,8 @@ public abstract class Module {
         throw new InvalidMessageType("Got a QurnikMessage in module " + moduleType.toString());
     }
 
-    public void handleTyped(RMIMessage message) throws InterruptedException, InvalidMessageType {
-        throw new InvalidMessageType("Got an RMIMessage in module " + moduleType.toString());
+    public void handleTyped(RemikMessage message) throws InterruptedException, InvalidMessageType {
+        throw new InvalidMessageType("Got a RemikMessage in module " + moduleType.toString());
     }
 
     public void handleTyped(StanikMessage message) throws InterruptedException, InvalidMessageType {
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/RMI.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/RMI.java
deleted file mode 100644
index 1a86fc7..0000000
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/RMI.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package pl.edu.mimuw.cloudatlas.agent.modules;
-
-import pl.edu.mimuw.cloudatlas.agent.ApiImplementation;
-import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
-import pl.edu.mimuw.cloudatlas.agent.messages.RMIMessage;
-import pl.edu.mimuw.cloudatlas.api.Api;
-import pl.edu.mimuw.cloudatlas.interpreter.Main;
-import pl.edu.mimuw.cloudatlas.model.ZMI;
-
-import java.rmi.registry.LocateRegistry;
-import java.rmi.registry.Registry;
-import java.rmi.server.UnicastRemoteObject;
-
-public class RMI extends Module {
-    public RMI(ModuleType moduleType) {
-        super(moduleType);
-        runRegistry();
-    }
-
-    public void runRegistry() {
-        try {
-            ZMI root = Main.createTestHierarchy2();
-            ApiImplementation api = new ApiImplementation(root);
-            Api apiStub =
-                    (Api) UnicastRemoteObject.exportObject(api, 0);
-            Registry registry = LocateRegistry.getRegistry();
-            registry.rebind("Api", apiStub);
-            System.out.println("Agent: api bound");
-        } catch (Exception e) {
-            System.err.println("Agent registry initialization exception:");
-            e.printStackTrace();
-        }
-    }
-
-    public void handleTyped(RMIMessage event) throws InterruptedException {
-    }
-}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Remik.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Remik.java
new file mode 100644
index 0000000..9645d71
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Remik.java
@@ -0,0 +1,54 @@
+package pl.edu.mimuw.cloudatlas.agent.modules;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.HashMap;
+import java.util.Map;
+
+
+import pl.edu.mimuw.cloudatlas.agent.messages.GetStateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.RemikMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.RequestStateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.ResponseMessage;
+
+/*
+ * Remik is a cute little module that allows RMI functions to interface with
+ * agent's asynchronous modules.
+ */
+public class Remik extends Module {
+    private Map<Long, CompletableFuture<ResponseMessage>> awaitingRequests;
+    private long nextRequestId = 0;
+
+    public Remik() {
+        super(ModuleType.RMI);
+        awaitingRequests = new HashMap();
+    }
+
+    public void handleTyped(RemikMessage message) throws InvalidMessageType, InterruptedException {
+        switch (message.getType()) {
+            case REQUEST_STATE:
+                handleRequestState((RequestStateMessage) message);
+                break;
+            default:
+                throw new InvalidMessageType("This type of message cannot be handled by Remik");
+        }
+    }
+
+    public void handleTyped(ResponseMessage message) {
+        CompletableFuture<ResponseMessage> responseFuture = awaitingRequests.get(message.getRequestId());
+
+        if (responseFuture == null) {
+            System.out.println("ERROR: Remik got response for nonexistent/finished request");
+        } else {
+            responseFuture.complete(message);
+        }
+    }
+
+    private void handleRequestState(RequestStateMessage message) throws InterruptedException {
+        awaitingRequests.put(nextRequestId, message.getFuture());
+
+        GetStateMessage getStateMessage = new GetStateMessage("", 0, ModuleType.RMI, nextRequestId);
+        nextRequestId++;
+
+        sendMessage(getStateMessage);
+    }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
index 54fbf43..cda0296 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
@@ -219,8 +219,12 @@ public class ZMI implements Cloneable, Serializable {
      * @return a <code>PathName</code> object representing this zone
      */
     public PathName getPathName() {
-        String name = ((ValueString)getAttributes().get("name")).getValue();
-        return getFather() == null? PathName.ROOT : getFather().getPathName().levelDown(name);
+        if (getFather() == null) {
+            return PathName.ROOT;
+        } else {
+            String name = ((ValueString)getAttributes().get("name")).getValue();
+            return getFather().getPathName().levelDown(name);
+        }
     }
 
     public static ZMI deserialize(InputStream in) {
diff --git a/src/test/java/pl/edu/mimuw/cloudatlas/Container.java b/src/test/java/pl/edu/mimuw/cloudatlas/Container.java
new file mode 100644
index 0000000..db5a156
--- /dev/null
+++ b/src/test/java/pl/edu/mimuw/cloudatlas/Container.java
@@ -0,0 +1,5 @@
+package pl.edu.mimuw.cloudatlas;
+
+public class Container<T> {
+    public T thing;
+}
diff --git a/src/test/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementationTests.java b/src/test/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementationTests.java
index c964ed9..0a7a8df 100644
--- a/src/test/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementationTests.java
+++ b/src/test/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementationTests.java
@@ -3,7 +3,6 @@ package pl.edu.mimuw.cloudatlas.agent;
 import org.junit.Before;
 import org.junit.Test;
 import static org.junit.Assert.*;
-import static org.junit.Assert.assertThat;
 import static org.hamcrest.CoreMatchers.hasItems;
 
 import java.util.ArrayList;
diff --git a/src/test/java/pl/edu/mimuw/cloudatlas/agent/MockEventBus.java b/src/test/java/pl/edu/mimuw/cloudatlas/agent/MockEventBus.java
new file mode 100644
index 0000000..e8f8bfa
--- /dev/null
+++ b/src/test/java/pl/edu/mimuw/cloudatlas/agent/MockEventBus.java
@@ -0,0 +1,17 @@
+package pl.edu.mimuw.cloudatlas.agent;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
+
+public class MockEventBus extends EventBus {
+    public LinkedBlockingQueue<AgentMessage> events;
+
+    public MockEventBus() {
+        events = new LinkedBlockingQueue<AgentMessage>();
+    }
+
+    public void addMessage(AgentMessage msg) throws InterruptedException {
+        events.put(msg);
+    }
+}
diff --git a/src/test/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementationTests.java b/src/test/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementationTests.java
new file mode 100644
index 0000000..a3b57a9
--- /dev/null
+++ b/src/test/java/pl/edu/mimuw/cloudatlas/agent/NewApiImplementationTests.java
@@ -0,0 +1,171 @@
+package pl.edu.mimuw.cloudatlas.agent;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.hasItems;
+
+import java.util.concurrent.TimeUnit;
+import java.util.HashSet;
+import java.util.Set;
+
+import pl.edu.mimuw.cloudatlas.Container;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
+import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
+import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.RequestStateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.StateMessage;
+
+public class NewApiImplementationTests {
+    private NewApiImplementation api;
+    private MockEventBus eventBus;
+
+    @Before
+    public void initializeApi() throws Exception {
+        eventBus = new MockEventBus();
+        api = new NewApiImplementation(eventBus);
+    }
+
+    @Test
+    public void testGetZoneSet() throws Exception {
+        final Set<String> zoneSet = new HashSet();
+        final Container<Exception> exceptionContainer = new Container();
+        Thread apiThread = new Thread(() -> {
+            try {
+                zoneSet.addAll(api.getZoneSet());
+            } catch (Exception e) {
+                exceptionContainer.thing = e;
+            }
+        });
+        apiThread.start();
+
+        AgentMessage message = eventBus.events.poll(100, TimeUnit.MILLISECONDS);
+        assertNotNull(message);
+        assertEquals(ModuleType.RMI, message.getDestinationModule());
+        RequestStateMessage requestMessage = (RequestStateMessage) message;
+
+        ZMI root = new ZMI();
+        StateMessage responseMessage = new StateMessage("", ModuleType.RMI, 0, 0, root, null);
+
+        requestMessage.getFuture().complete(responseMessage);
+
+        apiThread.join(100);
+        assertFalse(apiThread.isAlive());
+        assertNull(exceptionContainer.thing);
+
+        assertThat(zoneSet, hasItems("/"));
+    }
+
+    /*
+    @Test
+    public void testRootGetZoneAttributeValue() throws Exception {
+        AttributesMap rootAttributes = api.getZoneAttributeValues("/");
+        assertEquals(new ValueInt(0l), rootAttributes.get("level"));
+        assertEquals(ValueNull.getInstance(), rootAttributes.get("name"));
+    }
+
+    @Test
+    public void testIntermediateGetZoneAttributeValue() throws Exception {
+        AttributesMap attributes = api.getZoneAttributeValues("/uw");
+        assertEquals(new ValueInt(1l), attributes.get("level"));
+        assertEquals(new ValueString("uw"), attributes.get("name"));
+    }
+
+    @Test
+    public void testLeafGetZoneAttributeValue() throws Exception {
+        AttributesMap attributes = api.getZoneAttributeValues("/pjwstk/whatever01");
+        assertEquals(new ValueInt(2l), attributes.get("level"));
+        assertEquals(new ValueString("whatever01"), attributes.get("name"));
+        assertEquals(new ValueString("/pjwstk/whatever01"), attributes.get("owner"));
+        assertEquals(new ValueTime("2012/11/09 21:12:00.000"), attributes.get("timestamp"));
+        assertEquals(new ValueInt(1l), attributes.get("cardinality"));
+        assertEquals(new ValueTime("2012/10/18 07:03:00.000"), attributes.get("creation"));
+        assertEquals(new ValueDouble(0.1), attributes.get("cpu_usage"));
+        assertEquals(new ValueInt(7l), attributes.get("num_cores"));
+        assertEquals(new ValueInt(215l), attributes.get("num_processes"));
+
+        List<Value> phpModules = new ArrayList<Value>();
+        phpModules.add(new ValueString("rewrite"));
+        assertEquals(new ValueList(phpModules, TypePrimitive.STRING), attributes.get("php_modules"));
+    }
+
+    @Test
+    public void testInstallQuery() throws Exception {
+        String name = "&query";
+        String queryCode = "SELECT 1 AS one";
+        api.installQuery(name, queryCode);
+        assertAttributeInZmiEquals(name, new ValueQuery(queryCode), "/");
+        assertAttributeInZmiEquals(name, new ValueQuery(queryCode), "/uw");
+        assertAttributeInZmiEquals(name, new ValueQuery(queryCode), "/pjwstk");
+    }
+
+    @Test
+    public void testInstallQueryRuns() throws Exception {
+        api.installQuery("&query", "SELECT 1 AS one");
+        assertAttributeInZmiEquals("one", new ValueInt(1l), "/");
+        assertAttributeInZmiEquals("one", new ValueInt(1l), "/uw");
+        assertAttributeInZmiEquals("one", new ValueInt(1l), "/pjwstk");
+    }
+
+    @Test
+    public void testInstallQueryRuns2() throws Exception {
+        api.installQuery("&query", "SELECT sum(num_processes) AS num_processes");
+        assertAttributeInZmiEquals("num_processes", new ValueInt(362l), "/uw");
+        assertAttributeInZmiEquals("num_processes", new ValueInt(437l), "/pjwstk");
+        assertAttributeInZmiEquals("num_processes", new ValueInt(799l), "/");
+    }
+
+    @Test
+    public void testInstallQueryWithInvalidNameFails() throws Exception {
+        String name = "query";
+        String queryCode = "SELECT 1 AS one";
+        try {
+            api.installQuery(name, queryCode);
+            assertTrue("should have thrown", false);
+        } catch (Exception e) {
+            assertEquals("Invalid query identifier", e.getMessage());
+        }
+    }
+
+    public void assertAttributeInZmiEquals(String attribute, Value expected, String zmiPath) throws Exception {
+        AttributesMap attributes = api.getZoneAttributeValues(zmiPath);
+        assertEquals(expected, attributes.get(attribute));
+    }
+
+    @Test
+    public void testUninstallQuery() throws Exception {
+        String name = "&query";
+        String queryCode = "SELECT 1 AS one";
+        api.installQuery(name, queryCode);
+        api.uninstallQuery(name);
+        AttributesMap attributes = api.getZoneAttributeValues("/pjwstk");
+        assertNull(attributes.getOrNull(name));
+    }
+
+    @Test
+    public void testSetAttributeValueChange() throws Exception {
+        Value numProcesses = new ValueInt(42l);
+        api.setAttributeValue("/uw/khaki13", "num_processes", numProcesses);
+        AttributesMap attributes = api.getZoneAttributeValues("/uw/khaki13");
+        assertEquals(numProcesses, attributes.get("num_processes"));
+    }
+
+    @Test
+    public void testSetAttributeValueAdd() throws Exception {
+        Value numProcesses = new ValueInt(42l);
+        api.setAttributeValue("/uw/khaki13", "an_attribute", numProcesses);
+        AttributesMap attributes = api.getZoneAttributeValues("/uw/khaki13");
+        assertEquals(numProcesses, attributes.get("an_attribute"));
+    }
+
+    @Test
+    public void testSetAttributeValueRunsQueries() throws Exception {
+        api.installQuery("&query", "SELECT sum(num_processes) AS num_processes");
+        Value numProcesses = new ValueInt(42l);
+        api.setAttributeValue("/uw/khaki13", "num_processes", numProcesses);
+        assertAttributeInZmiEquals("num_processes", new ValueInt(297l), "/uw");
+        assertAttributeInZmiEquals("num_processes", new ValueInt(437l), "/pjwstk");
+        assertAttributeInZmiEquals("num_processes", new ValueInt(734l), "/");
+    }
+    */
+}
-- 
cgit v1.2.3