Layout and Region configs.
- Listen for topo-layout config changes.
- Augmenting UiTopoLayout to include fields for geomap/sprite, scale/offset
Change-Id: I2b1f747f41d39b64b0a1a53946c4cbd5750db9e5
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 e75df69..4091f4e 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
@@ -132,12 +132,12 @@
/**
* Sets the name of the geomap (topojson file) to use for this layout.
*
- * @param geomap geomap name
+ * @param geomap geomap name; null to clear
* @return config for UI topology layout
* @throws InvalidFieldException if the sprites field is already set
*/
public BasicUiTopoLayoutConfig geomap(String geomap) {
- if (hasField(SPRITES)) {
+ if (geomap != null && hasField(SPRITES)) {
throw new InvalidFieldException(GEOMAP, E_SPRITES_ALREADY_SET);
}
setOrClear(GEOMAP, geomap);
@@ -156,12 +156,12 @@
/**
* Sets the name of the sprites definition to use for this layout.
*
- * @param sprites sprites definition name
+ * @param sprites sprites definition name; null to clear
* @return config for UI topology layout
* @throws InvalidFieldException if the geomap field is already set
*/
public BasicUiTopoLayoutConfig sprites(String sprites) {
- if (hasField(GEOMAP)) {
+ if (sprites != null && hasField(GEOMAP)) {
throw new InvalidFieldException(GEOMAP, E_GEOMAP_ALREADY_SET);
}
setOrClear(SPRITES, sprites);
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java b/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java
index 3a42dac..8872087 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoLayoutService.java
@@ -37,7 +37,7 @@
UiTopoLayout getRootLayout();
/**
- * Returns the set of available layouts.
+ * Returns the set of available layouts (not including the root layout).
*
* @return set of available layouts
*/
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java
index ade86e1..b37f1df 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopoLayout.java
@@ -16,36 +16,68 @@
package org.onosproject.ui.model.topo;
+import com.google.common.base.MoreObjects;
import org.onosproject.net.region.Region;
import org.onosproject.net.region.RegionId;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
/**
* Represents a specific "subset" of the UI model of the network topology
* that a user might wish to view. Backed by a {@link Region}.
+ * <p>
+ * These instances include information about which geo-map (or sprite definition)
+ * should be displayed, along with zoom and offset parameters.
*/
public class UiTopoLayout {
+ // package private for unit test access
+ static final double SCALE_MIN = 0.01;
+ static final double SCALE_MAX = 100.0;
+ static final double SCALE_DEFAULT = 1.0;
+ static final double OFFSET_DEFAULT = 0.0;
+
+ static final String E_ROOT_PARENT = "Cannot change parent ID of root layout";
+ static final String E_ROOT_REGION = "Cannot set region on root layout";
+ static final String E_SPRITES_SET = "Cannot set geomap if sprites is set";
+ static final String E_GEOMAP_SET = "Cannot set sprites if geomap is set";
+ static final String E_SCALE_OOB =
+ "Scale out of bounds; expected [" + SCALE_MIN + ".." + SCALE_MAX + "]";
+
private final UiTopoLayoutId id;
- private final Region region;
- private final UiTopoLayoutId parent;
+
+ private Region region;
+ private UiTopoLayoutId parent;
+ private String geomap;
+ private String sprites;
+ private double scale = SCALE_DEFAULT;
+ private double offsetX = OFFSET_DEFAULT;
+ private double offsetY = OFFSET_DEFAULT;
/**
* Created a new UI topology layout.
*
- * @param id layout identifier
- * @param region backing region
- * @param parent identifier of the parent layout
+ * @param id layout identifier
*/
- public UiTopoLayout(UiTopoLayoutId id, Region region, UiTopoLayoutId parent) {
+ public UiTopoLayout(UiTopoLayoutId id) {
+ checkNotNull(id, "layout ID cannot be null");
this.id = id;
- this.region = region;
+
// NOTE: root layout is its own parent...
- this.parent = parent != null ? parent : this.id;
+ if (isRoot()) {
+ parent = id;
+ }
}
- @Override
- public String toString() {
- return "{UiTopoLayout: " + id + "}";
+ /**
+ * Returns true if this layout instance is at the top of the
+ * hierarchy tree.
+ *
+ * @return true if this is the root layout
+ */
+ public boolean isRoot() {
+ return UiTopoLayoutId.DEFAULT_ID.equals(id);
}
/**
@@ -57,6 +89,37 @@
return id;
}
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id)
+ .add("region", region)
+ .add("parent", parent)
+ .add("geomap", geomap)
+ .add("sprites", sprites)
+ .add("scale", scale)
+ .add("offsetX", offsetX)
+ .add("offsetY", offsetY)
+ .toString();
+ }
+
+ /**
+ * Sets the backing region for this layout. Note that an exception will
+ * be thrown if this is the root layout.
+ *
+ * @param region the backing region
+ * @return self, for chaining
+ * @throws IllegalArgumentException if this is the root layout
+ */
+ public UiTopoLayout region(Region region) {
+ if (isRoot()) {
+ throw new IllegalArgumentException(E_ROOT_REGION);
+ }
+
+ this.region = region;
+ return this;
+ }
+
/**
* Returns the backing region with which this layout is associated. Note
* that this may be null (for the root layout).
@@ -76,8 +139,27 @@
* @return backing region identifier
*/
public RegionId regionId() {
- return isRoot() ? UiRegion.NULL_ID
- : (region == null ? null : region.id());
+ return isRoot() ? UiRegion.NULL_ID :
+ (region == null ? null : region.id());
+ }
+
+ /**
+ * Sets the identity of this layout's parent. May be null to unset.
+ * Note that an exception will be thrown if this is the root layout,
+ * since the parent of the root is always itself, and cannot be changed.
+ *
+ * @param parentId parent layout identifier
+ * @return self, for chaining
+ * @throws IllegalArgumentException if this instance is the root layout
+ */
+ public UiTopoLayout parent(UiTopoLayoutId parentId) {
+ if (isRoot()) {
+ throw new IllegalArgumentException(E_ROOT_PARENT);
+ }
+ // TODO: consider checking ancestry chain to prevent loops
+
+ parent = parentId;
+ return this;
}
/**
@@ -90,12 +172,129 @@
}
/**
- * Returns true if this layout instance is at the top of the
- * hierarchy tree.
+ * Sets the name of the geomap for this layout. This is the symbolic
+ * name for a "topojson" file containing a geographic map projection,
+ * to be displayed in the topology view, for this layout.
+ * <p>
+ * Since the geomap and sprites fields are mutually exclusive, this
+ * method will throw an exception if the sprites field is already set.
*
- * @return true if this is the root layout
+ * @param geomap the geomap name
+ * @return self, for chaining
+ * @throws IllegalArgumentException if the sprites field is not null
*/
- public boolean isRoot() {
- return id.equals(parent);
+ public UiTopoLayout geomap(String geomap) {
+ if (sprites != null) {
+ throw new IllegalArgumentException(E_SPRITES_SET);
+ }
+ this.geomap = geomap;
+ return this;
}
+
+ /**
+ * Returns the symbolic name for the geomap for this layout.
+ *
+ * @return name of geomap
+ */
+ public String geomap() {
+ return geomap;
+ }
+
+ /**
+ * Sets the name of the sprites definition for this layout. This is the
+ * symbolic name for a "json" file containing a definition of sprites,
+ * which render as a symbolic background (e.g. a campus, or floor plan),
+ * to be displayed in the topology view, for this layout.
+ * <p>
+ * Since the geomap and sprites fields are mutually exclusive, this
+ * method will throw an exception if the geomap field is already set.
+ *
+ * @param sprites the sprites definition name
+ * @return self, for chaining
+ * @throws IllegalArgumentException if the geomap field is not null
+ */
+ public UiTopoLayout sprites(String sprites) {
+ if (geomap != null) {
+ throw new IllegalArgumentException(E_GEOMAP_SET);
+ }
+ this.sprites = sprites;
+ return this;
+ }
+
+ /**
+ * Returns the symbolic name for the sprites definition for this layout.
+ *
+ * @return name of sprites definition
+ */
+ public String sprites() {
+ return sprites;
+ }
+
+ private boolean scaleWithinBounds(double scale) {
+ return scale >= SCALE_MIN && scale <= SCALE_MAX;
+ }
+
+ /**
+ * Sets the scale for the geomap / sprite image. Note that the
+ * acceptable bounds are from {@value #SCALE_MIN} to {@value #SCALE_MAX}.
+ *
+ * @param scale the scale
+ * @return self for chaining
+ * @throws IllegalArgumentException if the value is out of bounds
+ */
+ public UiTopoLayout scale(double scale) {
+ checkArgument(scaleWithinBounds(scale), E_SCALE_OOB);
+ this.scale = scale;
+ return this;
+ }
+
+ /**
+ * Returns the scale for the geomap / sprite image.
+ *
+ * @return the scale
+ */
+ public double scale() {
+ return scale;
+ }
+
+ /**
+ * Sets the x-offset value.
+ *
+ * @param offsetX x-offset
+ * @return self, for chaining
+ */
+ public UiTopoLayout offsetX(double offsetX) {
+ this.offsetX = offsetX;
+ return this;
+ }
+
+ /**
+ * Returns the x-offset value.
+ *
+ * @return the x-offset
+ */
+ public double offsetX() {
+ return offsetX;
+ }
+
+ /**
+ * Sets the y-offset value.
+ *
+ * @param offsetY y-offset
+ * @return self, for chaining
+ */
+ public UiTopoLayout offsetY(double offsetY) {
+ this.offsetY = offsetY;
+ return this;
+ }
+
+ /**
+ * Returns the y-offset value.
+ *
+ * @return the y-offset
+ */
+ public double offsetY() {
+ return offsetY;
+ }
+
}
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiTopoLayoutTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiTopoLayoutTest.java
new file mode 100644
index 0000000..b8db969
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiTopoLayoutTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+package org.onosproject.ui.model.topo;
+
+import org.junit.Test;
+import org.onosproject.net.region.DefaultRegion;
+import org.onosproject.net.region.Region;
+import org.onosproject.net.region.RegionId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.onosproject.net.region.Region.Type.CAMPUS;
+import static org.onosproject.net.region.RegionId.regionId;
+import static org.onosproject.ui.model.topo.UiTopoLayout.E_GEOMAP_SET;
+import static org.onosproject.ui.model.topo.UiTopoLayout.E_ROOT_PARENT;
+import static org.onosproject.ui.model.topo.UiTopoLayout.E_ROOT_REGION;
+import static org.onosproject.ui.model.topo.UiTopoLayout.E_SPRITES_SET;
+import static org.onosproject.ui.model.topo.UiTopoLayoutId.layoutId;
+
+/**
+ * Unit tests for {@link UiTopoLayout}.
+ */
+public class UiTopoLayoutTest {
+
+ private static final String AM_NOEX = "no exception thrown";
+ private static final String AM_WREXMSG = "wrong exception message";
+
+ private static final double DELTA = Double.MIN_VALUE * 2.0;
+
+ private static final UiTopoLayoutId OTHER_ID = layoutId("other-id");
+ private static final RegionId REGION_ID = regionId("some-region");
+ private static final Region REGION =
+ new DefaultRegion(REGION_ID, "Region-1", CAMPUS, null);
+ private static final String GEOMAP = "geo1";
+ private static final String SPRITE = "spr1";
+
+
+ private UiTopoLayout layout;
+
+ private void mkRootLayout() {
+ layout = new UiTopoLayout(UiTopoLayoutId.DEFAULT_ID);
+ }
+
+ private void mkOtherLayout() {
+ layout = new UiTopoLayout(OTHER_ID);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void nullIdentifier() {
+ layout = new UiTopoLayout(null);
+ }
+
+ @Test
+ public void rootLayout() {
+ mkRootLayout();
+ assertEquals("wrong id", UiTopoLayoutId.DEFAULT_ID, layout.id());
+ assertEquals("wrong parent (not self)",
+ UiTopoLayoutId.DEFAULT_ID, layout.parent());
+ assertTrue("should be root", layout.isRoot());
+
+ assertNull("unexpected region", layout.region());
+ assertEquals("unexpected region id", UiRegion.NULL_ID, layout.regionId());
+ }
+
+ @Test
+ public void otherLayout() {
+ mkOtherLayout();
+ assertEquals("wrong id", OTHER_ID, layout.id());
+ assertEquals("not null parent", null, layout.parent());
+ assertFalse("should NOT be root", layout.isRoot());
+
+ // check attribute default values...
+ assertNull("unexpected region", layout.region());
+ assertNull("unexpected region id", layout.regionId());
+ assertNull("unexpected geomap", layout.geomap());
+ assertNull("unexpected sprites", layout.sprites());
+ assertEquals("non-unity scale", 1.0, layout.scale(), DELTA);
+ assertEquals("non-zero x-off", 0.0, layout.offsetX(), DELTA);
+ assertEquals("non-zero y-off", 0.0, layout.offsetY(), DELTA);
+ }
+
+ @Test
+ public void setRegionOnRoot() {
+ mkRootLayout();
+ try {
+ layout.region(REGION);
+ fail(AM_NOEX);
+ } catch (IllegalArgumentException e) {
+ assertEquals(AM_WREXMSG, E_ROOT_REGION, e.getMessage());
+ }
+
+ try {
+ layout.region(null);
+ fail(AM_NOEX);
+ } catch (IllegalArgumentException e) {
+ assertEquals(AM_WREXMSG, E_ROOT_REGION, e.getMessage());
+ }
+ }
+
+ @Test
+ public void setRegionOnOther() {
+ mkOtherLayout();
+ layout.region(REGION);
+ assertEquals("wrong region", REGION, layout.region());
+ assertEquals("wrong region id", REGION_ID, layout.regionId());
+
+ layout.region(null);
+ assertEquals("non-null region", null, layout.region());
+ assertEquals("non-null region id", null, layout.regionId());
+ }
+
+ @Test
+ public void setParentOnRoot() {
+ mkRootLayout();
+ try {
+ layout.parent(OTHER_ID);
+ fail(AM_NOEX);
+ } catch (IllegalArgumentException e) {
+ assertEquals(AM_WREXMSG, E_ROOT_PARENT, e.getMessage());
+ }
+
+ try {
+ layout.parent(null);
+ fail(AM_NOEX);
+ } catch (IllegalArgumentException e) {
+ assertEquals(AM_WREXMSG, E_ROOT_PARENT, e.getMessage());
+ }
+ }
+
+ @Test
+ public void setParentOnOther() {
+ mkOtherLayout();
+ layout.parent(OTHER_ID);
+ assertEquals("wrong parent", OTHER_ID, layout.parent());
+
+ layout.parent(null);
+ assertEquals("non-null parent", null, layout.parent());
+ }
+
+ @Test
+ public void setGeomap() {
+ mkRootLayout();
+ assertEquals("geo to start", null, layout.geomap());
+ layout.geomap(GEOMAP);
+ assertEquals("wrong geo", GEOMAP, layout.geomap());
+ }
+
+ @Test
+ public void setGeomapAfterSprites() {
+ mkRootLayout();
+ layout.sprites(SPRITE);
+ assertEquals("geo to start", null, layout.geomap());
+ try {
+ layout.geomap(GEOMAP);
+ fail(AM_NOEX);
+ } catch (IllegalArgumentException e) {
+ assertEquals(AM_WREXMSG, E_SPRITES_SET, e.getMessage());
+ }
+ }
+
+ @Test
+ public void setSprites() {
+ mkRootLayout();
+ assertEquals("sprite to start", null, layout.sprites());
+ layout.sprites(SPRITE);
+ assertEquals("wrong sprite", SPRITE, layout.sprites());
+ }
+
+ @Test
+ public void setSpritesAfterGeomap() {
+ mkRootLayout();
+ layout.geomap(GEOMAP);
+ assertEquals("sprites to start", null, layout.sprites());
+ try {
+ layout.sprites(SPRITE);
+ fail(AM_NOEX);
+ } catch (IllegalArgumentException e) {
+ assertEquals(AM_WREXMSG, E_GEOMAP_SET, e.getMessage());
+ }
+ }
+
+ @Test
+ public void setScale() {
+ mkRootLayout();
+ layout.scale(3.0);
+ assertEquals("wrong scale", 3.0, layout.scale(), DELTA);
+ layout.scale(0.05);
+ assertEquals("wrong scale", 0.05, layout.scale(), DELTA);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void scaleTooSmall() {
+ mkRootLayout();
+ layout.scale(0.0099);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void scaleTooBig() {
+ mkRootLayout();
+ layout.scale(100.009);
+ }
+
+ @Test
+ public void setXOff() {
+ mkOtherLayout();
+ layout.offsetX(23.4);
+ assertEquals("wrong x-offset", 23.4, layout.offsetX(), DELTA);
+ }
+
+ @Test
+ public void setYOff() {
+ mkOtherLayout();
+ layout.offsetY(2.71828);
+ assertEquals("wrong y-offset", 2.71828, layout.offsetY(), DELTA);
+ }
+
+}