[ONOS-7732] Automating switch workflow: annotation based data model injection, applying programming counter, and small fixes

Change-Id: I4092d9c2695bcc8c4e8e01d54c442d3fac284eb6
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
index 3a74bd2..7ee1be7 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
@@ -21,11 +21,17 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.IntNode;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.JsonNodeType;
 import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
 import com.google.common.base.MoreObjects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Objects;
 
@@ -34,6 +40,8 @@
  */
 public final class JsonDataModelTree implements DataModelTree {
 
+    private static final Logger log = LoggerFactory.getLogger(JsonDataModelTree.class);
+
     /**
      * Root node of json data model tree.
      */
@@ -65,30 +73,88 @@
 
     @Override
     public void attach(String path, DataModelTree tree) throws WorkflowException {
+
         if (root == null || root instanceof MissingNode) {
             throw new WorkflowException("Invalid root node");
         }
 
         JsonPointer ptr = JsonPointer.compile(path);
-        JsonNode node = root.at(ptr);
-        if (!(node instanceof MissingNode)) {
-            throw new WorkflowException("Path(" + path + ") has already subtree(" + node + ")");
-        }
 
         if (!(tree instanceof JsonDataModelTree)) {
             throw new WorkflowException("Invalid subTree(" + tree + ")");
         }
         JsonNode attachingNode = ((JsonDataModelTree) tree).root();
 
-        alloc(ptr.head(), Nodetype.MAP);
-        JsonNode parentNode = root.at(ptr.head());
+        attach(ptr, attachingNode);
+    }
 
-        if (!parentNode.isObject()) {
-            throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + ")");
+    private void attach(JsonPointer ptr, JsonNode attachingNode) throws WorkflowException {
+
+        JsonNode node = root.at(ptr);
+        if (!(node instanceof MissingNode)) {
+            throw new WorkflowException("Path(" + ptr + ") has already subtree(" + node + ")");
         }
 
-        String key = ptr.last().getMatchingProperty();
-        ((ObjectNode) parentNode).put(key, attachingNode);
+        if (ptr.last().getMatchingIndex() != -1) {
+
+            alloc(ptr.head(), Nodetype.ARRAY);
+            JsonNode parentNode = root.at(ptr.head());
+            if (!parentNode.isArray()) {
+                throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
+            }
+            int index = ptr.last().getMatchingIndex();
+            ((ArrayNode) parentNode).insert(index, attachingNode);
+
+        } else if (ptr.last().getMatchingProperty() != null) {
+
+            alloc(ptr.head(), Nodetype.MAP);
+            JsonNode parentNode = root.at(ptr.head());
+            if (!parentNode.isObject()) {
+                throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
+            }
+            String key = ptr.last().getMatchingProperty();
+            ((ObjectNode) parentNode).put(key, attachingNode);
+
+        } else {
+            throw new WorkflowException("Invalid path(" + ptr + ")");
+        }
+    }
+
+    @Override
+    public void remove(String path) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        remove(ptr);
+    }
+
+    private void remove(JsonPointer ptr) throws WorkflowException {
+
+        JsonNode node = root.at(ptr);
+        if (node instanceof MissingNode) {
+            log.warn("{} does not have valid node", ptr);
+            return;
+        }
+
+        if (ptr.last().getMatchingIndex() != -1) {
+
+            JsonNode parentNode = root.at(ptr.head());
+            if (!parentNode.isArray()) {
+                throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
+            }
+            int index = ptr.last().getMatchingIndex();
+            ((ArrayNode) parentNode).remove(index);
+
+        } else if (ptr.last().getMatchingProperty() != null) {
+
+            JsonNode parentNode = root.at(ptr.head());
+            if (!parentNode.isObject()) {
+                throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
+            }
+            String key = ptr.last().getMatchingProperty();
+            ((ObjectNode) parentNode).remove(key);
+
+        } else {
+            throw new WorkflowException("Invalid path(" + ptr + ")");
+        }
     }
 
     @Override
@@ -200,6 +266,9 @@
             throw new WorkflowException("Invalid root node");
         }
         JsonNode node = root.at(ptr);
+        if (node instanceof MissingNode) {
+            return null;
+        }
         if (!(node instanceof ObjectNode)) {
             throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
         }
@@ -228,6 +297,9 @@
             throw new WorkflowException("Invalid root node");
         }
         JsonNode node = root.at(ptr);
+        if (node instanceof MissingNode) {
+            return null;
+        }
         if (!(node instanceof ArrayNode)) {
             throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
         }
@@ -235,6 +307,165 @@
     }
 
     /**
+     * Gets text node on specific path.
+     * @param path path of json node
+     * @return text on specific path
+     * @throws WorkflowException workflow exception
+     */
+    public String textAt(String path) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        return textAt(ptr);
+    }
+
+    /**
+     * Gets text on specific json pointer.
+     * @param ptr json pointer
+     * @return text on specific json pointer
+     * @throws WorkflowException workflow exception
+     */
+    public String textAt(JsonPointer ptr) throws WorkflowException {
+        if (root == null || root instanceof MissingNode) {
+            throw new WorkflowException("Invalid root node");
+        }
+        JsonNode node = root.at(ptr);
+        if (node instanceof MissingNode) {
+            return null;
+        }
+        if (!(node instanceof TextNode)) {
+            throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+        }
+        return ((TextNode) node).asText();
+    }
+
+    /**
+     * Gets integer node on specific path.
+     * @param path path of json node
+     * @return integer on specific path
+     * @throws WorkflowException workflow exception
+     */
+    public Integer intAt(String path) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        return intAt(ptr);
+    }
+
+    /**
+     * Gets integer on specific json pointer.
+     * @param ptr json pointer
+     * @return integer on specific json pointer
+     * @throws WorkflowException workflow exception
+     */
+    public Integer intAt(JsonPointer ptr) throws WorkflowException {
+        if (root == null || root instanceof MissingNode) {
+            throw new WorkflowException("Invalid root node");
+        }
+        JsonNode node = root.at(ptr);
+        if (node instanceof MissingNode) {
+            return null;
+        }
+        if (!(node instanceof NumericNode)) {
+            throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+        }
+        return ((NumericNode) node).asInt();
+    }
+
+    /**
+     * Gets boolean on specific path.
+     * @param path path of json node
+     * @return boolean on specific path
+     * @throws WorkflowException workflow exception
+     */
+    public Boolean booleanAt(String path) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        return booleanAt(ptr);
+    }
+
+    /**
+     * Gets boolean on specific json pointer.
+     * @param ptr json pointer
+     * @return boolean on specific json pointer
+     * @throws WorkflowException workflow exception
+     */
+    public Boolean booleanAt(JsonPointer ptr) throws WorkflowException {
+        if (root == null || root instanceof MissingNode) {
+            throw new WorkflowException("Invalid root node");
+        }
+        JsonNode node = root.at(ptr);
+        if (node instanceof MissingNode) {
+            return null;
+        }
+        if (!(node instanceof BooleanNode)) {
+            throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+        }
+        return ((BooleanNode) node).asBoolean();
+    }
+
+    /**
+     * Sets text on specific json path.
+     * @param path json path
+     * @param text text to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(String path, String text) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        setAt(ptr, text);
+    }
+
+    /**
+     * Sets text on the specific json pointer.
+     * @param ptr json pointer
+     * @param text text to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(JsonPointer ptr, String text) throws WorkflowException {
+        TextNode textNode = TextNode.valueOf(text);
+        attach(ptr, textNode);
+    }
+
+    /**
+     * Sets boolean on specific json path.
+     * @param path json path
+     * @param isTrue boolean to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(String path, Boolean isTrue) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        setAt(ptr, isTrue);
+    }
+
+    /**
+     * Sets boolean on the specific json pointer.
+     * @param ptr json pointer
+     * @param isTrue boolean to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(JsonPointer ptr, Boolean isTrue) throws WorkflowException {
+        BooleanNode booleanNode = BooleanNode.valueOf(isTrue);
+        attach(ptr, booleanNode);
+    }
+
+    /**
+     * Sets integer on specific json path.
+     * @param path json path
+     * @param number number to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(String path, Integer number) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        setAt(ptr, number);
+    }
+
+    /**
+     * Sets integer on the specific json pointer.
+     * @param ptr json pointer
+     * @param number number to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(JsonPointer ptr, Integer number) throws WorkflowException {
+        IntNode intNode = IntNode.valueOf(number);
+        attach(ptr, intNode);
+    }
+
+    /**
      * Allocates json data model tree on json pointer path with specific leaf type.
      * @param node current json node in the json tree path
      * @param ptr json pointer
@@ -245,11 +476,11 @@
     private JsonNode alloc(JsonNode node, JsonPointer ptr, JsonNodeType leaftype) throws WorkflowException {
 
         if (ptr.matches()) {
-            if (node instanceof MissingNode) {
+            if (node == null || node instanceof MissingNode) {
                 node = createEmpty(leaftype);
             } else {
                 //TODO: checking existing node type is matched with leaftype
-                if (Objects.equals(node.getNodeType(), leaftype)) {
+                if (!Objects.equals(node.getNodeType(), leaftype)) {
                     throw new WorkflowException("Requesting leaftype(" + leaftype + ") is not matched with "
                             + "existing nodetype(" + node.getNodeType() + ") for " + ptr);
                 }
@@ -258,7 +489,7 @@
         }
 
         if (ptr.getMatchingIndex() != -1) {
-            if (node instanceof MissingNode) {
+            if (node == null || node instanceof MissingNode) {
                 node = createEmpty(JsonNodeType.ARRAY);
             }
             JsonNode child = alloc(node.get(ptr.getMatchingIndex()), ptr.tail(), leaftype);
@@ -266,7 +497,7 @@
                 ((ArrayNode) node).insert(ptr.getMatchingIndex(), child);
             }
         } else if (ptr.getMatchingProperty() != null) {
-            if (node instanceof MissingNode) {
+            if (node == null || node instanceof MissingNode) {
                 node = createEmpty(JsonNodeType.OBJECT);
             }
             JsonNode child = alloc(node.get(ptr.getMatchingProperty()), ptr.tail(), leaftype);
@@ -304,7 +535,7 @@
         try {
             str = (new ObjectMapper()).writerWithDefaultPrettyPrinter().writeValueAsString(root);
         } catch (JsonProcessingException e) {
-            e.printStackTrace();
+            log.error("Exception: ", e);
         }
         return str;
     }
@@ -312,7 +543,7 @@
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
-                .add("json", formattedRootString())
+                .add("json", root)
                 .toString();
     }
 }