ONOS-5411: BasicRegionConfig and BasicUiTopoLayoutConfig.
- added setters to BasicRegionConfig.
- implemented BasicUiTopoLayoutConfig.
- also furnished unit tests.

Change-Id: I965ce5817c7f36b56e634a318989447071130c2a
diff --git a/core/api/src/main/java/org/onosproject/net/config/Config.java b/core/api/src/main/java/org/onosproject/net/config/Config.java
index 1b57cdb..a3824c8 100644
--- a/core/api/src/main/java/org/onosproject/net/config/Config.java
+++ b/core/api/src/main/java/org/onosproject/net/config/Config.java
@@ -29,6 +29,7 @@
 import org.onosproject.net.ConnectPoint;
 
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
@@ -86,7 +87,7 @@
      * @param delegate delegate context, or null for detached configs.
      */
     public final void init(S subject, String key, JsonNode node, ObjectMapper mapper,
-                     ConfigApplyDelegate delegate) {
+                           ConfigApplyDelegate delegate) {
         this.subject = checkNotNull(subject, "Subject cannot be null");
         this.key = key;
         this.node = checkNotNull(node, "Node cannot be null");
@@ -206,7 +207,7 @@
     /**
      * Clears the specified property.
      *
-     * @param name  property name
+     * @param name property name
      * @return self
      */
     protected Config<S> clear(String name) {
@@ -364,10 +365,10 @@
     /**
      * Gets the specified array property as a list of items.
      *
-     * @param name     property name
-     * @param function mapper from string to item
+     * @param name         property name
+     * @param function     mapper from string to item
      * @param defaultValue default value if property not set
-     * @param <T>      type of item
+     * @param <T>          type of item
      * @return list of items
      */
     protected <T> List<T> getList(String name, Function<String, T> function, List<T> defaultValue) {
@@ -402,6 +403,33 @@
     }
 
     /**
+     * Returns true if this config contains a field with the given name.
+     *
+     * @param name the field name
+     * @return true if field is present, false otherwise
+     */
+    protected boolean hasField(String name) {
+        return hasField(object, name);
+    }
+
+    /**
+     * Returns true if the given node contains a field with the given name.
+     *
+     * @param node the node to examine
+     * @param name the name to look for
+     * @return true if the node has a field with the given name, false otherwise
+     */
+    protected boolean hasField(ObjectNode node, String name) {
+        Iterator<String> fnames = node.fieldNames();
+        while (fnames.hasNext()) {
+            if (fnames.next().equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Indicates whether only the specified fields are present in the backing JSON.
      *
      * @param allowedFields allowed field names
@@ -415,7 +443,7 @@
      * Indicates whether only the specified fields are present in a particular
      * JSON object.
      *
-     * @param node node whose fields to check
+     * @param node          node whose fields to check
      * @param allowedFields allowed field names
      * @return true if only allowedFields are present; false otherwise
      */
@@ -443,7 +471,7 @@
      * Indicates whether all specified fields are present in a particular
      * JSON object.
      *
-     * @param node node whose fields to check
+     * @param node            node whose fields to check
      * @param mandatoryFields mandatory field names
      * @return true if all mandatory fields are present; false otherwise
      */
@@ -474,8 +502,8 @@
      * MAC address.
      *
      * @param objectNode JSON node
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -502,9 +530,9 @@
      * Indicates whether the specified field of a particular node holds a valid
      * IP address.
      *
-     * @param objectNode     node from whom to access the field
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
+     * @param objectNode node from whom to access the field
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -531,9 +559,9 @@
      * Indicates whether the specified field of a particular node holds a valid
      * IP prefix.
      *
-     * @param objectNode     node from whom to access the field
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
+     * @param objectNode node from whom to access the field
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -547,7 +575,7 @@
     /**
      * Indicates whether the specified field holds a valid transport layer port.
      *
-     * @param field JSON field name
+     * @param field    JSON field name
      * @param presence specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
@@ -561,8 +589,8 @@
      * transport layer port.
      *
      * @param objectNode node from whom to access the field
-     * @param field JSON field name
-     * @param presence specifies if field is optional or mandatory
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -590,8 +618,8 @@
      * connect point string.
      *
      * @param objectNode JSON node
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -620,9 +648,9 @@
      * string value.
      *
      * @param objectNode JSON node
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
-     * @param pattern  optional regex pattern
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
+     * @param pattern    optional regex pattern
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -655,9 +683,9 @@
      * valid number.
      *
      * @param objectNode JSON object
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
-     * @param minMax   optional min/max values
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
+     * @param minMax     optional min/max values
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -692,9 +720,9 @@
      * integer.
      *
      * @param objectNode JSON node
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
-     * @param minMax   optional min/max values
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
+     * @param minMax     optional min/max values
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -729,9 +757,9 @@
      * decimal number.
      *
      * @param objectNode JSON node
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
-     * @param minMax   optional min/max values
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
+     * @param minMax     optional min/max values
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -765,8 +793,8 @@
      * boolean value.
      *
      * @param objectNode JSON object node
-     * @param field    JSON field name
-     * @param presence specifies if field is optional or mandatory
+     * @param field      JSON field name
+     * @param presence   specifies if field is optional or mandatory
      * @return true if valid; false otherwise
      * @throws InvalidFieldException if the field is present but not valid
      */
@@ -794,9 +822,9 @@
      * Indicates whether a field in the node is present and of correct value or
      * not mandatory and absent.
      *
-     * @param objectNode JSON object node containing field to validate
-     * @param field name of field to validate
-     * @param presence specified if field is optional or mandatory
+     * @param objectNode         JSON object node containing field to validate
+     * @param field              name of field to validate
+     * @param presence           specified if field is optional or mandatory
      * @param validationFunction function which can be used to verify if the
      *                           node has the correct value
      * @return true if the field is as expected
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicRegionConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicRegionConfig.java
index 200b375..2d15fa5 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicRegionConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicRegionConfig.java
@@ -16,24 +16,55 @@
 
 package org.onosproject.net.config.basics;
 
+import com.google.common.base.MoreObjects;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.Config;
 import org.onosproject.net.region.Region;
 import org.onosproject.net.region.RegionId;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Basic configuration for network regions.
  */
 public final class BasicRegionConfig extends Config<RegionId> {
 
+    private static final String NAME = "name";
     private static final String TYPE = "type";
     private static final String DEVICES = "devices";
 
     @Override
     public boolean isValid() {
-        return hasOnlyFields(TYPE, DEVICES);
+        return hasOnlyFields(NAME, TYPE, DEVICES);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("name", name())
+                .add("type", type())
+                .add("devices", devices())
+                .toString();
+    }
+
+    /**
+     * Returns the region name.
+     *
+     * @return the region name
+     */
+    public String name() {
+        return get(NAME, null);
+    }
+
+    /**
+     * Sets the name of this region.
+     *
+     * @param name name of region, or null to unset
+     * @return the config of the region
+     */
+    public BasicRegionConfig name(String name) {
+        return (BasicRegionConfig) setOrClear(NAME, name);
     }
 
     /**
@@ -41,7 +72,7 @@
      *
      * @return the region type
      */
-    public Region.Type getType() {
+    public Region.Type type() {
         String t = get(TYPE, null);
         return t == null ? null : regionTypeFor(t);
     }
@@ -55,13 +86,32 @@
     }
 
     /**
+     * Sets the region type.
+     *
+     * @param type the region type, or null to unset
+     * @return the config of the region
+     */
+    public BasicRegionConfig type(Region.Type type) {
+        String t = type == null ? null : type.name().toLowerCase();
+        return (BasicRegionConfig) setOrClear(TYPE, t);
+    }
+
+    /**
      * Returns the identities of the devices in this region.
      *
      * @return list of device identifiers
      */
-    public List<DeviceId> getDevices() {
-        return getList(DEVICES, DeviceId::deviceId);
+    public List<DeviceId> devices() {
+        return object.has(DEVICES) ? getList(DEVICES, DeviceId::deviceId) : null;
     }
 
-    // TODO: implement setters
+    /**
+     * Sets the devices of this region.
+     *
+     * @param devices the device identifiers, or null to unset
+     * @return the config of the region
+     */
+    public BasicRegionConfig devices(Set<DeviceId> devices) {
+        return (BasicRegionConfig) setOrClear(DEVICES, devices);
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicUiTopoLayoutConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicUiTopoLayoutConfig.java
index 5ba8093..e75df69 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicUiTopoLayoutConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicUiTopoLayoutConfig.java
@@ -16,7 +16,9 @@
 
 package org.onosproject.net.config.basics;
 
+import com.google.common.base.MoreObjects;
 import org.onosproject.net.config.Config;
+import org.onosproject.net.config.InvalidFieldException;
 import org.onosproject.net.region.RegionId;
 import org.onosproject.ui.model.topo.UiTopoLayoutId;
 
@@ -24,37 +26,206 @@
 
 /**
  * Basic configuration for UI topology layouts.
+ * <p>
+ * Note that a layout configuration will include information about
+ * which background map (or sprites definition) to use, and at what
+ * relative scale and offset.
+ * <p>
+ * Note also that the {@code geomap} and {@code sprites} fields are
+ * mutually exclusive.
  */
 public class BasicUiTopoLayoutConfig extends Config<UiTopoLayoutId> {
 
-    private static final String REGION = "region";
-    private static final String PARENT = "parent";
+    static final String REGION = "region";
+    static final String PARENT = "parent";
+    static final String GEOMAP = "geomap";
+    static final String SPRITES = "sprites";
+    static final String SCALE = "scale";
+    static final String OFFSET_X = "offsetX";
+    static final String OFFSET_Y = "offsetY";
+
+    static final double DEFAULT_SCALE = 1.0;
+    static final double DEFAULT_OFFSET = 0.0;
+
+    private static final String E_GEOMAP_SPRITE =
+            "Layout cannot have both geomap and sprites defined";
+    private static final String E_SPRITES_ALREADY_SET =
+            "Can't set geomap when sprites is already set";
+    private static final String E_GEOMAP_ALREADY_SET =
+            "Can't set sprites when geomap is already set";
 
     @Override
     public boolean isValid() {
-        return hasOnlyFields(REGION, PARENT);
+        if (object.has(GEOMAP) && object.has(SPRITES)) {
+            throw new InvalidFieldException(GEOMAP, E_GEOMAP_SPRITE);
+        }
+
+        return hasOnlyFields(REGION, PARENT, GEOMAP, SPRITES, SCALE,
+                OFFSET_X, OFFSET_Y);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("region", region())
+                .add("parent", parent())
+                .add("geomap", geomap())
+                .add("sprites", sprites())
+                .add("scale", scale())
+                .add("offX", offsetX())
+                .add("offY", offsetY())
+                .toString();
     }
 
     /**
      * Returns the identifier of the backing region. This will be
      * null if there is no backing region.
      *
-     * @return backing region identity
+     * @return backing region identifier
      */
-    public RegionId getRegion() {
+    public RegionId region() {
         String r = get(REGION, null);
         return r == null ? null : regionId(r);
     }
 
     /**
+     * Sets the identifier of the backing region.
+     *
+     * @param id backing region identifier, or null to unset
+     * @return config for UI topology layout
+     */
+    public BasicUiTopoLayoutConfig region(RegionId id) {
+        setOrClear(REGION, id == null ? null : id.id());
+        return this;
+    }
+
+    /**
      * Returns the identifier of the parent layout.
      *
      * @return layout identifier of parent
      */
-    public UiTopoLayoutId getParent() {
+    public UiTopoLayoutId parent() {
         String p = get(PARENT, null);
         return p == null ? UiTopoLayoutId.DEFAULT_ID : UiTopoLayoutId.layoutId(p);
     }
 
-    // TODO: implement setters
+    /**
+     * Sets the identifier of the parent layout.
+     *
+     * @param id parent ui-topo-layout identifier, or null to unset
+     * @return config for UI topology layout
+     */
+    public BasicUiTopoLayoutConfig parent(UiTopoLayoutId id) {
+        setOrClear(PARENT, id == null ? null : id.id());
+        return this;
+    }
+
+    /**
+     * Returns the identifier for the background geo-map.
+     *
+     * @return geo-map identifier
+     */
+    public String geomap() {
+        return get(GEOMAP, null);
+    }
+
+    /**
+     * Sets the name of the geomap (topojson file) to use for this layout.
+     *
+     * @param geomap geomap name
+     * @return config for UI topology layout
+     * @throws InvalidFieldException if the sprites field is already set
+     */
+    public BasicUiTopoLayoutConfig geomap(String geomap) {
+        if (hasField(SPRITES)) {
+            throw new InvalidFieldException(GEOMAP, E_SPRITES_ALREADY_SET);
+        }
+        setOrClear(GEOMAP, geomap);
+        return this;
+    }
+
+    /**
+     * Returns the identifier for the background sprites.
+     *
+     * @return sprites identifier
+     */
+    public String sprites() {
+        return get(SPRITES, null);
+    }
+
+    /**
+     * Sets the name of the sprites definition to use for this layout.
+     *
+     * @param sprites sprites definition name
+     * @return config for UI topology layout
+     * @throws InvalidFieldException if the geomap field is already set
+     */
+    public BasicUiTopoLayoutConfig sprites(String sprites) {
+        if (hasField(GEOMAP)) {
+            throw new InvalidFieldException(GEOMAP, E_GEOMAP_ALREADY_SET);
+        }
+        setOrClear(SPRITES, sprites);
+        return this;
+    }
+
+    /**
+     * Returns the scale for the geomap / sprites background.
+     *
+     * @return scale of background map / diagram
+     */
+    public double scale() {
+        return get(SCALE, DEFAULT_SCALE);
+    }
+
+    /**
+     * Sets the scale for the geomap / sprites background.
+     *
+     * @param scale the scale to set
+     * @return config for UI topology layout
+     */
+    public BasicUiTopoLayoutConfig scale(Double scale) {
+        setOrClear(SCALE, scale);
+        return this;
+    }
+
+    /**
+     * Returns the x-offset for the geomap / sprites background.
+     *
+     * @return x-offset of background map / diagram
+     */
+    public double offsetX() {
+        return get(OFFSET_X, DEFAULT_OFFSET);
+    }
+
+    /**
+     * Sets the x-offset for the geomap / sprites background.
+     *
+     * @param offsetX the x-offset to set
+     * @return config for UI topology layout
+     */
+    public BasicUiTopoLayoutConfig offsetX(Double offsetX) {
+        setOrClear(OFFSET_X, offsetX);
+        return this;
+    }
+
+    /**
+     * Returns the y-offset for the geomap / sprites background.
+     *
+     * @return y-offset of background map / diagram
+     */
+    public double offsetY() {
+        return get(OFFSET_Y, DEFAULT_OFFSET);
+    }
+
+    /**
+     * Sets the scale for the geomap / sprites background.
+     *
+     * @param offsetY the y-offset to set
+     * @return config for UI topology layout
+     */
+    public BasicUiTopoLayoutConfig offsetY(Double offsetY) {
+        setOrClear(OFFSET_Y, offsetY);
+        return this;
+    }
+
 }