ONOS-6257: fixing Region-peer-location function...

- corrected UiRegion.isRoot() implementation
- added to/from-compact-strings for LayoutLocation, so we can encode
   a list of them in an annotation
- Fixed bug in DistributedRegionStore which was emiting events that
   had a null region as subject.

Change-Id: I547e0c7f62385b85b191b8d63e6b569196623b84
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java
index ada4d15..0a2fa2f 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiRegion.java
@@ -123,7 +123,7 @@
      * @return true if root region
      */
     public boolean isRoot() {
-        return id().equals(parent);
+        return parent == null;
     }
 
     /**
@@ -181,7 +181,7 @@
      * @return the backing region instance
      */
     public Region backingRegion() {
-        return topology.services.region().getRegion(regionId);
+        return isRoot() ? null : topology.services.region().getRegion(regionId);
     }
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/LayoutLocation.java b/core/api/src/main/java/org/onosproject/ui/topo/LayoutLocation.java
index 9e006b5..69be6aa 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/LayoutLocation.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/LayoutLocation.java
@@ -17,6 +17,12 @@
 
 package org.onosproject.ui.topo;
 
+import com.google.common.base.Strings;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -25,6 +31,14 @@
  */
 public final class LayoutLocation {
     private static final double ZERO_THRESHOLD = Double.MIN_VALUE * 2.0;
+    private static final String COMMA = ",";
+    private static final String TILDE = "~";
+    private static final String EMPTY = "";
+
+    private static final String E_BAD_COMPACT = "Badly formatted compact form: ";
+    private static final String E_EMPTY_ID = "id must be non-empty";
+    private static final String E_BAD_DOUBLE = "unparsable double";
+
 
     /**
      * Designates the type of location; either geographic or logical grid.
@@ -45,7 +59,6 @@
         this.locType = locType;
     }
 
-
     private boolean doubleIsZero(double value) {
         return value >= -ZERO_THRESHOLD && value <= ZERO_THRESHOLD;
     }
@@ -70,6 +83,16 @@
     }
 
     /**
+     * Returns the location type (geo or grid), which indicates how the data
+     * is to be interpreted.
+     *
+     * @return location type
+     */
+    public Type locType() {
+        return locType;
+    }
+
+    /**
      * Returns the latitude (geo) or y-coord (grid) data value.
      *
      * @return geo latitude or grid y-coord
@@ -87,27 +110,111 @@
         return longOrX;
     }
 
-    /**
-     * Returns the location type (geo or grid), which indicates how the data
-     * is to be interpreted.
-     *
-     * @return location type
-     */
-    public Type locType() {
-        return locType;
-    }
-
     @Override
     public String toString() {
         return toStringHelper(this)
                 .add("id", id)
+                .add("loc-type", locType)
                 .add("lat/Y", latOrY)
                 .add("long/X", longOrX)
-                .add("loc-type", locType)
                 .toString();
     }
 
     /**
+     * Produces a compact string representation of this instance.
+     *
+     * @return compact string rep
+     */
+    public String toCompactListString() {
+        return id + COMMA + locType + COMMA + latOrY + COMMA + longOrX;
+    }
+
+    /**
+     * Produces a layout location instance from a compact string representation.
+     *
+     * @param s the compact string
+     * @return a corresponding instance
+     */
+    public static LayoutLocation fromCompactString(String s) {
+        String[] tokens = s.split(COMMA);
+        if (tokens.length != 4) {
+            throw new IllegalArgumentException(E_BAD_COMPACT + s);
+        }
+        String id = tokens[0];
+        String type = tokens[1];
+        String latY = tokens[2];
+        String longX = tokens[3];
+
+        if (Strings.isNullOrEmpty(id)) {
+            throw new IllegalArgumentException(E_BAD_COMPACT + E_EMPTY_ID);
+        }
+
+        double latOrY;
+        double longOrX;
+        try {
+            latOrY = Double.parseDouble(latY);
+            longOrX = Double.parseDouble(longX);
+        } catch (NumberFormatException nfe) {
+            throw new IllegalArgumentException(E_BAD_COMPACT + E_BAD_DOUBLE);
+        }
+
+        return LayoutLocation.layoutLocation(id, type, latOrY, longOrX);
+    }
+
+    /**
+     * Produces a compact encoding of a list of layout locations.
+     *
+     * @param locs array of layout location instances
+     * @return string encoding
+     */
+    public static String toCompactListString(LayoutLocation... locs) {
+        if (locs == null || locs.length == 0) {
+            return EMPTY;
+        }
+        List<LayoutLocation> lls = Arrays.asList(locs);
+        return toCompactListString(lls);
+    }
+
+    /**
+     * Produces a compact encoding of a list of layout locations.
+     *
+     * @param locs list of layout location instances
+     * @return string encoding
+     */
+    public static String toCompactListString(List<LayoutLocation> locs) {
+        // note: locs may be empty
+        if (locs == null || locs.isEmpty()) {
+            return EMPTY;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (LayoutLocation ll : locs) {
+            sb.append(ll.toCompactListString()).append(TILDE);
+        }
+        final int len = sb.length();
+        sb.replace(len - 1, len, "");
+        return sb.toString();
+    }
+
+    /**
+     * Returns a list of layout locations from a compact string representation.
+     *
+     * @param compactList string representation
+     * @return corresponding list of layout locations
+     */
+    public static List<LayoutLocation> fromCompactListString(String compactList) {
+        List<LayoutLocation> locs = new ArrayList<>();
+        if (!Strings.isNullOrEmpty(compactList)) {
+            String[] items = compactList.split(TILDE);
+            for (String s : items) {
+                locs.add(fromCompactString(s));
+            }
+        }
+        return locs;
+    }
+
+
+    /**
      * Creates an instance of a layout location.
      *
      * @param id      an identifier for the item at this location
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/LayoutLocationTest.java b/core/api/src/test/java/org/onosproject/ui/topo/LayoutLocationTest.java
index 64908fd..cca430c 100644
--- a/core/api/src/test/java/org/onosproject/ui/topo/LayoutLocationTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/topo/LayoutLocationTest.java
@@ -20,9 +20,12 @@
 import org.junit.Test;
 import org.onosproject.ui.AbstractUiTest;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.collect.ImmutableList.of;
 import static org.junit.Assert.*;
-import static org.onosproject.ui.topo.LayoutLocation.Type;
-import static org.onosproject.ui.topo.LayoutLocation.layoutLocation;
+import static org.onosproject.ui.topo.LayoutLocation.*;
 
 /**
  * Unit tests for {@link LayoutLocation}.
@@ -30,11 +33,18 @@
 public class LayoutLocationTest extends AbstractUiTest {
 
     private static final String SOME_ID = "foo";
+    private static final String OTHER_ID = "bar";
     private static final double SQRT2 = 1.414;
     private static final double PI = 3.142;
     private static final double ZERO = 0.0;
 
+    private static final String COMPACT_LL_1 = "foo,GEO,3.142,1.414";
+    private static final String COMPACT_LL_2 = "bar,GRID,1.414,3.142";
+
+    private static final String COMPACT_LIST = COMPACT_LL_1 + "~" + COMPACT_LL_2;
+
     private LayoutLocation ll;
+    private LayoutLocation ll2;
 
     @Test
     public void basic() {
@@ -86,4 +96,121 @@
     public void nullId() {
         layoutLocation(null, Type.GRID, PI, PI);
     }
+
+    @Test
+    public void compactString() {
+        ll = layoutLocation(SOME_ID, Type.GEO, PI, SQRT2);
+        String s = ll.toCompactListString();
+        assertEquals("wrong compactness", COMPACT_LL_1, s);
+    }
+
+    @Test
+    public void fromCompactStringTest() {
+        ll = fromCompactString(COMPACT_LL_1);
+        verifyLL1(ll);
+    }
+
+    private void verifyLL1(LayoutLocation ll) {
+        assertEquals("LL1 bad id", SOME_ID, ll.id());
+        assertEquals("LL1 bad type", Type.GEO, ll.locType());
+        assertEquals("LL1 bad Y", PI, ll.latOrY(), TOLERANCE);
+        assertEquals("LL1 bad X", SQRT2, ll.longOrX(), TOLERANCE);
+    }
+
+    private void verifyLL2(LayoutLocation ll) {
+        assertEquals("LL1 bad id", OTHER_ID, ll.id());
+        assertEquals("LL1 bad type", Type.GRID, ll.locType());
+        assertEquals("LL1 bad Y", SQRT2, ll.latOrY(), TOLERANCE);
+        assertEquals("LL1 bad X", PI, ll.longOrX(), TOLERANCE);
+    }
+
+
+    @Test(expected = IllegalArgumentException.class)
+    public void badCompactTooShort() {
+        fromCompactString("one,two,three");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void badCompactTooLong() {
+        fromCompactString("one,two,three,4,5");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void badCompactNoId() {
+        fromCompactString(",GEO,1,2");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void badCompactUnparsableY() {
+        fromCompactString("foo,GEO,yyy,2.3");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void badCompactUnparsableX() {
+        fromCompactString("foo,GEO,0.2,xxx");
+    }
+
+    @Test
+    public void toCompactList() {
+        ll = layoutLocation(SOME_ID, Type.GEO, PI, SQRT2);
+        ll2 = layoutLocation(OTHER_ID, Type.GRID, SQRT2, PI);
+        String compact = toCompactListString(ll, ll2);
+        print(compact);
+        assertEquals("wrong list encoding", COMPACT_LIST, compact);
+    }
+
+    @Test
+    public void toCompactList2() {
+        ll = layoutLocation(SOME_ID, Type.GEO, PI, SQRT2);
+        ll2 = layoutLocation(OTHER_ID, Type.GRID, SQRT2, PI);
+        List<LayoutLocation> locs = of(ll, ll2);
+        String compact = toCompactListString(locs);
+        print(compact);
+        assertEquals("wrong list encoding", COMPACT_LIST, compact);
+    }
+
+    @Test
+    public void fromCompactList() {
+        List<LayoutLocation> locs = fromCompactListString(COMPACT_LIST);
+        ll = locs.get(0);
+        ll2 = locs.get(1);
+        verifyLL1(ll);
+        verifyLL2(ll2);
+    }
+
+    @Test
+    public void fromCompactListNull() {
+        List<LayoutLocation> locs = fromCompactListString(null);
+        assertEquals("non-empty list", 0, locs.size());
+    }
+
+    @Test
+    public void fromCompactListEmpty() {
+        List<LayoutLocation> locs = fromCompactListString("");
+        assertEquals("non-empty list", 0, locs.size());
+    }
+
+    @Test
+    public void toCompactListStringNullList() {
+        String s = toCompactListString((List<LayoutLocation>) null);
+        assertEquals("not empty string", "", s);
+    }
+
+    @Test
+    public void toCompactListStringNullArray() {
+        String s = toCompactListString((LayoutLocation[]) null);
+        assertEquals("not empty string", "", s);
+    }
+
+    @Test
+    public void toCompactListStringEmptyArray() {
+        String s = toCompactListString();
+        assertEquals("not empty string", "", s);
+    }
+
+    @Test
+    public void toCompactListStringEmptyList() {
+        String s = toCompactListString(new ArrayList<>());
+        assertEquals("not empty string", "", s);
+    }
 }