From e742bf9b8d1bc5ee7a97586510643db6fd3174f2 Mon Sep 17 00:00:00 2001
From: Martin <marcin.j.chrzanowski@gmail.com>
Date: Mon, 18 Nov 2019 14:53:44 +0100
Subject: Implement basic API (#15)

---
 .../java/pl/edu/mimuw/cloudatlas/agent/Agent.java  |  5 +-
 .../mimuw/cloudatlas/agent/ApiImplementation.java  | 71 ++++++++++++++++++++--
 src/main/java/pl/edu/mimuw/cloudatlas/api/Api.java |  6 +-
 .../pl/edu/mimuw/cloudatlas/interpreter/Main.java  | 10 +--
 .../pl/edu/mimuw/cloudatlas/model/Attribute.java   |  4 +-
 .../edu/mimuw/cloudatlas/model/AttributesMap.java  |  3 +-
 .../java/pl/edu/mimuw/cloudatlas/model/Type.java   |  6 +-
 .../edu/mimuw/cloudatlas/model/TypePrimitive.java  |  6 ++
 .../java/pl/edu/mimuw/cloudatlas/model/Value.java  |  4 +-
 .../edu/mimuw/cloudatlas/model/ValueDuration.java  |  5 +-
 .../pl/edu/mimuw/cloudatlas/model/ValueList.java   |  1 +
 .../pl/edu/mimuw/cloudatlas/model/ValueQuery.java  | 70 +++++++++++++++++++++
 .../java/pl/edu/mimuw/cloudatlas/model/ZMI.java    | 35 +++++++++++
 13 files changed, 202 insertions(+), 24 deletions(-)
 create mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/model/ValueQuery.java

(limited to 'src/main/java/pl/edu')

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 95ede6e..8eb8b4f 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/Agent.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/Agent.java
@@ -6,11 +6,14 @@ import java.rmi.registry.Registry;
 import java.rmi.server.UnicastRemoteObject;
 
 import pl.edu.mimuw.cloudatlas.api.Api;
+import pl.edu.mimuw.cloudatlas.interpreter.Main;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
 
 public class Agent {
     public static void main(String[] args) {
         try {
-            ApiImplementation api = new ApiImplementation();
+            ZMI root = Main.createTestHierarchy2();
+            ApiImplementation api = new ApiImplementation(root);
             Api apiStub =
                 (Api) UnicastRemoteObject.exportObject(api, 0);
             Registry registry = LocateRegistry.getRegistry();
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementation.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementation.java
index fd52052..12682a6 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementation.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/ApiImplementation.java
@@ -5,32 +5,91 @@ import java.rmi.RemoteException;
 import java.util.Set;
 import java.util.HashSet;
 
+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.TypePrimitive;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
 import pl.edu.mimuw.cloudatlas.api.Api;
 
 public class ApiImplementation implements Api {
+    ZMI root;
+    Set<ValueContact> contacts;
+
+    public ApiImplementation(ZMI root) {
+        this.root = root;
+        this.contacts = new HashSet<ValueContact>();
+    }
+
     public Set<String> getZoneSet() throws RemoteException {
-        return null;
+        Set<String> zones = new HashSet<String>();
+        collectZoneNames(root, zones);
+        return zones;
+    }
+
+    private void collectZoneNames(ZMI zone, Set<String> names) {
+        names.add(zone.getPathName().toString());
+        for (ZMI son : zone.getSons()) {
+            collectZoneNames(son, names);
+        }
     }
 
-    public AttributesMap getZoneAttributeValue(String zoneName) throws RemoteException {
-        return null;
+    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);
+        }
     }
 
-    public void installQuery(String queryName, String query) throws RemoteException {
+    public void installQuery(String name, String queryCode) throws RemoteException {
+        try {
+            ValueQuery query = new ValueQuery(queryCode);
+            Attribute attributeName = new Attribute(name);
+            installQueryInHierarchy(root, attributeName, query);
+        } 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 attributeName, Value value) throws RemoteException {
+    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);
+        } catch (ZMI.NoSuchZoneException e) {
+            throw new RemoteException("Zone not found", e);
+        }
     }
 
-    public void setFallbackContacts(Set<ValueContact> serializedContacts) throws RemoteException {
+    public void setFallbackContacts(Set<ValueContact> contacts) throws RemoteException {
+        this.contacts = contacts;
     }
 }
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/api/Api.java b/src/main/java/pl/edu/mimuw/cloudatlas/api/Api.java
index c5a4581..c62ee39 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/api/Api.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/api/Api.java
@@ -25,14 +25,14 @@ public interface Api extends Remote {
 
     public Set<String> getZoneSet() throws RemoteException;
 
-    public AttributesMap getZoneAttributeValue(String zoneName) throws RemoteException;
+    public AttributesMap getZoneAttributeValues(String zoneName) throws RemoteException;
 
     public void installQuery(String queryName, String query) throws RemoteException;
 
     public void uninstallQuery(String queryName) throws RemoteException;
 
-    public void setAttributeValue(String attributeName, Value value) throws RemoteException;
+    public void setAttributeValue(String zoneName, String attributeName, Value value) throws RemoteException;
 
-    public void setFallbackContacts(Set<ValueContact> serializedContacts) throws RemoteException;
+    public void setFallbackContacts(Set<ValueContact> contacts) throws RemoteException;
 
 }
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Main.java b/src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Main.java
index ea9fdb1..979aa92 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Main.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/interpreter/Main.java
@@ -65,11 +65,6 @@ public class Main {
         scanner.close();
     }
 
-    private static PathName getPathName(ZMI zmi) {
-        String name = ((ValueString)zmi.getAttributes().get("name")).getValue();
-        return zmi.getFather() == null? PathName.ROOT : getPathName(zmi.getFather()).levelDown(name);
-    }
-
     private static void executeQueries(ZMI zmi, String query, PrintStream out) throws Exception {
         if(!zmi.getSons().isEmpty()) {
             for(ZMI son : zmi.getSons())
@@ -78,7 +73,7 @@ public class Main {
             Yylex lex = new Yylex(new ByteArrayInputStream(query.getBytes()));
             try {
                 List<QueryResult> result = interpreter.interpretProgram((new parser(lex)).pProgram());
-                PathName zone = getPathName(zmi);
+                PathName zone = zmi.getPathName();
                 for(QueryResult r : result) {
                     out.println(zone + ": " + r);
                     zmi.getAttributes().addOrChange(r.getName(), r.getValue());
@@ -249,7 +244,7 @@ public class Main {
         return root;
     }
 
-    private static ZMI createTestHierarchy2() throws ParseException, UnknownHostException {
+    public static ZMI createTestHierarchy2() throws ParseException, UnknownHostException {
         ValueContact violet07Contact = createContact("/uw/violet07", (byte)10, (byte)1, (byte)1, (byte)10);
         ValueContact khaki13Contact = createContact("/uw/khaki13", (byte)10, (byte)1, (byte)1, (byte)38);
         ValueContact khaki31Contact = createContact("/uw/khaki31", (byte)10, (byte)1, (byte)1, (byte)39);
@@ -368,6 +363,7 @@ public class Main {
         khaki13.getAttributes().add("creation", new ValueTime((Long)null));
         khaki13.getAttributes().add("cpu_usage", new ValueDouble(0.1));
         khaki13.getAttributes().add("num_cores", new ValueInt(null));
+        khaki13.getAttributes().add("num_processes", new ValueInt(107l));
         khaki13.getAttributes().add("has_ups", new ValueBoolean(true));
         list = Arrays.asList(new Value[] {});
         khaki13.getAttributes().add("some_names", new ValueList(list, TypePrimitive.STRING));
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/Attribute.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/Attribute.java
index aa0cb64..eb916be 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/Attribute.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/Attribute.java
@@ -24,6 +24,8 @@
 
 package pl.edu.mimuw.cloudatlas.model;
 
+import java.io.Serializable;
+
 /**
  * Represents an attribute (without value, name only).
  * <p>
@@ -32,7 +34,7 @@ package pl.edu.mimuw.cloudatlas.model;
  * <p>
  * This class is immutable.
  */
-public class Attribute {
+public class Attribute implements Serializable {
     private final String name;
 
     /**
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java
index 4065ad6..c74c1df 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/AttributesMap.java
@@ -24,6 +24,7 @@
 
 package pl.edu.mimuw.cloudatlas.model;
 
+import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -32,7 +33,7 @@ import java.util.Map.Entry;
 /**
  * Represents a map from <code>Attribute</code> to <code>Value</code>. It cannot contain duplicate keys.
  */
-public class AttributesMap implements Iterable<Entry<Attribute, Value>>, Cloneable {
+public class AttributesMap implements Iterable<Entry<Attribute, Value>>, Cloneable, Serializable {
     private Map<Attribute, Value> map = new HashMap<Attribute, Value>();
 
     private void checkNulls(Attribute attribute, Value value) {
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/Type.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/Type.java
index 986db71..0994cba 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/Type.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/Type.java
@@ -24,16 +24,18 @@
 
 package pl.edu.mimuw.cloudatlas.model;
 
+import java.io.Serializable;
+
 /**
  * A type of a value that may be stored as an attribute.
  */
-public abstract class Type {
+public abstract class Type implements Serializable {
     /**
      * A primary type. This is a characteristic that every type has. It can be extended: for instance a collection may
      * be parameterized with a type of stored values.
      */
     public static enum PrimaryType {
-        BOOLEAN, CONTACT, DOUBLE, DURATION, INT, LIST, NULL, SET, STRING, TIME,
+        BOOLEAN, CONTACT, DOUBLE, DURATION, INT, LIST, NULL, SET, STRING, TIME, QUERY
     }
 
     private final PrimaryType primaryType;
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/TypePrimitive.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/TypePrimitive.java
index ab28cb4..ad07c0a 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/TypePrimitive.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/TypePrimitive.java
@@ -73,6 +73,11 @@ public class TypePrimitive extends Type {
      */
     public static final TypePrimitive TIME = new TypePrimitive(PrimaryType.TIME);
 
+    /**
+     * Query type.
+     */
+    public static final TypePrimitive QUERY = new TypePrimitive(PrimaryType.QUERY);
+
     private TypePrimitive(PrimaryType primaryType) {
         super(primaryType);
         switch(primaryType) {
@@ -84,6 +89,7 @@ public class TypePrimitive extends Type {
             case NULL:
             case STRING:
             case TIME:
+            case QUERY:
                 break;
             default:
                 throw new IllegalArgumentException(
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/Value.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/Value.java
index c4054cf..55353c1 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/Value.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/Value.java
@@ -26,10 +26,12 @@ package pl.edu.mimuw.cloudatlas.model;
 
 import pl.edu.mimuw.cloudatlas.model.Value;
 
+import java.io.Serializable;
+
 /**
  * A single value stored as an attribute.
  */
-public abstract class Value {
+public abstract class Value implements Serializable {
     /**
      * An operation that may be performed on values.
      */
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueDuration.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueDuration.java
index 7022bbd..7a74776 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueDuration.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueDuration.java
@@ -248,7 +248,8 @@ public class ValueDuration extends ValueSimple<Long> {
         return new ValueDuration(-getValue());
     }
 
-    public String toString() {
+
+    private String makeString() {
         long remainingUnits = getValue();
         boolean positive = remainingUnits >= 0;
         remainingUnits = positive ? remainingUnits : -remainingUnits;
@@ -272,7 +273,7 @@ public class ValueDuration extends ValueSimple<Long> {
     public Value convertTo(Type type) {
         switch(type.getPrimaryType()) {
             case STRING:
-                return getValue() == null? ValueString.NULL_STRING : new ValueString(toString());
+                return getValue() == null? ValueString.NULL_STRING : new ValueString(makeString());
             case DURATION:
                 return this;
             default:
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueList.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueList.java
index 8414cc4..c7f1036 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueList.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueList.java
@@ -24,6 +24,7 @@
 
 package pl.edu.mimuw.cloudatlas.model;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueQuery.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueQuery.java
new file mode 100644
index 0000000..d9cbe4c
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/ValueQuery.java
@@ -0,0 +1,70 @@
+package pl.edu.mimuw.cloudatlas.model;
+
+import java.io.ByteArrayInputStream;
+
+import pl.edu.mimuw.cloudatlas.interpreter.query.Absyn.Program;
+import pl.edu.mimuw.cloudatlas.interpreter.query.parser;
+import pl.edu.mimuw.cloudatlas.interpreter.query.Yylex;
+import pl.edu.mimuw.cloudatlas.model.Value;
+
+/**
+ * A class that holds a CloudAtlas query.
+ */
+public class ValueQuery extends Value {
+    // Original source code
+    private String code;
+    // Parsed query
+    private Program query;
+    /**
+     * Constructs a new <code>ValueQuery</code> object.
+     *
+     * @param name the name of the query
+     * @param query the code of the query
+     */
+    public ValueQuery(String query) throws Exception {
+        this.code = query;
+        Yylex lex = new Yylex(new ByteArrayInputStream(query.getBytes()));
+        this.query = (new parser(lex)).pProgram();
+    }
+
+    private ValueQuery() {
+        this.code = null;
+        this.query = null;
+    }
+
+    @Override
+    public Type getType() {
+        return TypePrimitive.QUERY;
+    }
+
+    @Override
+    public boolean isNull() {
+        return query == null || code == null;
+    }
+
+    public Value isEqual(Value value) {
+        sameTypesOrThrow(value, Operation.EQUAL);
+        if(isNull() && value.isNull())
+            return new ValueBoolean(true);
+        else if(isNull() || value.isNull())
+            return new ValueBoolean(false);
+        return new ValueBoolean(code.equals(((ValueQuery)value).code));
+    }
+
+    @Override
+    public Value getDefaultValue() {
+        return new ValueQuery();
+    }
+
+    @Override
+    public Value convertTo(Type type) {
+        switch(type.getPrimaryType()) {
+            case QUERY:
+                return this;
+            case STRING:
+                return isNull() ? ValueString.NULL_STRING : new ValueString(code);
+            default:
+                throw new UnsupportedConversionException(getType(), type);
+        }
+    }
+}
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 5a560ae..a311c61 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
@@ -41,6 +41,11 @@ import com.esotericsoftware.kryo.io.Output;
  * references to its father and sons in the tree.
  */
 public class ZMI implements Cloneable {
+    public class NoSuchZoneException extends Exception {
+        public NoSuchZoneException(PathName path) {
+            super("No such zone: " + path);
+        }
+    }
     private final AttributesMap attributes = new AttributesMap();
 
     private final List<ZMI> sons = new ArrayList<ZMI>();
@@ -85,6 +90,26 @@ public class ZMI implements Cloneable {
         this.father = father;
     }
 
+    public ZMI findDescendant(PathName path) throws NoSuchZoneException {
+        ZMI descendant = this;
+        for (String component : path.getComponents()) {
+            boolean foundNextSon = false;
+            for (ZMI son : descendant.getSons()) {
+                if (son.getAttributes().get("name").equals(new ValueString(component))) {
+                    descendant = son;
+                    foundNextSon = true;
+                    break;
+                }
+            }
+
+            if (!foundNextSon) {
+                throw new NoSuchZoneException(path);
+            }
+        }
+
+        return descendant;
+    }
+
     /**
      * Gets the list of sons of this ZMI. Modifying a value in the returned list will cause an exception.
      *
@@ -170,6 +195,16 @@ public class ZMI implements Cloneable {
         return attributes.toString();
     }
 
+    /**
+     * Gets the PathName representing this zone.
+     *
+     * @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);
+    }
+
     public static ZMI deserialize(InputStream in) {
         Kryo kryo = new Kryo();
         Input kryoInput = new Input(in);
-- 
cgit v1.2.3