Implement mutation by DeviceEvent.

Change-Id: I6fadf22556bfd245dc70c32ee3a926309959fede
diff --git a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/NetworkGraphImpl.java b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/NetworkGraphImpl.java
index 2355b03..8367f16 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/networkgraph/NetworkGraphImpl.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/networkgraph/NetworkGraphImpl.java
@@ -1,5 +1,6 @@
 package net.onrc.onos.ofcontroller.networkgraph;
 
+import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -24,8 +25,7 @@
  * TODO To be synchronized based on TopologyEvent Notification.
  *
  * TODO TBD: Caller is expected to maintain parent/child calling order. Parent
- * Object must exist before adding sub component(Add Switch -> Port). Child
- * Object need to be removed before removing parent (Delete Port->Switch)
+ * Object must exist before adding sub component(Add Switch -> Port).
  *
  * TODO TBD: This class may delay the requested change to handle event
  * re-ordering. e.g.) Link Add came in, but Switch was not there.
@@ -374,88 +374,128 @@
 	    throw new IllegalArgumentException("Device cannot be null");
 	}
 
+	Device device = getDeviceByMac(deviceEvt.getMac());
+	if ( device == null ) {
+	    device = new DeviceImpl(this, deviceEvt.getMac());
+	    Device existing = mac2Device.putIfAbsent(deviceEvt.getMac(), device);
+	    if (existing != null) {
+		log.warn(
+			"Concurrent putDevice seems to be in action. Continuing updating {}",
+			existing);
+		device = existing;
+	    }
+	}
+	DeviceImpl memDevice = getDeviceImpl(device);
+
 	// for each attachment point
-	/// TODO check if Link exist on that Port
+	for (SwitchPort swp : deviceEvt.getAttachmentPoints() ) {
+	    // Attached Ports' Parent Switch must exist
+	    Switch sw = getSwitch(swp.dpid);
+	    if ( sw ==  null ) {
+		log.warn("Switch {} for the attachment point did not exist. skipping mutation", sw);
+		continue;
+	    }
+	    // Attached Ports must exist
+	    Port port = sw.getPort(swp.number);
+	    if ( port == null ) {
+		log.warn("Port {} for the attachment point did not exist. skipping mutation", port);
+		continue;
+	    }
+	    // Attached Ports must not have Link
+	    if ( port.getOutgoingLink() != null || port.getIncomingLink() != null ) {
+		log.warn("Link (Out:{},In:{}) exist on the attachment point, skipping mutation.", port.getOutgoingLink(), port.getIncomingLink());
+		continue;
+	    }
 
+	    // finally add Device <-> Port on In-memory structure
+	    PortImpl memPort = getPortImpl(port);
+	    memPort.addDevice(device);
+	    memDevice.addAttachmentPoint(port);
+	}
 
+	// for each IP address
+	for( InetAddress ipAddr : deviceEvt.getIpAddresses() ) {
+	    // Add Device -> IP
+	    memDevice.addIpAddress(ipAddr);
 
-	// TODO Auto-generated method stub
-
-	// Device existingDevice =
-	// getDeviceByMac(deviceToUpdate.getMacAddress());
-	// if (existingDevice != deviceToUpdate) {
-	// throw new IllegalArgumentException(
-	// "Must supply Device Object in this NetworkGraph");
-	// }
-	//
-	// DeviceImpl device = getDeviceImpl(deviceToUpdate);
-	//
-	// // Update IP Addr
-	// // uniq
-	// Set<InetAddress> prevAddrs = new HashSet<>(
-	// deviceToUpdate.getIpAddress());
-	// Set<InetAddress> newAddrs = updatedIpAddrs;
-	//
-	// // delta
-	// @SuppressWarnings("unchecked")
-	// Collection<InetAddress> delAddr = CollectionUtils.subtract(newAddrs,
-	// prevAddrs);
-	// @SuppressWarnings("unchecked")
-	// Collection<InetAddress> addAddr = CollectionUtils.subtract(prevAddrs,
-	// newAddrs);
-	//
-	// for (InetAddress addr : delAddr) {
-	// Set<Device> devices = addr2Device.get(addr);
-	// if (devices == null) {
-	// continue;
-	// }
-	// devices.remove(device);
-	// device.removeIpAddress(addr);
-	// }
-	// for (InetAddress addr : addAddr) {
-	// Set<Device> devices = addr2Device.get(addr);
-	// if (devices == null) {
-	// devices = new HashSet<>();
-	// addr2Device.put(addr, devices);
-	// }
-	// devices.add(device);
-	// device.addIpAddress(addr);
-	// }
-	//
-	// // Update Attachment Point
-	// // uniq
-	// Set<Port> prevPorts = new HashSet<>();
-	// CollectionUtils.addAll(prevAddrs,
-	// deviceToUpdate.getAttachmentPoints()
-	// .iterator());
-	// Set<Port> newPorts = updatedAttachmentPoints;
-	// // delta
-	// @SuppressWarnings("unchecked")
-	// Collection<Port> delPorts = CollectionUtils.subtract(newPorts,
-	// prevPorts);
-	// @SuppressWarnings("unchecked")
-	// Collection<Port> addPorts = CollectionUtils.subtract(prevPorts,
-	// newPorts);
-	//
-	// for (Port p : delPorts) {
-	// device.removeAttachmentPoint(p);
-	// getPortImpl(p).removeDevice(device);
-	// }
-	//
-	// for (Port p : addPorts) {
-	// device.addAttachmentPoint(p);
-	// getPortImpl(p).addDevice(device);
-	// }
-
-	// TODO Auto-generated method stub
-
+	    // Add IP -> Set<Device>
+	    boolean updated = false;
+	    do {
+		Set<Device> devices = this.addr2Device.get(ipAddr);
+		if ( devices == null ) {
+		    devices = new HashSet<>();
+		    Set<Device> existing = this.addr2Device.putIfAbsent(ipAddr, devices);
+		    if ( existing == null ) {
+			// success
+			updated = true;
+		    }
+		} else {
+		    Set<Device> updateDevices = new HashSet<>(devices);
+		    updateDevices.add(device);
+		    updated = this.addr2Device.replace(ipAddr, devices, updateDevices);
+		}
+		if (!updated) {
+		    log.debug("Collision detected, updating IP to Device mapping retrying.");
+		}
+	    } while( !updated );
+	}
     }
 
-    void removeDevice(DeviceEvent device) {
-	if (device == null) {
+    void removeDevice(DeviceEvent deviceEvt) {
+	if (deviceEvt == null) {
 	    throw new IllegalArgumentException("Device cannot be null");
 	}
-	// TODO Auto-generated method stub
+
+	Device device = getDeviceByMac(deviceEvt.getMac());
+	if ( device == null ) {
+	    log.warn("Device {} already removed, ignoring", deviceEvt);
+	    return;
+	}
+	DeviceImpl memDevice = getDeviceImpl(device);
+
+	// for each attachment point
+	for (SwitchPort swp : deviceEvt.getAttachmentPoints() ) {
+	    // Attached Ports' Parent Switch must exist
+	    Switch sw = getSwitch(swp.dpid);
+	    if ( sw ==  null ) {
+		log.warn("Switch {} for the attachment point did not exist. skipping mutation", sw);
+		continue;
+	    }
+	    // Attached Ports must exist
+	    Port port = sw.getPort(swp.number);
+	    if ( port == null ) {
+		log.warn("Port {} for the attachment point did not exist. skipping mutation", port);
+		continue;
+	    }
+
+	    // finally remove Device <-> Port on In-memory structure
+	    PortImpl memPort = getPortImpl(port);
+	    memPort.removeDevice(device);
+	    memDevice.removeAttachmentPoint(port);
+	}
+
+	// for each IP address
+	for( InetAddress ipAddr : deviceEvt.getIpAddresses() ) {
+	    // Remove Device -> IP
+	    memDevice.removeIpAddress(ipAddr);
+
+	    // Remove IP -> Set<Device>
+	    boolean updated = false;
+	    do {
+		Set<Device> devices = this.addr2Device.get(ipAddr);
+		if ( devices == null ) {
+		    // already empty set, nothing to do
+		    updated = true;
+		} else {
+		    Set<Device> updateDevices = new HashSet<>(devices);
+		    updateDevices.remove(device);
+		    updated = this.addr2Device.replace(ipAddr, devices, updateDevices);
+		}
+		if (!updated) {
+		    log.debug("Collision detected, updating IP to Device mapping retrying.");
+		}
+	    } while( !updated );
+	}
     }
 
     private SwitchImpl getSwitchImpl(Switch sw) {