Supporting workflow list & map json data model for workflow context.

Workflow json data model bind to list & map objects
usage. For example
List<Object> data;
Map<Object, Object> data.

Change-Id: I46899c8d2267ebca0663e85302eba2242fbcfdda
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
index f7ada8d..9c54535 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
@@ -61,10 +61,18 @@
     DataModelTree alloc(String path, Nodetype leaftype) throws WorkflowException;
 
     /**
-     * Remove node on the path.
+     * Remove node on the path. This removes the entity.
      * @param path data model tree path
      * @throws WorkflowException workflow exception
      */
     void remove(String path) throws WorkflowException;
+
+    /**
+     * Clear node on the path. This does not remove the entry. This initializes the value to Null.
+     * @param path data model tree path
+     * @throws WorkflowException workflow exception
+     */
+    void clear(String path) throws WorkflowException;
+
 }
 
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 aec19ae..8a0ec84 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
@@ -18,14 +18,17 @@
 
 import com.fasterxml.jackson.core.JsonPointer;
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
 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.NullNode;
 import com.fasterxml.jackson.databind.node.NumericNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.TextNode;
@@ -33,6 +36,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -92,11 +99,11 @@
     private void attach(JsonPointer ptr, JsonNode attachingNode) throws WorkflowException {
 
         JsonNode node = root.at(ptr);
-        if (!(node instanceof MissingNode)) {
+        if (!(node instanceof MissingNode || node instanceof NullNode)) {
             throw new WorkflowException("Path(" + ptr + ") has already subtree(" + node + ")");
         }
 
-        if (ptr.last().getMatchingIndex() != -1) {
+        if (isArrayIndex(ptr.last())) {
 
             alloc(ptr.head(), Nodetype.ARRAY);
             JsonNode parentNode = root.at(ptr.head());
@@ -104,9 +111,14 @@
                 throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
             }
             int index = ptr.last().getMatchingIndex();
+
+            if (parentNode.get(index) instanceof NullNode) {
+                ((ArrayNode) parentNode).remove(index);
+            }
+
             ((ArrayNode) parentNode).insert(index, attachingNode);
 
-        } else if (ptr.last().getMatchingProperty() != null) {
+        } else if (isMapKey(ptr.last())) {
 
             alloc(ptr.head(), Nodetype.MAP);
             JsonNode parentNode = root.at(ptr.head());
@@ -124,10 +136,16 @@
     @Override
     public void remove(String path) throws WorkflowException {
         JsonPointer ptr = JsonPointer.compile(path);
-        remove(ptr);
+        clear(ptr, true);
     }
 
-    private void remove(JsonPointer ptr) throws WorkflowException {
+    @Override
+    public void clear(String path) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        clear(ptr, false);
+    }
+
+    private void clear(JsonPointer ptr, boolean dispose) throws WorkflowException {
 
         JsonNode node = root.at(ptr);
         if (node instanceof MissingNode) {
@@ -135,23 +153,31 @@
             return;
         }
 
-        if (ptr.last().getMatchingIndex() != -1) {
+        if (isArrayIndex(ptr.last())) {
 
             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);
+            if (dispose) {
+                ((ArrayNode) parentNode).remove(index);
+            } else {
+                ((ArrayNode) parentNode).set(index, NullNode.getInstance());
+            }
 
-        } else if (ptr.last().getMatchingProperty() != null) {
+        } else if (isMapKey(ptr.last())) {
 
             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);
+            if (dispose) {
+                ((ObjectNode) parentNode).remove(key);
+            } else {
+                ((ObjectNode) parentNode).putNull(key);
+            }
 
         } else {
             throw new WorkflowException("Invalid path(" + ptr + ")");
@@ -363,6 +389,35 @@
     }
 
     /**
+     * Gets list on specific path.
+     *
+     * @param path path of json node
+     * @param <T> class type
+     * @param listDataTypeClass data type class of list
+     * @return list on specific path
+     * @throws WorkflowException workflow exception
+     */
+    public <T> List<T> listAt(String path, Class<T> listDataTypeClass) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        return listAt(ptr, listDataTypeClass);
+    }
+
+    /**
+     * Gets map on specific path.
+     *
+     * @param path path of json node
+     * @param <T> class type
+     * @param mapDataTypeClass data type of map value
+     * @return map of specific class type
+     * @throws WorkflowException workflow excepion
+     */
+    public <T> Map<String, T> mapAt(String path, Class<T> mapDataTypeClass) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        return mapAt(ptr, mapDataTypeClass);
+    }
+
+
+    /**
      * Gets integer on specific json pointer.
      *
      * @param ptr json pointer
@@ -380,7 +435,61 @@
         if (!(node instanceof NumericNode)) {
             throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
         }
-        return ((NumericNode) node).asInt();
+        return node.asInt();
+    }
+
+    /**
+     * Gets list on specific json pointer.
+     *
+     * @param ptr json pointer
+     * @param <T> class type
+     * @param listDataTypeClass data type class of list
+     * @return list on specific json pointer
+     * @throws WorkflowException workflow exception
+     */
+    public <T> List<T> listAt(JsonPointer ptr, Class<T> listDataTypeClass) {
+        List<T> list = new ArrayList<>();
+        try {
+            if (root == null || root instanceof MissingNode) {
+                throw new WorkflowException("Invalid root node");
+            }
+            JsonNode node = root.at(ptr);
+            ObjectMapper objectMapper = new ObjectMapper();
+            ObjectReader reader = objectMapper.readerFor(new TypeReference<List<T>>() {
+            });
+            if (node.isMissingNode()) {
+                return null;
+            }
+            list = reader.readValue(node);
+        } catch (Exception e) {
+            log.info("exception while parsing list {}", e.getStackTrace());
+        }
+        return list;
+    }
+
+    /**
+     * Gets map on specific json pointer.
+     *
+     * @param ptr json pointer
+     * @param <T> class type
+     * @param mapDataTypeClass data type class of value in the map
+     * @return map on specific json pointer
+     */
+    public <T> Map<String, T> mapAt(JsonPointer ptr, Class<T> mapDataTypeClass) {
+        Map<String, T> map = new HashMap<>();
+        try {
+            if (root == null || root instanceof MissingNode) {
+                throw new WorkflowException("Invalid root node");
+            }
+            JsonNode node = root.at(ptr);
+            ObjectMapper objectMapper = new ObjectMapper();
+            ObjectReader reader = objectMapper.readerFor(new TypeReference<Map<String, T>>() {
+            });
+            map = reader.readValue(node);
+        } catch (Exception e) {
+            log.info("exception while parsing map {}", e.getStackTrace());
+        }
+        return map;
     }
 
     /**
@@ -552,6 +661,57 @@
     }
 
     /**
+     * Sets list on specific json path.
+     *
+     * @param path json path
+     * @param list list to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(String path, List list) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        setAt(ptr, list);
+    }
+
+    /**
+     * Sets list on the specific json pointer.
+     *
+     * @param ptr  json pointer
+     * @param list list to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(JsonPointer ptr, List list) throws WorkflowException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode node = mapper.valueToTree(list);
+        attach(ptr, node);
+    }
+
+    /**
+     * Sets map on specific json path.
+     *
+     * @param path json path
+     * @param map  map to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(String path, Map map) throws WorkflowException {
+        JsonPointer ptr = JsonPointer.compile(path);
+        setAt(ptr, map);
+    }
+
+    /**
+     * Sets map on the specific json pointer.
+     *
+     * @param ptr json pointer
+     * @param map map to set
+     * @throws WorkflowException workflow exception
+     */
+    public void setAt(JsonPointer ptr, Map map) throws WorkflowException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode node = mapper.valueToTree(map);
+        attach(ptr, node);
+    }
+
+
+    /**
      * Sets integer on the specific json pointer.
      *
      * @param ptr    json pointer
@@ -574,8 +734,8 @@
      */
     private JsonNode alloc(JsonNode node, JsonPointer ptr, JsonNodeType leaftype) throws WorkflowException {
 
-        if (ptr.matches()) {
-            if (node == null || node instanceof MissingNode) {
+        if (ptr.matches()) { // node is leaf
+            if (node == null || node instanceof MissingNode || node instanceof NullNode) {
                 node = createEmpty(leaftype);
             } else {
                 //TODO: checking existing node type is matched with leaftype
@@ -587,19 +747,27 @@
             return node;
         }
 
-        if (ptr.getMatchingIndex() != -1) {
-            if (node == null || node instanceof MissingNode) {
+        // node is intermediate in the ptr
+        if (isArrayIndex(ptr)) {
+            if (node == null || node instanceof MissingNode || node instanceof NullNode) {
                 node = createEmpty(JsonNodeType.ARRAY);
             }
             JsonNode child = alloc(node.get(ptr.getMatchingIndex()), ptr.tail(), leaftype);
             if (!node.has(ptr.getMatchingIndex())) {
                 ((ArrayNode) node).insert(ptr.getMatchingIndex(), child);
+            } else {
+                ((ArrayNode) node).set(ptr.getMatchingIndex(), child);
             }
-        } else if (ptr.getMatchingProperty() != null) {
-            if (node == null || node instanceof MissingNode) {
+        } else if (isMapKey(ptr)) {
+            if (node == null || node instanceof MissingNode || node instanceof NullNode) {
                 node = createEmpty(JsonNodeType.OBJECT);
             }
             JsonNode child = alloc(node.get(ptr.getMatchingProperty()), ptr.tail(), leaftype);
+
+            if (node.get(ptr.getMatchingProperty()) instanceof NullNode) {
+                ((ObjectNode) node).remove(ptr.getMatchingProperty());
+            }
+
             if (!node.has(ptr.getMatchingProperty())) {
                 ((ObjectNode) node).put(ptr.getMatchingProperty(), child);
             }
@@ -641,6 +809,24 @@
         return str;
     }
 
+    /**
+     * Returns whether the json ptr is array type(having array index) or not.
+     * @param ptr
+     * @return Whether the json ptr is array type(having array index) or not.
+     */
+    private boolean isArrayIndex(JsonPointer ptr) {
+        return (ptr.getMatchingIndex() != -1);
+    }
+
+    /**
+     * Returns whether the json ptr is map type(having key) or not.
+     * @param ptr
+     * @return Whether the json ptr is map type(having key) or not.
+     */
+    private boolean isMapKey(JsonPointer ptr) {
+        return (ptr.getMatchingProperty() != null);
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())