Move code from drools-apps to common

Extracted code from ControlLoopUtils to create a new class in
common, PropertyObjectUtils.
Fixed a deprecated method invocation in a junit.
Fixed an object casting (i.e., sonar) issue in SCO.

Issue-ID: POLICY-2305
Signed-off-by: Jim Hahn <jrh3@att.com>
Change-Id: I331a47297f67097ea6986be125ef93cd1954b5ff
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/MessageTypeDispatcher.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/MessageTypeDispatcher.java
index 195a7ee..31e670a 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/MessageTypeDispatcher.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/MessageTypeDispatcher.java
@@ -36,7 +36,7 @@
     /**
      * Name of the message field, which may be hierarchical.
      */
-    private final String[] messageFieldNames;
+    private final Object[] messageFieldNames;
 
     /**
      * Name of the message field, joined with "." - for logging.
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/RequestIdDispatcher.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/RequestIdDispatcher.java
index fcf9c9a..e4686c1 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/RequestIdDispatcher.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/listeners/RequestIdDispatcher.java
@@ -43,7 +43,7 @@
     /**
      * Name of the request id field, which may be hierarchical.
      */
-    private final String[] requestIdFieldNames;
+    private final Object[] requestIdFieldNames;
 
     /**
      * Listeners for autonomous messages.
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyObjectUtils.java b/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyObjectUtils.java
new file mode 100644
index 0000000..996f1b8
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyObjectUtils.java
@@ -0,0 +1,243 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.properties;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utilities for generating POJOs from Properties.
+ */
+public class PropertyObjectUtils {
+
+    public static final Logger logger = LoggerFactory.getLogger(PropertyObjectUtils.class);
+    private static final Pattern NAME_PAT = Pattern.compile("\\[(\\d{1,3})\\]$");
+
+    private PropertyObjectUtils() {
+        // do nothing
+    }
+
+    /**
+     * Converts a set of properties to a Map. Supports json-path style property names with
+     * "." separating components, where components may have an optional subscript.
+     *
+     * @param properties properties to be converted
+     * @param prefix properties whose names begin with this prefix are included. The
+     *        prefix is stripped from the name before adding the value to the map
+     * @return a hierarchical map representing the properties
+     */
+    public static Map<String, Object> toObject(Properties properties, String prefix) {
+        String dottedPrefix = prefix + (prefix.isEmpty() || prefix.endsWith(".") ? "" : ".");
+        int pfxlen = dottedPrefix.length();
+
+        Map<String, Object> map = new LinkedHashMap<>();
+
+        for (String name : properties.stringPropertyNames()) {
+            if (name.startsWith(dottedPrefix)) {
+                String[] components = name.substring(pfxlen).split("[.]");
+                setProperty(map, components, properties.getProperty(name));
+            }
+        }
+
+        return map;
+    }
+
+    /**
+     * Sets a property within a hierarchical map.
+     *
+     * @param map map into which the value should be placed
+     * @param names property name components
+     * @param value value to be placed into the map
+     */
+    private static void setProperty(Map<String, Object> map, String[] names, String value) {
+        Map<String, Object> node = map;
+
+        final int lastComp = names.length - 1;
+
+        // process all but the final component
+        for (int comp = 0; comp < lastComp; ++comp) {
+            node = getNode(node, names[comp]);
+        }
+
+        // process the final component
+        String name = names[lastComp];
+        Matcher matcher = NAME_PAT.matcher(name);
+
+        if (!matcher.find()) {
+            // no subscript
+            node.put(name, value);
+            return;
+        }
+
+        // subscripted
+        List<Object> array = getArray(node, name.substring(0, matcher.start()));
+        int index = Integer.parseInt(matcher.group(1));
+        expand(array, index);
+        array.set(index, value);
+    }
+
+    /**
+     * Gets a node.
+     *
+     * @param map map from which to get the object
+     * @param name name of the element to get from the map, with an optional subscript
+     * @return a Map
+     */
+    @SuppressWarnings("unchecked")
+    private static Map<String, Object> getNode(Map<String, Object> map, String name) {
+        Matcher matcher = NAME_PAT.matcher(name);
+
+        if (!matcher.find()) {
+            // no subscript
+            return getObject(map, name);
+        }
+
+        // subscripted
+        List<Object> array = getArray(map, name.substring(0, matcher.start()));
+        int index = Integer.parseInt(matcher.group(1));
+        expand(array, index);
+
+        Object item = array.get(index);
+        if (item instanceof Map) {
+            return (Map<String, Object>) item;
+
+        } else {
+            LinkedHashMap<String, Object> result = new LinkedHashMap<>();
+            array.set(index, result);
+            return result;
+        }
+    }
+
+    /**
+     * Ensures that an array's size is large enough to hold the specified element.
+     *
+     * @param array array to be expanded
+     * @param index index of the desired element
+     */
+    private static void expand(List<Object> array, int index) {
+        while (array.size() <= index) {
+            array.add(null);
+        }
+    }
+
+    /**
+     * Gets an object (i.e., Map) from a map. If the particular element is not a Map, then
+     * it is replaced with an empty Map.
+     *
+     * @param map map from which to get the object
+     * @param name name of the element to get from the map, without any subscript
+     * @return a Map
+     */
+    private static Map<String, Object> getObject(Map<String, Object> map, String name) {
+        @SuppressWarnings("unchecked")
+        Map<String, Object> result = (Map<String, Object>) map.compute(name, (key, value) -> {
+            if (value instanceof Map) {
+                return value;
+            } else {
+                return new LinkedHashMap<>();
+            }
+        });
+
+        return result;
+    }
+
+    /**
+     * Gets an array from a map. If the particular element is not an array, then it is
+     * replaced with an empty array.
+     *
+     * @param map map from which to get the array
+     * @param name name of the element to get from the map, without any subscript
+     * @return an array
+     */
+    private static List<Object> getArray(Map<String, Object> map, String name) {
+        @SuppressWarnings("unchecked")
+        List<Object> result = (List<Object>) map.compute(name, (key, value) -> {
+            if (value instanceof List) {
+                return value;
+            } else {
+                return new ArrayList<>();
+            }
+        });
+
+        return result;
+    }
+
+    /**
+     * Compresses lists contained within a generic object, removing all {@code null}
+     * items.
+     *
+     * @param object object to be compressed
+     * @return the original object, modified in place
+     */
+    public static Object compressLists(Object object) {
+        if (object instanceof Map) {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> asMap = (Map<String, Object>) object;
+            compressMapValues(asMap);
+
+        } else if (object instanceof List) {
+            @SuppressWarnings("unchecked")
+            List<Object> asList = (List<Object>) object;
+            compressListItems(asList);
+        }
+
+        // else: ignore anything else
+
+        return object;
+    }
+
+    /**
+     * Walks a hierarchical map and removes {@code null} items found in any Lists.
+     *
+     * @param map map whose lists are to be compressed
+     */
+    private static void compressMapValues(Map<String, Object> map) {
+        for (Object value : map.values()) {
+            compressLists(value);
+        }
+    }
+
+    /**
+     * Removes {@code null} items from the list. In addition, it walks the items within
+     * the list, compressing them, as well.
+     *
+     * @param list the list to be compressed
+     */
+    private static void compressListItems(List<Object> list) {
+        Iterator<Object> iter = list.iterator();
+        while (iter.hasNext()) {
+            Object item = iter.next();
+            if (item == null) {
+                // null item - remove it
+                iter.remove();
+
+            } else {
+                compressLists(item);
+            }
+        }
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/properties/PropertyObjectUtilsTest.java b/utils/src/test/java/org/onap/policy/common/utils/properties/PropertyObjectUtilsTest.java
new file mode 100644
index 0000000..83b264f
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/properties/PropertyObjectUtilsTest.java
@@ -0,0 +1,212 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.properties;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.AbstractSet;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import org.junit.Test;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+public class PropertyObjectUtilsTest {
+
+    @Test
+    public void testToObject() {
+        Map<String, String> map = Map.of("abc", "def", "ghi", "jkl");
+        Properties props = new Properties();
+        props.putAll(map);
+
+        // with empty prefix
+        Map<String, Object> result = PropertyObjectUtils.toObject(props, "");
+        assertEquals(map, result);
+
+        // with dotted prefix - other items skipped
+        map = Map.of("pfx.abc", "def", "ghi", "jkl", "pfx.mno", "pqr", "differentpfx.stu", "vwx");
+        props.clear();
+        props.putAll(Map.of("pfx.abc", "def", "ghi", "jkl", "pfx.mno", "pqr", "differentpfx.stu", "vwx"));
+        result = PropertyObjectUtils.toObject(props, "pfx.");
+        map = Map.of("abc", "def", "mno", "pqr");
+        assertEquals(map, result);
+
+        // undotted prefix - still skips other items
+        result = PropertyObjectUtils.toObject(props, "pfx");
+        assertEquals(map, result);
+    }
+
+    @Test
+    public void testSetProperty() {
+        // one, two, and three components in the name, the last two with subscripts
+        Map<String, Object> map = Map.of("one", "one.abc", "two.def", "two.ghi", "three.jkl.mno[0]", "three.pqr",
+                        "three.jkl.mno[1]", "three.stu");
+        Properties props = new Properties();
+        props.putAll(map);
+
+        Map<String, Object> result = PropertyObjectUtils.toObject(props, "");
+        // @formatter:off
+        map = Map.of(
+                "one", "one.abc",
+                "two", Map.of("def", "two.ghi"),
+                "three", Map.of("jkl",
+                            Map.of("mno",
+                                List.of("three.pqr", "three.stu"))));
+        // @formatter:on
+        assertEquals(map, result);
+    }
+
+    @Test
+    public void testGetNode() {
+        Map<String, Object> map = Map.of("abc[0].def", "node.ghi", "abc[0].jkl", "node.mno", "abc[1].def", "node.pqr");
+        Properties props = new Properties();
+        props.putAll(map);
+
+        Map<String, Object> result = PropertyObjectUtils.toObject(props, "");
+        // @formatter:off
+        map = Map.of(
+                "abc",
+                    List.of(
+                        Map.of("def", "node.ghi", "jkl", "node.mno"),
+                        Map.of("def", "node.pqr")
+                    ));
+        // @formatter:on
+        assertEquals(map, result);
+
+    }
+
+    @Test
+    public void testExpand() {
+        // add subscripts out of order
+        Properties props = makeProperties("abc[2]", "expand.def", "abc[1]", "expand.ghi");
+
+        Map<String, Object> result = PropertyObjectUtils.toObject(props, "");
+        // @formatter:off
+        Map<String,Object> map =
+            Map.of("abc",
+                Arrays.asList(null, "expand.ghi", "expand.def"));
+        // @formatter:on
+        assertEquals(map, result);
+
+    }
+
+    @Test
+    public void testGetObject() {
+        // first value is primitive, while second is a map
+        Properties props = makeProperties("object.abc", "object.def", "object.abc.ghi", "object.jkl");
+
+        Map<String, Object> result = PropertyObjectUtils.toObject(props, "");
+        // @formatter:off
+        Map<String,Object> map =
+            Map.of("object",
+                Map.of("abc",
+                    Map.of("ghi", "object.jkl")));
+        // @formatter:on
+        assertEquals(map, result);
+    }
+
+    @Test
+    public void testGetArray() {
+        // first value is primitive, while second is an array
+        Properties props = makeProperties("array.abc", "array.def", "array.abc[0].ghi", "array.jkl");
+
+        Map<String, Object> result = PropertyObjectUtils.toObject(props, "");
+        // @formatter:off
+        Map<String,Object> map =
+            Map.of("array",
+                Map.of("abc",
+                    List.of(
+                        Map.of("ghi", "array.jkl"))));
+        // @formatter:on
+        assertEquals(map, result);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testCompressLists() throws IOException, CoderException {
+        assertEquals("plain-string", PropertyObjectUtils.compressLists("plain-string").toString());
+
+        // @formatter:off
+        Map<String, Object> map =
+            Map.of(
+                "cmp.abc", "cmp.def",
+                "cmp.ghi",
+                    Arrays.asList(null, "cmp.list1", null, "cmp.list2",
+                        Map.of("cmp.map", Arrays.asList("cmp.map.list1", "cmp.map1.list2", null))));
+        // @formatter:on
+
+        // the data structure needs to be modifiable, so we'll encode/decode it
+        StandardCoder coder = new StandardCoder();
+        map = coder.decode(coder.encode(map), LinkedHashMap.class);
+
+        PropertyObjectUtils.compressLists(map);
+
+        // @formatter:off
+        Map<String, Object> expected =
+            Map.of(
+                "cmp.abc", "cmp.def",
+                "cmp.ghi",
+                    Arrays.asList("cmp.list1", "cmp.list2",
+                        Map.of("cmp.map", Arrays.asList("cmp.map.list1", "cmp.map1.list2"))));
+        // @formatter:on
+        assertEquals(expected, map);
+    }
+
+    /**
+     * Makes properties containing the specified key/value pairs. The property set returns
+     * names in the order listed.
+     *
+     * @return a new properties containing the specified key/value pairs
+     */
+    private Properties makeProperties(String key1, String value1, String key2, String value2) {
+        // control the order in which the names are returned
+        List<String> keyList = List.of(key1, key2);
+
+        Set<String> keySet = new AbstractSet<>() {
+            @Override
+            public Iterator<String> iterator() {
+                return keyList.iterator();
+            }
+
+            @Override
+            public int size() {
+                return 2;
+            }
+        };
+
+        Properties props = new Properties() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public Set<String> stringPropertyNames() {
+                return keySet;
+            }
+        };
+
+        props.putAll(Map.of(key1, value1, key2, value2));
+
+        return props;
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/properties/exception/SupportBasicPropertyExceptionTester.java b/utils/src/test/java/org/onap/policy/common/utils/properties/exception/SupportBasicPropertyExceptionTester.java
index 97503f8..f584292 100644
--- a/utils/src/test/java/org/onap/policy/common/utils/properties/exception/SupportBasicPropertyExceptionTester.java
+++ b/utils/src/test/java/org/onap/policy/common/utils/properties/exception/SupportBasicPropertyExceptionTester.java
@@ -7,9 +7,9 @@
  * 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.
@@ -20,11 +20,9 @@
 
 package org.onap.policy.common.utils.properties.exception;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-
-import org.hamcrest.CoreMatchers;
 
 /**
  * Superclass used to test subclasses of {@link PropertyException}.
@@ -52,7 +50,7 @@
     protected static final String FIELD = "PROPERTY";
 
     /*
-     * Methods to perform various tests on the except subclass. 
+     * Methods to perform various tests on the except subclass.
      */
 
     protected void doTestPropertyExceptionStringField_AllPopulated(PropertyException ex) {
@@ -98,7 +96,7 @@
 
     /**
      * Performs standard tests that should apply to all subclasses.
-     * 
+     *
      * @param ex exception to test
      */
     protected void standardTests(PropertyException ex) {
@@ -111,17 +109,17 @@
     /**
      * Performs standard tests for exceptions that were provided a message in their
      * constructor.
-     * 
+     *
      * @param ex exception to test
      */
     protected void standardMessageTests(PropertyException ex) {
-        assertThat(ex.getMessage(), CoreMatchers.endsWith(MESSAGE));
+        assertThat(ex.getMessage()).endsWith(MESSAGE);
     }
 
     /**
      * Performs standard tests for exceptions that were provided a throwable in their
      * constructor.
-     * 
+     *
      * @param ex exception to test
      */
     protected void standardThrowableTests(PropertyException ex) {