Adding topology auto-layout.

Change-Id: I2b9e0b5808b8d193e8d08b7fe6ffdb007b083809
diff --git a/apps/layout/src/main/java/org/onosproject/layout/LayoutAlgorithm.java b/apps/layout/src/main/java/org/onosproject/layout/LayoutAlgorithm.java
new file mode 100644
index 0000000..f9e062b
--- /dev/null
+++ b/apps/layout/src/main/java/org/onosproject/layout/LayoutAlgorithm.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.layout;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.BasicDeviceConfig;
+import org.onosproject.net.config.basics.BasicHostConfig;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.link.LinkService;
+
+import java.util.Collection;
+
+/**
+ * Represents a topology layout algorithm.
+ */
+public abstract class LayoutAlgorithm {
+
+    public static final String SPINE = "spine";
+    public static final String AGGREGATION = "aggregation";
+    public static final String LEAF = "leaf";
+    public static final String ACCESS = "access";
+    public static final String GATEWAY = "gateway";
+    public static final String COMPUTE = "compute";
+
+    protected DeviceService deviceService;
+    protected HostService hostService;
+    protected LinkService linkService;
+    protected NetworkConfigService netConfigService;
+
+    protected ListMultimap<String, DeviceId> deviceCategories = ArrayListMultimap.create();
+    protected ListMultimap<String, HostId> hostCategories = ArrayListMultimap.create();
+
+
+    /**
+     * Initializes layout algorithm for operating on device and host inventory.
+     *
+     * @param deviceService        device service
+     * @param hostService          host service
+     * @param linkService          link service
+     * @param networkConfigService net config service
+     */
+    protected void init(DeviceService deviceService,
+                        HostService hostService,
+                        LinkService linkService,
+                        NetworkConfigService networkConfigService) {
+        this.deviceService = deviceService;
+        this.hostService = hostService;
+        this.linkService = linkService;
+        this.netConfigService = networkConfigService;
+    }
+
+    /**
+     * Places the specified device on the layout grid.
+     *
+     * @param id device identifier
+     * @param x grid X
+     * @param y grid Y
+     */
+    protected void place(DeviceId id, double x, double y) {
+        netConfigService.addConfig(id, BasicDeviceConfig.class)
+                .gridX(x).gridY(y).locType("grid").apply();
+    }
+
+    /**
+     * Places the specified device on the layout grid.
+     *
+     * @param id host identifier
+     * @param x grid X
+     * @param y grid Y
+     */
+    protected void place(HostId id, double x, double y) {
+        netConfigService.addConfig(id, BasicHostConfig.class)
+                .gridX(x).gridY(y).locType("grid").apply();
+    }
+
+    /**
+     * Computes grid coordinate for the i-th element of n-elements in a tier
+     * using a default gap of 400.
+     *
+     * @param i element index
+     * @param n number of elements
+     * @return grid Y
+     */
+    protected double c(int i, int n) {
+        return c(i, n, 400);
+    }
+
+    /**
+     * Computes grid coordinate for the i-th element of n-elements in a tier.
+     *
+     * @param i   element index
+     * @param n   number of elements
+     * @param gap gap width
+     * @return grid Y
+     */
+    protected double c(int i, int n, double gap) {
+        return c(i, n, gap, 0);
+    }
+
+    /**
+     * Computes grid coordinate for the i-th element of n-elements in a tier.
+     *
+     * @param i      element index
+     * @param n      number of elements
+     * @param gap    gap width
+     * @param offset additional Y offset
+     * @return grid Y
+     */
+    protected double c(int i, int n, double gap, double offset) {
+        return gap * (i - 1) - (gap * (n - 1)) / 2 + offset;
+    }
+
+    /**
+     * Places the specified collection of hosts (all presumably connected to
+     * the same network device) in a block.
+     *
+     * @param hosts       hosts to place
+     * @param gridX       grid X of the top of the block
+     * @param gridY       grid Y of the center of the block
+     * @param hostsPerRow number of hosts in a 'row'
+     * @param rowGap      gap width between rows
+     * @param colGap      gap width between columns
+     */
+    protected void placeHostBlock(Collection<HostId> hosts,
+                                  double gridX, double gridY, int hostsPerRow,
+                                  double rowGap, double colGap) {
+        double yStep = rowGap / hostsPerRow;
+        double y = gridY;
+        double x = gridX - (colGap * (hostsPerRow - 1)) / 2;
+        int i = 1;
+
+        for (HostId id : hosts) {
+            place(id, x, y);
+            if ((i % hostsPerRow) == 0) {
+                x = gridX - (colGap * (hostsPerRow - 1)) / 2;
+            } else {
+                x += colGap;
+                y += yStep;
+            }
+            i++;
+        }
+    }
+
+    /**
+     * Applies device and host classifications.
+     */
+    public void classify() {
+        deviceService.getDevices().forEach(this::classify);
+        hostService.getHosts().forEach(this::classify);
+    }
+
+    /**
+     * Classifies the specified device.
+     *
+     * @param device device to be classified
+     * @return true if classified
+     */
+    protected boolean classify(Device device) {
+        BasicDeviceConfig cfg = netConfigService.getConfig(device.id(), BasicDeviceConfig.class);
+        if (cfg != null && !cfg.roles().isEmpty()) {
+            cfg.roles().forEach(r -> deviceCategories.put(r, device.id()));
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Classifies the specified host.
+     *
+     * @param host host to be classified
+     * @return true if classified
+     */
+    protected boolean classify(Host host) {
+        BasicHostConfig cfg = netConfigService.getConfig(host.id(), BasicHostConfig.class);
+        if (cfg != null && !cfg.roles().isEmpty()) {
+            cfg.roles().forEach(r -> hostCategories.put(r, host.id()));
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Applies the specified layout algorithm.
+     */
+    abstract void apply();
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("deviceCategories", count(deviceCategories))
+                .add("hostCategories", count(hostCategories))
+                .toString();
+    }
+
+    private String count(ListMultimap<String, ? extends ElementId> categories) {
+        StringBuilder sb = new StringBuilder("[ ");
+        categories.keySet().forEach(k -> sb.append(k).append("=").append(categories.get(k).size()).append(" "));
+        return sb.append("]").toString();
+    }
+
+}