Gather topology element info to TopologyImpl

- Moved all the self-contained topology elements (*Event) to
  TopologyImpl. (ONOS-1651)
  Now {Switch, Port, Link, Host}Impl is just a handler attached to
  TopologyImpl.
- BugFix: TopologyManager.addHost(HostEvent)
  HostEvent could be pushed to reorder queue multiple times,
  if multiple attachment point was given.
- BugFix: TopologyManager.{addLink, removePort}
  Properly handle if Host attachment point was removed as side-effect.
- BugFix: Copy HostEvent#lastSeenTime
- BugFix: Event instance notified to listeners (api*Events) should be
  the event which was/will be in the replica. (TopologyManager)
- Added/Modified debug log in TopologyManager so that log will be in
  same format for each event type.
  "{Added, Update, Removed} <Self-contained>"
- Removed backdoor method and use TestUtils instead.

Change-Id: If053d6f11f39574a188e7a52cb6194114f8afe5d
diff --git a/src/main/java/net/onrc/onos/core/topology/HostEvent.java b/src/main/java/net/onrc/onos/core/topology/HostEvent.java
index 4c8f78d..c35764e 100644
--- a/src/main/java/net/onrc/onos/core/topology/HostEvent.java
+++ b/src/main/java/net/onrc/onos/core/topology/HostEvent.java
@@ -59,6 +59,7 @@
         super(original);
         this.mac = original.mac;
         this.attachmentPoints = new ArrayList<>(original.attachmentPoints);
+        this.lastSeenTime = original.lastSeenTime;
     }
 
 
diff --git a/src/main/java/net/onrc/onos/core/topology/HostImpl.java b/src/main/java/net/onrc/onos/core/topology/HostImpl.java
index 469d335..d0964a1 100644
--- a/src/main/java/net/onrc/onos/core/topology/HostImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/HostImpl.java
@@ -9,50 +9,29 @@
 import net.onrc.onos.core.util.SwitchPort;
 
 /**
- * Host Object stored in In-memory Topology.
+ * Handler to Host object stored in In-memory Topology snapshot.
+ * <p/>
  */
 public class HostImpl extends TopologyObject implements Host {
 
-    //////////////////////////////////////////////////////
-    /// Topology element attributes
-    ///  - any changes made here needs to be replicated.
-    //////////////////////////////////////////////////////
-    private HostEvent deviceObj;
+    private final MACAddress id;
 
 
     /**
-     * Creates a Host object based on {@link HostEvent}.
-     *
-     * @param topology Topology instance this object belongs to
-     * @param scHost self contained {@link HostEvent}
-     */
-    public HostImpl(Topology topology, HostEvent scHost) {
-        super(topology);
-        Validate.notNull(scHost);
-
-        // TODO should we assume deviceObj is already frozen before this call
-        //      or expect attribute update will happen after .
-        if (scHost.isFrozen()) {
-            this.deviceObj = scHost;
-        } else {
-            this.deviceObj = new HostEvent(scHost);
-            this.deviceObj.freeze();
-        }
-    }
-
-    /**
-     * Creates a Host object with empty attributes.
+     * Creates a Host handler object.
      *
      * @param topology Topology instance this object belongs to
      * @param mac MAC address of the host
      */
-    public HostImpl(Topology topology, MACAddress mac) {
-        this(topology, new HostEvent(mac).freeze());
+    HostImpl(TopologyInternal topology, MACAddress mac) {
+        super(topology);
+        Validate.notNull(mac);
+        this.id = mac;
     }
 
     @Override
     public MACAddress getMacAddress() {
-        return this.deviceObj.getMac();
+        return id;
     }
 
     @Override
@@ -60,7 +39,7 @@
         List<Port> ports = new ArrayList<>();
         topology.acquireReadLock();
         try {
-            for (SwitchPort swp : this.deviceObj.getAttachmentPoints()) {
+            for (SwitchPort swp : getHostEvent().getAttachmentPoints()) {
                 Port p = this.topology.getPort(swp);
                 if (p != null) {
                     ports.add(p);
@@ -74,7 +53,7 @@
 
     @Override
     public long getLastSeenTime() {
-        return deviceObj.getLastSeenTime();
+        return this.topology.getHostEvent(id).getLastSeenTime();
     }
 
     @Override
@@ -82,45 +61,16 @@
         return getMacAddress().toString();
     }
 
-    // TODO we may no longer need this. confirm and delete later.
-    void setLastSeenTime(long lastSeenTime) {
-        // XXX Following will make this instance thread unsafe. Need to use AtomicRef.
-        HostEvent updated = new HostEvent(this.deviceObj);
-        updated.setLastSeenTime(lastSeenTime);
-        updated.freeze();
-        this.deviceObj = updated;
-    }
-
     /**
-     * Only {@link TopologyManager} should use this method.
+     * Gets the current HostEvent.
      *
-     * @param port the port that the device is attached to
+     * @return HostEvent
      */
-    void addAttachmentPoint(Port port) {
-        // XXX Following will make this instance thread unsafe. Need to use AtomicRef.
-        HostEvent updated = new HostEvent(this.deviceObj);
-        updated.removeAttachmentPoint(port.asSwitchPort());
-        updated.addAttachmentPoint(port.asSwitchPort());
-        updated.freeze();
-        this.deviceObj = updated;
+    private HostEvent getHostEvent() {
+        return this.topology.getHostEvent(id);
     }
 
     /**
-     * Only {@link TopologyManager} should use this method.
-     *
-     * @param port the port that the device is attached to
-     */
-    boolean removeAttachmentPoint(Port port) {
-        // XXX Following will make this instance thread unsafe. Need to use AtomicRef.
-        HostEvent updated = new HostEvent(this.deviceObj);
-        final boolean result = updated.removeAttachmentPoint(port.asSwitchPort());
-        updated.freeze();
-        this.deviceObj = updated;
-        return result;
-    }
-
-
-    /**
      * Returns the type of topology object.
      *
      * @return the type of the topology object
diff --git a/src/main/java/net/onrc/onos/core/topology/LinkImpl.java b/src/main/java/net/onrc/onos/core/topology/LinkImpl.java
index 5ad0194..8379279 100644
--- a/src/main/java/net/onrc/onos/core/topology/LinkImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/LinkImpl.java
@@ -7,63 +7,36 @@
 import org.apache.commons.lang.Validate;
 
 /**
- * Link Object stored in In-memory Topology.
+ * Handler to Link object stored in In-memory Topology snapshot.
  * <p/>
- * TODO REMOVE following design memo: This object itself may hold the DBObject,
- * but this Object itself will not issue any read/write to the DataStore.
  */
 public class LinkImpl extends TopologyObject implements Link {
 
-    //////////////////////////////////////////////////////
-    /// Topology element attributes
-    ///  - any changes made here needs to be replicated.
-    //////////////////////////////////////////////////////
-    private LinkEvent linkObj;
+    private final LinkTuple id;
 
 
     /**
-     * Creates a Link object based on {@link LinkEvent}.
+     * Creates a Link handler object.
      *
      * @param topology Topology instance this object belongs to
-     * @param scPort self contained {@link LinkEvent}
+     * @param linkTuple Link identifier
      */
-    public LinkImpl(Topology topology, LinkEvent scPort) {
+    LinkImpl(TopologyInternal topology, LinkTuple linkTuple) {
         super(topology);
-        Validate.notNull(scPort);
-
-        // TODO should we assume linkObj is already frozen before this call
-        //      or expect attribute update will happen after .
-        if (scPort.isFrozen()) {
-            this.linkObj = scPort;
-        } else {
-            this.linkObj = new LinkEvent(scPort);
-            this.linkObj.freeze();
-        }
-    }
-
-    /**
-     * Creates a Link object with empty attributes.
-     *
-     * @param topology Topology instance this object belongs to
-     * @param srcPort source port
-     * @param dstPort destination port
-     */
-    public LinkImpl(Topology topology, Port srcPort, Port dstPort) {
-        this(topology,
-             new LinkEvent(srcPort.asSwitchPort(),
-                           dstPort.asSwitchPort()).freeze());
+        Validate.notNull(linkTuple);
+        this.id = linkTuple;
     }
 
     @Override
     public LinkTuple getLinkTuple() {
-        return linkObj.getLinkTuple();
+        return id;
     }
 
     @Override
     public Switch getSrcSwitch() {
         topology.acquireReadLock();
         try {
-            return topology.getSwitch(linkObj.getSrc().getDpid());
+            return topology.getSwitch(id.getSrc().getDpid());
         } finally {
             topology.releaseReadLock();
         }
@@ -73,7 +46,7 @@
     public Port getSrcPort() {
         topology.acquireReadLock();
         try {
-            return topology.getPort(linkObj.getSrc());
+            return topology.getPort(id.getSrc());
         } finally {
             topology.releaseReadLock();
         }
@@ -83,7 +56,7 @@
     public Switch getDstSwitch() {
         topology.acquireReadLock();
         try {
-            return topology.getSwitch(linkObj.getDst().getDpid());
+            return topology.getSwitch(id.getDst().getDpid());
         } finally {
             topology.releaseReadLock();
         }
@@ -93,7 +66,7 @@
     public Port getDstPort() {
         topology.acquireReadLock();
         try {
-            return topology.getPort(linkObj.getDst());
+            return topology.getPort(id.getDst());
         } finally {
             topology.releaseReadLock();
         }
@@ -107,38 +80,12 @@
 
     @Override
     public Double getCapacity() {
-        return this.linkObj.getCapacity();
+        return this.topology.getLinkEvent(id).getCapacity();
     }
 
-    void setCapacity(Double capacity) {
-        if (this.linkObj.isFrozen()) {
-            this.linkObj = new LinkEvent(this.linkObj);
-            this.linkObj.setCapacity(capacity);
-            this.linkObj.freeze();
-        } else {
-            this.linkObj.setCapacity(capacity);
-        }
-    }
-
-    // XXX actually replaces everything
-    void replaceStringAttributes(LinkEvent updated) {
-        Validate.isTrue(this.linkObj.getSrc().equals(updated.getSrc()),
-                "Wrong LinkEvent given.");
-        Validate.isTrue(this.linkObj.getDst().equals(updated.getDst()),
-                "Wrong LinkEvent given.");
-
-        // XXX simply replacing whole self-contained object for now
-        if (updated.isFrozen()) {
-            this.linkObj = updated;
-        } else {
-            this.linkObj = new LinkEvent(updated).freeze();
-        }
-    }
-
-
     @Override
     public String getStringAttribute(String attr) {
-        return linkObj.getStringAttribute(attr);
+        return this.topology.getLinkEvent(id).getStringAttribute(attr);
     }
 
     @Override
@@ -153,7 +100,7 @@
 
     @Override
     public Map<String, String> getAllStringAttributes() {
-        return linkObj.getAllStringAttributes();
+        return this.topology.getLinkEvent(id).getAllStringAttributes();
     }
 
     @Override
diff --git a/src/main/java/net/onrc/onos/core/topology/Port.java b/src/main/java/net/onrc/onos/core/topology/Port.java
index 4758b16..50feb99 100644
--- a/src/main/java/net/onrc/onos/core/topology/Port.java
+++ b/src/main/java/net/onrc/onos/core/topology/Port.java
@@ -120,11 +120,10 @@
      */
     public Collection<Link> getIncomingLinks();
 
-    // XXX Iterable or Collection?
     /**
      * Gets all the devices attached to this port.
      *
      * @return {@link Host}s attached to this port
      */
-    public Iterable<Host> getHosts();
+    public Collection<Host> getHosts();
 }
diff --git a/src/main/java/net/onrc/onos/core/topology/PortImpl.java b/src/main/java/net/onrc/onos/core/topology/PortImpl.java
index 935b3c5..4e0da5e 100644
--- a/src/main/java/net/onrc/onos/core/topology/PortImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/PortImpl.java
@@ -10,61 +10,35 @@
 import net.onrc.onos.core.util.SwitchPort;
 
 /**
- * Port Object stored in In-memory Topology.
+ * Handler to Port object stored in In-memory Topology snapshot.
  * <p/>
- * TODO REMOVE following design memo: This object itself may hold the DBObject,
- * but this Object itself will not issue any read/write to the DataStore.
  */
 public class PortImpl extends TopologyObject implements Port {
 
-    //////////////////////////////////////////////////////
-    /// Topology element attributes
-    ///  - any changes made here needs to be replicated.
-    //////////////////////////////////////////////////////
-    private PortEvent portObj;
+    private final SwitchPort id;
 
 
     /**
-     * Creates a Port object based on {@link PortEvent}.
-     *
-     * @param topology Topology instance this object belongs to
-     * @param scPort self contained {@link PortEvent}
-     */
-    public PortImpl(Topology topology, PortEvent scPort) {
-        super(topology);
-        Validate.notNull(scPort);
-
-        // TODO should we assume portObj is already frozen before this call
-        //      or expect attribute update will happen after .
-        if (scPort.isFrozen()) {
-            this.portObj = scPort;
-        } else {
-            this.portObj = new PortEvent(scPort);
-            this.portObj.freeze();
-        }
-    }
-
-    /**
-     * Creates a Port object with empty attributes.
+     * Creates a Port handler object.
      *
      * @param topology Topology instance this object belongs to
      * @param switchPort SwitchPort
      */
-    public PortImpl(Topology topology, SwitchPort switchPort) {
-        this(topology, new PortEvent(switchPort).freeze());
+    PortImpl(TopologyInternal topology, SwitchPort switchPort) {
+        super(topology);
+        Validate.notNull(switchPort);
+        this.id = switchPort;
     }
 
     /**
-     * Creates a Port object with empty attributes.
+     * Creates a Port handler object.
      *
      * @param topology Topology instance this object belongs to
      * @param dpid DPID
      * @param number PortNumber
      */
-    public PortImpl(Topology topology, Dpid dpid, PortNumber number) {
+    PortImpl(TopologyInternal topology, Dpid dpid, PortNumber number) {
         this(topology, new SwitchPort(dpid, number));
-        Validate.notNull(dpid);
-        Validate.notNull(number);
     }
 
     @Override
@@ -79,7 +53,7 @@
 
     @Override
     public SwitchPort asSwitchPort() {
-        return portObj.getSwitchPort();
+        return id;
     }
 
     @Override
@@ -87,12 +61,6 @@
         return getStringAttribute(PortEvent.DESCRIPTION, "");
     }
 
-    void setDescription(String description) {
-//        portObj.createStringAttribute(attr, value);
-        // TODO implement using attributes
-        throw new UnsupportedOperationException("Not implemented yet");
-    }
-
     @Override
     public Long getHardwareAddress() {
         // TODO implement using attributes?
@@ -170,7 +138,7 @@
     }
 
     @Override
-    public Iterable<Host> getHosts() {
+    public Collection<Host> getHosts() {
         topology.acquireReadLock();
         try {
             return topology.getHosts(this.asSwitchPort());
@@ -179,21 +147,9 @@
         }
     }
 
-    void replaceStringAttributes(PortEvent updated) {
-        Validate.isTrue(this.asSwitchPort().equals(updated.getSwitchPort()),
-                "Wrong PortEvent given.");
-
-        // XXX simply replacing whole self-contained object for now
-        if (updated.isFrozen()) {
-            this.portObj = updated;
-        } else {
-            this.portObj = new PortEvent(updated).freeze();
-        }
-    }
-
     @Override
     public String getStringAttribute(String attr) {
-        return portObj.getStringAttribute(attr);
+        return this.topology.getPortEvent(id).getStringAttribute(attr);
     }
 
     @Override
@@ -208,7 +164,7 @@
 
     @Override
     public Map<String, String> getAllStringAttributes() {
-        return portObj.getAllStringAttributes();
+        return this.topology.getPortEvent(id).getAllStringAttributes();
     }
 
     @Override
diff --git a/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java b/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java
index b0486b0..6be8b13 100644
--- a/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/SwitchImpl.java
@@ -14,53 +14,30 @@
 import org.apache.commons.lang.Validate;
 
 /**
- * Switch Object stored in In-memory Topology.
+ * Handler to Switch object stored in In-memory Topology snapshot.
  * <p/>
- * TODO REMOVE following design memo: This object itself may hold the DBObject,
- * but this Object itself will not issue any read/write to the DataStore.
+ *
  */
 public class SwitchImpl extends TopologyObject implements Switch {
 
-    //////////////////////////////////////////////////////
-    /// Topology element attributes
-    ///  - any changes made here needs to be replicated.
-    //////////////////////////////////////////////////////
-    private SwitchEvent switchObj;
+    private final Dpid id;
 
 
     /**
-     * Creates a Switch object with empty attributes.
+     * Creates a Switch handler object.
      *
      * @param topology Topology instance this object belongs to
      * @param dpid DPID
      */
-    public SwitchImpl(Topology topology, Dpid dpid) {
-        this(topology, new SwitchEvent(dpid).freeze());
-    }
-
-    /**
-     * Creates a Switch object based on {@link SwitchEvent}.
-     *
-     * @param topology Topology instance this object belongs to
-     * @param scSw self contained {@link SwitchEvent}
-     */
-    public SwitchImpl(Topology topology, SwitchEvent scSw) {
+    SwitchImpl(TopologyInternal topology, Dpid dpid) {
         super(topology);
-        Validate.notNull(scSw);
-
-        // TODO should we assume switchObj is already frozen before this call
-        //      or expect attribute update will happen after .
-        if (scSw.isFrozen()) {
-            this.switchObj = scSw;
-        } else {
-            this.switchObj = new SwitchEvent(scSw);
-            this.switchObj.freeze();
-        }
+        Validate.notNull(dpid);
+        this.id = dpid;
     }
 
     @Override
     public Dpid getDpid() {
-        return switchObj.getDpid();
+        return this.id;
     }
 
     @Override
@@ -121,18 +98,6 @@
         return hosts;
     }
 
-    void replaceStringAttributes(SwitchEvent updated) {
-        Validate.isTrue(this.getDpid().equals(updated.getDpid()),
-                "Wrong SwitchEvent given.");
-
-        // XXX simply replacing whole self-contained object for now
-        if (updated.isFrozen()) {
-            this.switchObj = updated;
-        } else {
-            this.switchObj = new SwitchEvent(updated).freeze();
-        }
-    }
-
     @Override
     public Iterable<Link> getOutgoingLinks() {
         LinkedList<Link> links = new LinkedList<Link>();
@@ -159,7 +124,7 @@
 
     @Override
     public String getStringAttribute(String attr) {
-        return this.switchObj.getStringAttribute(attr);
+        return this.topology.getSwitchEvent(getDpid()).getStringAttribute(attr);
     }
 
     @Override
@@ -174,7 +139,7 @@
 
     @Override
     public Map<String, String> getAllStringAttributes() {
-        return this.switchObj.getAllStringAttributes();
+        return this.topology.getSwitchEvent(getDpid()).getAllStringAttributes();
     }
 
     @Override
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyImpl.java b/src/main/java/net/onrc/onos/core/topology/TopologyImpl.java
index decab6b..d79d204 100644
--- a/src/main/java/net/onrc/onos/core/topology/TopologyImpl.java
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyImpl.java
@@ -6,6 +6,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.locks.Lock;
@@ -16,6 +17,7 @@
 
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.LinkTuple;
 import net.onrc.onos.core.util.PortNumber;
 import net.onrc.onos.core.util.SwitchPort;
 
@@ -26,90 +28,113 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
 
-public class TopologyImpl implements Topology {
-    @SuppressWarnings("unused")
+
+/**
+ * Class to represent an instance of Topology Snapshot.
+ */
+public class TopologyImpl implements Topology, TopologyInternal {
+
     private static final Logger log = LoggerFactory.getLogger(TopologyImpl.class);
 
+    // TODO Revisit Map types after implementing CoW/lock-free
+
     // DPID -> Switch
-    private final ConcurrentMap<Dpid, Switch> switches;
-    // XXX may need to be careful when shallow copying.
-    private final ConcurrentMap<Dpid, ConcurrentMap<PortNumber, Port>> ports;
+    private final ConcurrentMap<Dpid, SwitchEvent> switches;
+    private final ConcurrentMap<Dpid, ConcurrentMap<PortNumber, PortEvent>> ports;
 
     // Index from Port to Host
-    private final Multimap<SwitchPort, Host> hosts;
-    private final ConcurrentMap<MACAddress, Host> mac2Host;
+    private final Multimap<SwitchPort, HostEvent> hosts;
+    private final ConcurrentMap<MACAddress, HostEvent> mac2Host;
 
     // SwitchPort -> (type -> Link)
-    private final ConcurrentMap<SwitchPort, ConcurrentMap<String, Link>> outgoingLinks;
-    private final ConcurrentMap<SwitchPort, ConcurrentMap<String, Link>> incomingLinks;
+    private final ConcurrentMap<SwitchPort, ConcurrentMap<String, LinkEvent>> outgoingLinks;
+    private final ConcurrentMap<SwitchPort, ConcurrentMap<String, LinkEvent>> incomingLinks;
 
     private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
     private Lock readLock = readWriteLock.readLock();
     // TODO use the write lock after refactor
     private Lock writeLock = readWriteLock.writeLock();
 
+    /**
+     * Create an empty Topology.
+     */
     public TopologyImpl() {
         // TODO: Does these object need to be stored in Concurrent Collection?
         switches = new ConcurrentHashMap<>();
         ports = new ConcurrentHashMap<>();
         hosts = Multimaps.synchronizedMultimap(
-                HashMultimap.<SwitchPort, Host>create());
+                HashMultimap.<SwitchPort, HostEvent>create());
         mac2Host = new ConcurrentHashMap<>();
         outgoingLinks = new ConcurrentHashMap<>();
         incomingLinks = new ConcurrentHashMap<>();
     }
 
+    /**
+     * Create a shallow copy of given Topology.
+     *
+     * @param original Topology
+     */
+    public TopologyImpl(TopologyImpl original) {
+        original.acquireReadLock();
+        try {
+            this.switches = new ConcurrentHashMap<>(original.switches);
+
+            // shallow copy Map in Map
+            this.ports = new ConcurrentHashMap<>(original.ports.size());
+            for (Entry<Dpid, ConcurrentMap<PortNumber, PortEvent>> entry
+                    : original.ports.entrySet()) {
+                this.ports.put(entry.getKey(), new ConcurrentHashMap<>(entry.getValue()));
+            }
+
+            this.hosts = Multimaps.synchronizedMultimap(
+                    HashMultimap.<SwitchPort, HostEvent>create(original.hosts));
+            this.mac2Host = new ConcurrentHashMap<>(original.mac2Host);
+
+            // shallow copy Map in Map
+            this.outgoingLinks = new ConcurrentHashMap<>(original.outgoingLinks.size());
+            for (Entry<SwitchPort, ConcurrentMap<String, LinkEvent>> entry
+                    : original.outgoingLinks.entrySet()) {
+                this.outgoingLinks.put(entry.getKey(), new ConcurrentHashMap<>(entry.getValue()));
+            }
+
+            // shallow copy Map in Map
+            this.incomingLinks = new ConcurrentHashMap<>(original.incomingLinks.size());
+            for (Entry<SwitchPort, ConcurrentMap<String, LinkEvent>> entry
+                    : original.incomingLinks.entrySet()) {
+                this.incomingLinks.put(entry.getKey(), new ConcurrentHashMap<>(entry.getValue()));
+            }
+        } finally {
+            original.releaseReadLock();
+        }
+    }
+
     @Override
     public Switch getSwitch(Dpid dpid) {
-        // TODO Check if it is safe to directly return this Object.
-        return switches.get(dpid);
-    }
-
-    // Only add switch.
-    protected void putSwitch(Switch sw) {
-        switches.put(sw.getDpid(), sw);
-        ports.putIfAbsent(sw.getDpid(), new ConcurrentHashMap<PortNumber, Port>());
-    }
-
-    // XXX Will remove ports in snapshot as side-effect.
-    protected void removeSwitch(Dpid dpid) {
-        switches.remove(dpid);
-        ports.remove(dpid);
-    }
-
-    // This method is expected to be serialized by writeLock.
-    protected void putPort(Port port) {
-        ConcurrentMap<PortNumber, Port> portMap = ports.get(port.getDpid());
-        if (portMap == null) {
-            portMap = new ConcurrentHashMap<>();
-            ConcurrentMap<PortNumber, Port> existing =
-                    ports.putIfAbsent(port.getDpid(), portMap);
-            if (existing != null) {
-                // port map was added concurrently, using theirs
-                portMap = existing;
-            }
-        }
-        portMap.put(port.getNumber(), port);
-    }
-
-    protected void removePort(Port port) {
-        ConcurrentMap<PortNumber, Port> portMap = ports.get(port.getDpid());
-        if (portMap != null) {
-            portMap.remove(port.getNumber());
+        final SwitchEvent sw = switches.get(dpid);
+        if (sw != null) {
+            return new SwitchImpl(this, dpid);
+        } else {
+            return null;
         }
     }
 
     @Override
     public Iterable<Switch> getSwitches() {
-        // TODO Check if it is safe to directly return this Object.
-        return Collections.unmodifiableCollection(switches.values());
+        List<Switch> list = new ArrayList<>(switches.size());
+        for (SwitchEvent elm : switches.values()) {
+            list.add(new SwitchImpl(this, elm.getDpid()));
+        }
+        return list;
     }
 
     @Override
     public Port getPort(Dpid dpid, PortNumber number) {
-        ConcurrentMap<PortNumber, Port> portMap = ports.get(dpid);
+        ConcurrentMap<PortNumber, PortEvent> portMap = ports.get(dpid);
         if (portMap != null) {
-            return portMap.get(number);
+            final PortEvent port = portMap.get(number);
+            if (port != null) {
+                return new PortImpl(this, port.getSwitchPort());
+            }
         }
         return null;
     }
@@ -121,11 +146,15 @@
 
     @Override
     public Collection<Port> getPorts(Dpid dpid) {
-        ConcurrentMap<PortNumber, Port> portMap = ports.get(dpid);
+        ConcurrentMap<PortNumber, PortEvent> portMap = ports.get(dpid);
         if (portMap == null) {
             return Collections.emptyList();
         }
-        return Collections.unmodifiableCollection(portMap.values());
+        List<Port> list = new ArrayList<>(portMap.size());
+        for (PortEvent elm : portMap.values()) {
+            list.add(new PortImpl(this, elm.getSwitchPort()));
+        }
+        return list;
     }
 
     @Override
@@ -135,32 +164,33 @@
 
     @Override
     public Link getOutgoingLink(SwitchPort port) {
-        Map<String, Link> links = outgoingLinks.get(port);
+        Map<String, LinkEvent> links = outgoingLinks.get(port);
         return getPacketLinkIfExists(links);
     }
 
     // TODO remove when we no longer need packet fall back behavior
     /**
-     * Gets the "packet" link if such exists, if not return whatever found.
+     * Gets the "packet" link if such exists,
+     * if not return whichever link is found first.
      *
      * @param links Collection of links to search from
      * @return Link instance found or null if no link exists
      */
-    private Link getPacketLinkIfExists(Map<String, Link> links) {
+    private Link getPacketLinkIfExists(Map<String, LinkEvent> links) {
 
         if (links == null) {
             return null;
         }
 
-        Link link = links.get(TopologyElement.TYPE_PACKET_LAYER);
+        LinkEvent link = links.get(TopologyElement.TYPE_PACKET_LAYER);
         if (link != null) {
             // return packet link
-            return link;
+            return new LinkImpl(this, link.getLinkTuple());
         } else {
             // return whatever found
-            Iterator<Link> it = links.values().iterator();
+            Iterator<LinkEvent> it = links.values().iterator();
             if (it.hasNext()) {
-                return it.next();
+                return new LinkImpl(this, it.next().getLinkTuple());
             }
         }
         return null;
@@ -173,13 +203,38 @@
 
     @Override
     public Link getOutgoingLink(SwitchPort port, String type) {
-        Map<String, Link> links = outgoingLinks.get(port);
-        return links.get(type);
+        Map<String, LinkEvent> links = outgoingLinks.get(port);
+        final LinkEvent link = links.get(type);
+        if (link != null) {
+            return new LinkImpl(this, link.getLinkTuple());
+        }
+        return null;
     }
 
     @Override
     public Collection<Link> getOutgoingLinks(SwitchPort port) {
-        return Collections.unmodifiableCollection(outgoingLinks.get(port).values());
+        ConcurrentMap<String, LinkEvent> typeMap = outgoingLinks.get(port);
+        if (typeMap == null) {
+            return Collections.emptyList();
+        }
+        return toLinkImpls(typeMap.values());
+    }
+
+    /**
+     * Converts collection of LinkEvent to collection of LinkImpls.
+     *
+     * @param links collection of LinkEvent
+     * @return collection of LinkImpls
+     */
+    private Collection<Link> toLinkImpls(final Collection<LinkEvent> links) {
+        if (links == null) {
+            return Collections.emptyList();
+        }
+        List<Link> list = new ArrayList<>(links.size());
+        for (LinkEvent elm : links) {
+            list.add(new LinkImpl(this, elm.getLinkTuple()));
+        }
+        return list;
     }
 
     @Override
@@ -189,7 +244,7 @@
 
     @Override
     public Link getIncomingLink(SwitchPort port) {
-        Map<String, Link> links = incomingLinks.get(port);
+        Map<String, LinkEvent> links = incomingLinks.get(port);
         return getPacketLinkIfExists(links);
     }
 
@@ -200,13 +255,21 @@
 
     @Override
     public Link getIncomingLink(SwitchPort port, String type) {
-        Map<String, Link> links = incomingLinks.get(port);
-        return links.get(type);
+        Map<String, LinkEvent> links = incomingLinks.get(port);
+        final LinkEvent link = links.get(type);
+        if (link != null) {
+            return new LinkImpl(this, link.getLinkTuple());
+        }
+        return null;
     }
 
     @Override
     public Collection<Link> getIncomingLinks(SwitchPort port) {
-        return Collections.unmodifiableCollection(incomingLinks.get(port).values());
+        ConcurrentMap<String, LinkEvent> typeMap = incomingLinks.get(port);
+        if (typeMap == null) {
+            return Collections.emptyList();
+        }
+        return toLinkImpls(typeMap.values());
     }
 
     @Override
@@ -248,94 +311,300 @@
     public Iterable<Link> getLinks() {
         List<Link> links = new ArrayList<>();
 
-        for (Map<String, Link> portLinks : outgoingLinks.values()) {
-            links.addAll(portLinks.values());
+        for (Map<String, LinkEvent> portLinks : outgoingLinks.values()) {
+            if (portLinks == null) {
+                continue;
+            }
+            for (LinkEvent elm : portLinks.values()) {
+                links.add(new LinkImpl(this, elm.getLinkTuple()));
+            }
         }
         return links;
     }
 
-    @GuardedBy("topology.writeLock")
-    protected void putLink(Link link) {
-        putLinkMap(outgoingLinks, link.getSrcPort().asSwitchPort(), link);
-        putLinkMap(incomingLinks, link.getDstPort().asSwitchPort(), link);
+    @Override
+    public Host getHostByMac(MACAddress address) {
+        HostEvent host = mac2Host.get(address);
+        if (host != null) {
+            return new HostImpl(this, address);
+        }
+        return null;
+    }
+
+    @Override
+    public Iterable<Host> getHosts() {
+        return toHostImpls(mac2Host.values());
+    }
+
+    /**
+     * Converts collection of HostEvent to collection of HostImpl.
+     *
+     * @param events collection of HostEvent
+     * @return collection of HostImpl
+     */
+    private List<Host> toHostImpls(Collection<HostEvent> events) {
+        if (events == null) {
+            return Collections.emptyList();
+        }
+        List<Host> list = new ArrayList<>(events.size());
+        for (HostEvent elm : events) {
+            list.add(new HostImpl(this, elm.getMac()));
+        }
+        return list;
+    }
+
+    @Override
+    public Collection<Host> getHosts(SwitchPort port) {
+        return toHostImpls(hosts.get(port));
+    }
+
+    @Override
+    public SwitchEvent getSwitchEvent(final Dpid dpid) {
+        return this.switches.get(dpid);
+    }
+
+    @Override
+    public PortEvent getPortEvent(final SwitchPort port) {
+        ConcurrentMap<PortNumber, PortEvent> portMap = this.ports.get(port.getDpid());
+        if (portMap != null) {
+            return portMap.get(port.getPortNumber());
+        }
+        return null;
+    }
+
+    @Override
+    public LinkEvent getLinkEvent(final LinkTuple linkId) {
+        ConcurrentMap<String, LinkEvent> links = this.outgoingLinks.get(linkId.getSrc());
+        if (links == null) {
+            return null;
+        }
+
+        // TODO Should we look for Packet link first?
+        //  Not unless invariant is broken.
+
+        for (LinkEvent link : links.values()) {
+            if (link.getDst().equals(linkId.getDst())) {
+                return link;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public LinkEvent getLinkEvent(final LinkTuple linkId, final String type) {
+        ConcurrentMap<String, LinkEvent> links = this.outgoingLinks.get(linkId.getSrc());
+        if (links == null) {
+            return null;
+        }
+        return links.get(type);
+    }
+
+    @Override
+    public Collection<LinkEvent> getLinkEvents(final LinkTuple linkId) {
+        ConcurrentMap<String, LinkEvent> links = this.outgoingLinks.get(linkId.getSrc());
+        if (links == null) {
+            return Collections.emptyList();
+        }
+
+        // unless invariant is broken, this should contain at most 1 element.
+        return Collections.unmodifiableCollection(links.values());
+    }
+
+    @Override
+    public HostEvent getHostEvent(final MACAddress mac) {
+        return this.mac2Host.get(mac);
+    }
+
+    /**
+     * Puts a SwitchEvent.
+     *
+     * @param sw Switch to add. (Will be frozen if not already)
+     */
+    @GuardedBy("writeLock")
+    protected void putSwitch(SwitchEvent sw) {
+        // TODO isFrozen check once we implement CoW/lock-free
+        switches.put(sw.getDpid(), sw.freeze());
+        ports.putIfAbsent(sw.getDpid(), new ConcurrentHashMap<PortNumber, PortEvent>());
+    }
+
+    /**
+     * Removes a SwitchEvent from this snapshot.
+     * <p/>
+     * Will also remove ports, if it has not been removed already.
+     *
+     * @param dpid Switch DPID
+     */
+    @GuardedBy("writeLock")
+    protected void removeSwitch(Dpid dpid) {
+        // TODO isFrozen check once we implement CoW/lock-free
+        switches.remove(dpid);
+        ConcurrentMap<PortNumber, PortEvent> removedPorts = ports.remove(dpid);
+        if (removedPorts != null && !removedPorts.isEmpty()) {
+            log.warn("Some ports were removed as side-effect of #removeSwitch({})", dpid);
+        }
+    }
+
+    /**
+     * Puts a PortEvent.
+     *
+     * @param port Port to add. (Will be frozen if not already)
+     */
+    @GuardedBy("writeLock")
+    protected void putPort(PortEvent port) {
+
+        ConcurrentMap<PortNumber, PortEvent> portMap = ports.get(port.getDpid());
+        if (portMap == null) {
+            portMap = new ConcurrentHashMap<>();
+            ConcurrentMap<PortNumber, PortEvent> existing
+                = ports.putIfAbsent(port.getDpid(), portMap);
+            if (existing != null) {
+                // port map was added concurrently, using theirs
+                portMap = existing;
+            }
+        }
+        portMap.put(port.getPortNumber(), port.freeze());
+    }
+
+    /**
+     * Removes a PortEvent from this snapshot.
+     *
+     * @param port SwitchPort to remove
+     */
+    @GuardedBy("writeLock")
+    protected void removePort(SwitchPort port) {
+        removePort(port.getDpid(), port.getPortNumber());
+    }
+
+    /**
+     * Removes a PortEvent from this snapshot.
+     * <p/>
+     * Will also remove ports, if it has not been removed already.
+     *
+     * @param dpid Switch DPID
+     * @param number PortNumber
+     */
+    @GuardedBy("writeLock")
+    protected void removePort(Dpid dpid, PortNumber number) {
+        // TODO sanity check Host attachment point.
+        ConcurrentMap<PortNumber, PortEvent> portMap = ports.get(dpid);
+        if (portMap != null) {
+            portMap.remove(number);
+        }
+    }
+
+    /**
+     * Puts a LinkEvent.
+     *
+     * @param link LinkEvent
+     */
+    @GuardedBy("writeLock")
+    protected void putLink(LinkEvent link) {
+        // TODO Do sanity check?
+        //   - There cannot be 2 links in same direction between a port pair.
+        putLinkMap(outgoingLinks, link.getSrc(), link);
+        putLinkMap(incomingLinks, link.getDst(), link);
     }
 
     /**
      * Helper method to update outgoingLinks, incomingLinks.
      *
-     * @param linkMap outgoingLinks or incomingLinks
-     * @param port Map key
+     * @param linkMap outgoingLinks or incomingLinks to update
+     * @param port {@code linkMap} key to update
      * @param link Link to add
      */
-    @GuardedBy("topology.writeLock")
-    private void putLinkMap(ConcurrentMap<SwitchPort, ConcurrentMap<String, Link>> linkMap,
-                            SwitchPort port, Link link) {
-        ConcurrentMap<String, Link> portLinks = new ConcurrentHashMap<String, Link>(3);
-        portLinks.put(link.getType(), link);
-        Map<String, Link> existing = linkMap.putIfAbsent(
+    @GuardedBy("writeLock")
+    private void putLinkMap(ConcurrentMap<SwitchPort, ConcurrentMap<String, LinkEvent>> linkMap,
+                            SwitchPort port, LinkEvent link) {
+
+        ConcurrentMap<String, LinkEvent> linksOnPort = linkMap.get(port);
+        if (linksOnPort == null) {
+            linksOnPort = new ConcurrentHashMap<>(4);
+            ConcurrentMap<String, LinkEvent> existing
+                = linkMap.putIfAbsent(
                     port,
-                    portLinks);
-        if (existing != null) {
-            // no conditional update here
-            existing.put(link.getType(), link);
+                    linksOnPort);
+
+            if (existing != null) {
+                linksOnPort = existing;
+            }
         }
+        linksOnPort.put(link.getType(), link);
     }
 
-    @GuardedBy("topology.writeLock")
-    protected void removeLink(Link link) {
-        ConcurrentMap<String, Link> portLinks = outgoingLinks.get(link.getSrcPort().asSwitchPort());
+    /**
+     * Removes a LinkEvent from this snapshot.
+     *
+     * @param link Link to remove
+     * @param type type of link to remove
+     */
+    @GuardedBy("writeLock")
+    protected void removeLink(LinkTuple link, String type) {
+        ConcurrentMap<String, LinkEvent> portLinks
+            = outgoingLinks.get(link.getSrc());
         if (portLinks != null) {
             // no conditional update here
-            portLinks.remove(link.getType());
+            portLinks.remove(type);
         }
-        portLinks = incomingLinks.get(link.getDstPort().asSwitchPort());
+        portLinks
+            = incomingLinks.get(link.getDst());
         if (portLinks != null) {
             // no conditional update here
-            portLinks.remove(link.getType());
+            portLinks.remove(type);
         }
     }
 
-    @Override
-    public Host getHostByMac(MACAddress address) {
-        return mac2Host.get(address);
-    }
-
-    @Override
-    public Iterable<Host> getHosts() {
-        return Collections.unmodifiableCollection(mac2Host.values());
-    }
-
-    @Override
-    public Collection<Host> getHosts(SwitchPort port) {
-        return Collections.unmodifiableCollection(hosts.get(port));
-    }
-
-    // This method is expected to be serialized by writeLock.
-    // XXX new or updated device
-    protected void putHost(Host host) {
-        // assuming Host is immutable
-        Host oldHost = mac2Host.get(host.getMacAddress());
-        if (oldHost != null) {
-            // remove old attachment point
-            removeHost(oldHost);
+    /**
+     * Removes a LinkEvent from this snapshot.
+     *
+     * @param link Link to remove
+     */
+    @GuardedBy("writeLock")
+    protected void removeLink(LinkTuple link) {
+        Collection<LinkEvent> links = getLinkEvents(link);
+        for (LinkEvent l : links) {
+            removeLink(link, l.getType());
         }
+    }
+
+    /**
+     * Puts a HostEvent.
+     * <p/>
+     * Removes attachment points for previous HostEvent and update
+     * them with new HostEvent
+     *
+     * @param host HostEvent
+     */
+    @GuardedBy("writeLock")
+    protected void putHost(HostEvent host) {
+        // Host cannot be simply put() to replace instance since it has mobility.
+        // Simply remove -> put for now.
+
+        // remove old attachment points
+        removeHost(host.getMac());
+
         // add new attachment points
-        for (Port port : host.getAttachmentPoints()) {
-            // TODO Won't need remove() if we define Host equality to reflect
-            //      all of it's fields.
-            hosts.remove(port.asSwitchPort(), host);
-            hosts.put(port.asSwitchPort(), host);
+        for (SwitchPort port : host.getAttachmentPoints()) {
+            hosts.put(port, host);
         }
-        mac2Host.put(host.getMacAddress(), host);
+        mac2Host.put(host.getMac(), host);
     }
 
-    protected void removeHost(Host host) {
-        for (Port port : host.getAttachmentPoints()) {
-            hosts.remove(port.asSwitchPort(), host);
+    /**
+     * Removes a HostEvent from this snapshot.
+     *
+     * @param mac MACAddress of the Host to remove
+     */
+    @GuardedBy("writeLock")
+    protected void removeHost(MACAddress mac) {
+        HostEvent host = mac2Host.remove(mac);
+        if (host != null) {
+            for (SwitchPort port : host.getAttachmentPoints()) {
+                hosts.remove(port, host);
+            }
         }
-        mac2Host.remove(host.getMacAddress());
     }
 
+
     @Override
     public void acquireReadLock() {
         readLock.lock();
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyInternal.java b/src/main/java/net/onrc/onos/core/topology/TopologyInternal.java
new file mode 100644
index 0000000..10b008e
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyInternal.java
@@ -0,0 +1,64 @@
+package net.onrc.onos.core.topology;
+
+import java.util.Collection;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.LinkTuple;
+import net.onrc.onos.core.util.SwitchPort;
+
+/**
+ * Interface to reference internal self-contained elements.
+ */
+public interface TopologyInternal extends Topology {
+
+    /**
+     * Gets a SwitchEvent.
+     *
+     * @param dpid Switch DPID
+     * @return the SwitchEvent for the Switch DPID if found, otherwise null
+     */
+    public SwitchEvent getSwitchEvent(Dpid dpid);
+
+    /**
+     * Gets a PortEvent.
+     *
+     * @param port Port identifier
+     * @return the PortEvent for the Port identifier if found, otherwise null
+     */
+    public PortEvent getPortEvent(SwitchPort port);
+
+    /**
+     * Gets a LinkEvent.
+     *
+     * @param linkId Link identifier
+     * @return the LinkEvent for the Link identifier if found, otherwise null
+     */
+    public LinkEvent getLinkEvent(LinkTuple linkId);
+
+    /**
+     * Gets a LinkEvent.
+     *
+     * @param linkId Link identifier
+     * @param type type
+     * @return the LinkEvent for the Link identifier and type if found, otherwise null
+     */
+    public LinkEvent getLinkEvent(final LinkTuple linkId, final String type);
+
+    /**
+     * Gets a LinkEvent.
+     *
+     * @param linkId Link identifier
+     * @return Collection of LinkEvent
+     */
+    public Collection<LinkEvent> getLinkEvents(LinkTuple linkId);
+
+    /**
+     * Gets a HostEvent.
+     *
+     * @param mac MACAddress of the host
+     * @return the HostEvent for the MACAddress if found, otherwise null
+     */
+    public HostEvent getHostEvent(MACAddress mac);
+
+}
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyManager.java b/src/main/java/net/onrc/onos/core/topology/TopologyManager.java
index bdc6d67..7e7f2b8 100644
--- a/src/main/java/net/onrc/onos/core/topology/TopologyManager.java
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyManager.java
@@ -2,13 +2,16 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -153,7 +156,7 @@
     /**
      * Event handler class.
      */
-    private class EventHandler extends Thread implements
+    class EventHandler extends Thread implements
             IEventChannelListener<byte[], TopologyEvent> {
         private BlockingQueue<EventEntry<TopologyEvent>> topologyEvents =
                 new LinkedBlockingQueue<EventEntry<TopologyEvent>>();
@@ -243,6 +246,8 @@
                 //
                 // Extract the events
                 //
+                // FIXME Following event squashing logic based only on ID
+                //       potentially lose attribute change.
                 switch (event.eventType()) {
                     case ENTRY_ADD:
                         log.debug("Topology event ENTRY_ADD: {}", topologyEvent);
@@ -836,6 +841,10 @@
         }
     }
 
+    //
+    // Methods to update topology replica
+    //
+
     /**
      * Adds a switch to the topology replica.
      *
@@ -843,16 +852,15 @@
      */
     @GuardedBy("topology.writeLock")
     private void addSwitch(SwitchEvent switchEvent) {
-        Switch sw = topology.getSwitch(switchEvent.getDpid());
-        if (sw == null) {
-            sw = new SwitchImpl(topology, switchEvent);
-            topology.putSwitch(sw);
-        } else {
-            // TODO: Update the switch attributes
-            log.debug("Update switch attributes");
-            SwitchImpl impl = (SwitchImpl) sw;
-            impl.replaceStringAttributes(switchEvent);
+        if (log.isDebugEnabled()) {
+            SwitchEvent sw = topology.getSwitchEvent(switchEvent.getDpid());
+            if (sw != null) {
+                log.debug("Update {}", switchEvent);
+            } else {
+                log.debug("Added {}", switchEvent);
+            }
         }
+        topology.putSwitch(switchEvent.freeze());
         apiAddedSwitchEvents.add(switchEvent);
     }
 
@@ -865,8 +873,10 @@
      */
     @GuardedBy("topology.writeLock")
     private void removeSwitch(SwitchEvent switchEvent) {
-        Switch sw = topology.getSwitch(switchEvent.getDpid());
-        if (sw == null) {
+        final Dpid dpid = switchEvent.getDpid();
+
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        if (swInTopo == null) {
             log.warn("Switch {} already removed, ignoring", switchEvent);
             return;
         }
@@ -875,19 +885,19 @@
         // Remove all Ports on the Switch
         //
         ArrayList<PortEvent> portsToRemove = new ArrayList<>();
-        for (Port port : sw.getPorts()) {
+        for (Port port : topology.getPorts(dpid)) {
             log.warn("Port {} on Switch {} should be removed prior to removing Switch. Removing Port now.",
                     port, switchEvent);
-            PortEvent portEvent = new PortEvent(port.getDpid(),
-                    port.getNumber());
+            PortEvent portEvent = new PortEvent(port.asSwitchPort());
             portsToRemove.add(portEvent);
         }
         for (PortEvent portEvent : portsToRemove) {
             removePort(portEvent);
         }
 
-        topology.removeSwitch(switchEvent.getDpid());
-        apiRemovedSwitchEvents.add(switchEvent);
+        log.debug("Removed {}", swInTopo);
+        topology.removeSwitch(dpid);
+        apiRemovedSwitchEvents.add(swInTopo);
     }
 
     /**
@@ -906,100 +916,101 @@
             return;
         }
 
-
-        Port port = sw.getPort(portEvent.getPortNumber());
-        if (port == null) {
-            port = new PortImpl(topology, portEvent);
-            topology.putPort(port);
-        } else {
-            // TODO: Update the port attributes
-            log.debug("Update port attributes");
-            PortImpl impl = (PortImpl) port;
-            impl.replaceStringAttributes(portEvent);
+        if (log.isDebugEnabled()) {
+            PortEvent port = topology.getPortEvent(portEvent.getSwitchPort());
+            if (port != null) {
+                log.debug("Update {}", portEvent);
+            } else {
+                log.debug("Added {}", portEvent);
+            }
         }
+        topology.putPort(portEvent.freeze());
         apiAddedPortEvents.add(portEvent);
     }
 
     /**
      * Removes a port from the topology replica.
      * <p/>
-     * It will call {@link #removeHost(HostEvent)} for each hosts on this port
+     * It will remove attachment points from each hosts on this port
      * and call {@link #removeLink(LinkEvent)} for each links on this port.
      *
      * @param portEvent the PortEvent with the port to remove.
      */
     @GuardedBy("topology.writeLock")
     private void removePort(PortEvent portEvent) {
-        Switch sw = topology.getSwitch(portEvent.getDpid());
+        SwitchEvent sw = topology.getSwitchEvent(portEvent.getDpid());
         if (sw == null) {
             log.warn("Parent Switch for Port {} already removed, ignoring",
                     portEvent);
             return;
         }
 
-        Port port = sw.getPort(portEvent.getPortNumber());
-        if (port == null) {
+        final SwitchPort switchPort = portEvent.getSwitchPort();
+        PortEvent portInTopo = topology.getPortEvent(switchPort);
+        if (portInTopo == null) {
             log.warn("Port {} already removed, ignoring", portEvent);
             return;
         }
 
         //
-        // Remove all Hosts attached to the Port
+        // Remove all Host attachment points bound to this Port
         //
-        ArrayList<HostEvent> hostsToRemove = new ArrayList<>();
-        for (Host host : port.getHosts()) {
-            log.debug("Removing Host {} on Port {}", host, portEvent);
-            HostEvent hostEvent = new HostEvent(host.getMacAddress());
-            SwitchPort switchPort = new SwitchPort(port.getSwitch().getDpid(),
-                    port.getNumber());
-            hostEvent.addAttachmentPoint(switchPort);
-            hostsToRemove.add(hostEvent);
+        List<HostEvent> hostsToUpdate = new ArrayList<>();
+        for (Host host : topology.getHosts(switchPort)) {
+            log.debug("Removing Host {} on Port {}", host, portInTopo);
+            HostEvent hostEvent = topology.getHostEvent(host.getMacAddress());
+            hostsToUpdate.add(hostEvent);
         }
-        for (HostEvent hostEvent : hostsToRemove) {
-            removeHost(hostEvent);
+        for (HostEvent hostEvent : hostsToUpdate) {
+            HostEvent newHostEvent = new HostEvent(hostEvent);
+            newHostEvent.removeAttachmentPoint(switchPort);
+            newHostEvent.freeze();
+
+            // TODO should this event be fired inside #addHost?
+            if (newHostEvent.getAttachmentPoints().isEmpty()) {
+                // No more attachment point left -> remove Host
+                removeHost(hostEvent);
+            } else {
+                // Update Host
+                addHost(newHostEvent);
+            }
         }
 
         //
         // Remove all Links connected to the Port
         //
         Set<Link> links = new HashSet<>();
-        links.add(port.getOutgoingLink());
-        links.add(port.getIncomingLink());
-        ArrayList<LinkEvent> linksToRemove = new ArrayList<>();
+        links.addAll(topology.getOutgoingLinks(switchPort));
+        links.addAll(topology.getIncomingLinks(switchPort));
         for (Link link : links) {
             if (link == null) {
                 continue;
             }
-            log.debug("Removing Link {} on Port {}", link, portEvent);
-            LinkEvent linkEvent = new LinkEvent(link.getSrcSwitch().getDpid(),
-                    link.getSrcPort().getNumber(),
-                    link.getDstSwitch().getDpid(),
-                    link.getDstPort().getNumber());
-            linksToRemove.add(linkEvent);
-        }
-        for (LinkEvent linkEvent : linksToRemove) {
-            removeLink(linkEvent);
+            LinkEvent linkEvent = topology.getLinkEvent(link.getLinkTuple());
+            if (linkEvent != null) {
+                log.debug("Removing Link {} on Port {}", link, portInTopo);
+                removeLink(linkEvent);
+            }
         }
 
-        // Remove the Port from the Switch
-        topology.removePort(port);
+        // Remove the Port from Topology
+        log.debug("Removed {}", portInTopo);
+        topology.removePort(switchPort);
 
-        apiRemovedPortEvents.add(portEvent);
+        apiRemovedPortEvents.add(portInTopo);
     }
 
     /**
      * Adds a link to the topology replica.
      * <p/>
-     * It will call {@link #removeHost(HostEvent)} for each hosts on both ports.
+     * It will remove attachment points from each hosts using the same ports.
      *
      * @param linkEvent the LinkEvent with the link to add.
      */
     @GuardedBy("topology.writeLock")
     private void addLink(LinkEvent linkEvent) {
-        Port srcPort = topology.getPort(linkEvent.getSrc().getDpid(),
-                linkEvent.getSrc().getPortNumber());
-        Port dstPort = topology.getPort(linkEvent.getDst().getDpid(),
-                linkEvent.getDst().getPortNumber());
+        PortEvent srcPort = topology.getPortEvent(linkEvent.getSrc());
+        PortEvent dstPort = topology.getPortEvent(linkEvent.getDst());
         if ((srcPort == null) || (dstPort == null)) {
             log.debug("{} reordered because {} port is null", linkEvent,
                     (srcPort == null) ? "src" : "dst");
@@ -1011,47 +1022,64 @@
             return;
         }
 
-        // Get the Link instance from the Destination Port Incoming Link
-        // XXX domain knowledge: Link is discovered by LLDP,
-        //      thus incoming link is likely to be more up-to-date
+        // XXX domain knowledge: Sanity check: Port cannot have both Link and Host
         // FIXME potentially local replica may not be up-to-date yet due to HZ delay.
         //        may need to manage local truth and use them instead.
-        Link link = dstPort.getIncomingLink();
-        assert (link == srcPort.getOutgoingLink());
-        if (link == null) {
-            link = new LinkImpl(topology, linkEvent);
-            topology.putLink(link);
+        if (topology.getLinkEvent(linkEvent.getLinkTuple()) == null) {
+            // Only check for existing Host when adding new Link.
 
-            // Remove all Hosts attached to the Ports
-            ArrayList<HostEvent> hostsToRemove = new ArrayList<>();
-            ArrayList<Port> ports = new ArrayList<>();
-            ports.add(srcPort);
-            ports.add(dstPort);
-            for (Port port : ports) {
-                for (Host host : port.getHosts()) {
+            // Remove all Hosts attached to the ports on both ends
+
+            Set<HostEvent> hostsToUpdate = new TreeSet<>(new Comparator<HostEvent>() {
+                // comparison only using ID(=MAC)
+                @Override
+                public int compare(HostEvent o1, HostEvent o2) {
+                    return Long.compare(o1.getMac().toLong(), o2.getMac().toLong());
+                }
+            });
+
+            List<SwitchPort> portsToCheck = Arrays.asList(
+                    srcPort.getSwitchPort(),
+                    dstPort.getSwitchPort());
+
+            // Enumerate Host which needs to be updated by this Link add event
+            for (SwitchPort port : portsToCheck) {
+                for (Host host : topology.getHosts(port)) {
                     log.error("Host {} on Port {} should have been removed prior to adding Link {}",
                             host, port, linkEvent);
-                    // FIXME must get Host info from topology, when we add attrs.
-                    HostEvent hostEvent =
-                            new HostEvent(host.getMacAddress());
-                    SwitchPort switchPort =
-                            new SwitchPort(port.getSwitch().getDpid(),
-                                    port.getNumber());
-                    // adding attachment port which needs to be removed
-                    hostEvent.addAttachmentPoint(switchPort);
-                    hostsToRemove.add(hostEvent);
+
+                    HostEvent hostEvent = topology.getHostEvent(host.getMacAddress());
+                    hostsToUpdate.add(hostEvent);
                 }
             }
-            for (HostEvent hostEvent : hostsToRemove) {
-                removeHost(hostEvent);
+            // remove attachment point from them.
+            for (HostEvent hostEvent : hostsToUpdate) {
+                // remove port from attachment point and update
+                HostEvent newHostEvent = new HostEvent(hostEvent);
+                newHostEvent.removeAttachmentPoint(srcPort.getSwitchPort());
+                newHostEvent.removeAttachmentPoint(dstPort.getSwitchPort());
+                newHostEvent.freeze();
+
+                // TODO should this event be fired inside #addHost?
+                if (newHostEvent.getAttachmentPoints().isEmpty()) {
+                    // No more attachment point left -> remove Host
+                    removeHost(hostEvent);
+                } else {
+                    // Update Host
+                    addHost(newHostEvent);
+                }
             }
-        } else {
-            // TODO: Update the link attributes
-            log.debug("Update link attributes");
-            LinkImpl impl = (LinkImpl) link;
-            impl.replaceStringAttributes(linkEvent);
         }
 
+        if (log.isDebugEnabled()) {
+            LinkEvent link = topology.getLinkEvent(linkEvent.getLinkTuple());
+            if (link != null) {
+                log.debug("Update {}", linkEvent);
+            } else {
+                log.debug("Added {}", linkEvent);
+            }
+        }
+        topology.putLink(linkEvent.freeze());
         apiAddedLinkEvents.add(linkEvent);
     }
 
@@ -1078,25 +1106,29 @@
             return;
         }
 
-        //
-        // Remove the Link instance from the Destination Port Incoming Link
-        // and the Source Port Outgoing Link.
-        //
-        Link link = dstPort.getIncomingLink();
-        if (link == null) {
-            log.warn("Link {} already removed on destination Port", linkEvent);
-        }
-        link = srcPort.getOutgoingLink();
-        if (link == null) {
-            log.warn("Link {} already removed on src Port", linkEvent);
+        LinkEvent linkInTopo = topology.getLinkEvent(linkEvent.getLinkTuple(),
+                linkEvent.getType());
+        if (linkInTopo == null) {
+            log.warn("Link {} already removed, ignoring", linkEvent);
+            return;
         }
 
-        // TODO should we check that we get the same link from each port?
-        if (link != null) {
-            topology.removeLink(link);
+        if (log.isDebugEnabled()) {
+            // only do sanity check on debug level
+
+            Link linkIn = dstPort.getIncomingLink(linkEvent.getType());
+            if (linkIn == null) {
+                log.warn("Link {} already removed on destination Port", linkEvent);
+            }
+            Link linkOut = srcPort.getOutgoingLink(linkEvent.getType());
+            if (linkOut == null) {
+                log.warn("Link {} already removed on src Port", linkEvent);
+            }
         }
 
-        apiRemovedLinkEvents.add(linkEvent);
+        log.debug("Removed {}", linkInTopo);
+        topology.removeLink(linkEvent.getLinkTuple(), linkEvent.getType());
+        apiRemovedLinkEvents.add(linkInTopo);
     }
 
     /**
@@ -1104,23 +1136,23 @@
      * <p/>
      * TODO: Host-related work is incomplete.
      * TODO: Eventually, we might need to consider reordering
-     * or addLink() and addDevice() events on the same port.
+     * or {@link #addLink(LinkEvent)} and {@link #addHost(HostEvent)} events on the same port.
      *
      * @param hostEvent the HostEvent with the host to add.
      */
     @GuardedBy("topology.writeLock")
     private void addHost(HostEvent hostEvent) {
-        log.debug("Adding a host to the topology with mac {}", hostEvent.getMac());
-        Host host = topology.getHostByMac(hostEvent.getMac());
 
-        if (host == null) {
-            log.debug("Existing host was not found in the Topology: Adding new host: mac {}", hostEvent.getMac());
-            host = new HostImpl(topology, hostEvent.getMac());
-        }
+        // TODO Decide how to handle update scenario.
+        // If the new HostEvent has less attachment point compared to
+        // existing HostEvent, what should the event be?
+        // - AddHostEvent with some attachment point removed? (current behavior)
 
-        HostImpl hostImpl = (HostImpl) host;
+        // create unfrozen copy
+        //  for removing attachment points which already has a link
+        HostEvent modifiedHostEvent = new HostEvent(hostEvent);
 
-        // Process each attachment point
+        // Verify each attachment point
         boolean attachmentFound = false;
         for (SwitchPort swp : hostEvent.getAttachmentPoints()) {
             // XXX domain knowledge: Port must exist before Host
@@ -1129,32 +1161,49 @@
             // Attached Ports must exist
             Port port = topology.getPort(swp.getDpid(), swp.getPortNumber());
             if (port == null) {
+                log.debug("{} reordered because port {} was not there", hostEvent, swp);
                 // Reordered event: delay the event in local cache
                 ByteBuffer id = hostEvent.getIDasByteBuffer();
                 reorderedAddedHostEvents.put(id, hostEvent);
-                continue;
+                return; // should not continue if re-applying later
             }
             // 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());
+                log.warn("Link (Out:{},In:{}) exist on the attachment point. "
+                        + "Ignoring this attachmentpoint ({}) from {}.",
+                        port.getOutgoingLink(), port.getIncomingLink(),
+                        swp, modifiedHostEvent);
+                // FIXME Should either reject, reorder this HostEvent,
+                //       or remove attachment point from given HostEvent
+                // Removing attachment point from given HostEvent for now.
+                modifiedHostEvent.removeAttachmentPoint(swp);
                 continue;
             }
 
-            // Add Host <-> Port attachment
-            hostImpl.addAttachmentPoint(port);
             attachmentFound = true;
         }
 
-        hostImpl.setLastSeenTime(hostEvent.getLastSeenTime());
-
         // Update the host in the topology
         if (attachmentFound) {
-            log.debug("Storing the host info into the Topology: mac {}", hostEvent.getMac());
-            topology.putHost(host);
-            apiAddedHostEvents.add(hostEvent);
+            if (modifiedHostEvent.getAttachmentPoints().isEmpty()) {
+                log.warn("No valid attachment point left. Ignoring."
+                        + "original: {}, modified: {}", hostEvent, modifiedHostEvent);
+                // TODO Should we call #removeHost to trigger remove event?
+                //      only if this call is update.
+                return;
+            }
+
+            if (log.isDebugEnabled()) {
+                HostEvent host = topology.getHostEvent(hostEvent.getMac());
+                if (host != null) {
+                    log.debug("Update {}", modifiedHostEvent);
+                } else {
+                    log.debug("Added {}", modifiedHostEvent);
+                }
+            }
+            topology.putHost(modifiedHostEvent.freeze());
+            apiAddedHostEvents.add(modifiedHostEvent);
         }
     }
 
@@ -1167,15 +1216,17 @@
      */
     @GuardedBy("topology.writeLock")
     private void removeHost(HostEvent hostEvent) {
-        log.debug("Removing a host from the topology: mac {}", hostEvent.getMac());
-        Host host = topology.getHostByMac(hostEvent.getMac());
-        if (host == null) {
+
+        final MACAddress mac = hostEvent.getMac();
+        HostEvent hostInTopo = topology.getHostEvent(mac);
+        if (hostInTopo == null) {
             log.warn("Host {} already removed, ignoring", hostEvent);
             return;
         }
 
-        topology.removeHost(host);
-        apiRemovedHostEvents.add(hostEvent);
+        log.debug("Removed {}", hostInTopo);
+        topology.removeHost(mac);
+        apiRemovedHostEvents.add(hostInTopo);
     }
 
     /**
@@ -1243,14 +1294,4 @@
 
         return collection;
     }
-    /**
-     * Replaces the internal datastore instance.
-     *
-     * @param dataStore instance
-     *
-     * @exclude Backdoor for unit testing purpose only, do not use.
-     */
-    void debugReplaceDataStore(final TopologyDatastore dataStoreService) {
-        this.datastore = dataStoreService;
-    }
 }
diff --git a/src/main/java/net/onrc/onos/core/topology/TopologyObject.java b/src/main/java/net/onrc/onos/core/topology/TopologyObject.java
index f929926..445c2d8 100644
--- a/src/main/java/net/onrc/onos/core/topology/TopologyObject.java
+++ b/src/main/java/net/onrc/onos/core/topology/TopologyObject.java
@@ -3,28 +3,40 @@
 import org.apache.commons.lang.Validate;
 
 
-
 /**
  * Base class for Topology Objects.
  */
 public abstract class TopologyObject implements ITopologyElement {
 
-    // XXX This will be a snapshot, thus should be replaceable
     /**
-     * Topology instance this object belongs to.
+     * Topology snapshot this object belongs to.
      */
-    protected final Topology topology;
+    protected volatile TopologyInternal topology;
+
+    // XXX Updater to be used once we implement snapshot update.
+    // Should it be static or not.
+    //     static: Low memory consumption, but higher contention on atomic update
+    // non-static: Updater per instance, but less chance of contention
+//    private static final AtomicReferenceFieldUpdater<TopologyObject, TopologyImpl>
+//        TOPOLOGY_UPDATER =
+//            AtomicReferenceFieldUpdater.newUpdater(
+//                        TopologyObject.class, TopologyImpl.class, "topology");
 
     /**
      * Constructor.
      *
      * @param topology Topology instance this object belongs to
      */
-    protected TopologyObject(Topology topology) {
+    protected TopologyObject(TopologyInternal topology) {
         Validate.notNull(topology);
         this.topology = topology;
     }
 
+    // TODO Add method to replace topology snapshot
+    //  - Request TopologyManager for latest TopologyImpl and swap?
+    //  - Make caller specify TopologyImpl instance?
+    //  -
+
 
     /**
      * Returns the type of topology object.
diff --git a/src/test/java/net/onrc/onos/core/topology/MockTopology.java b/src/test/java/net/onrc/onos/core/topology/MockTopology.java
index a089b6c..3208a0d 100644
--- a/src/test/java/net/onrc/onos/core/topology/MockTopology.java
+++ b/src/test/java/net/onrc/onos/core/topology/MockTopology.java
@@ -2,7 +2,9 @@
 
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.LinkTuple;
 import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.core.util.SwitchPort;
 
 /**
  * A mock class of Topology.
@@ -20,35 +22,35 @@
     public SwitchImpl sw1, sw2, sw3, sw4;
 
     public Switch addSwitch(Long switchId) {
-        SwitchImpl sw = new SwitchImpl(this, new Dpid(switchId));
+        SwitchEvent sw = new SwitchEvent(new Dpid(switchId));
         this.putSwitch(sw);
-        return sw;
+        return this.getSwitch(sw.getDpid());
     }
 
     public Port addPort(Switch sw, Long portNumber) {
-        PortImpl port = new PortImpl(this, sw.getDpid(),
+        PortEvent port = new PortEvent(sw.getDpid(),
                                 new PortNumber(portNumber.shortValue()));
         ((TopologyImpl) this).putPort(port);
-        return port;
+        return this.getPort(port.getSwitchPort());
     }
 
-    public Link[] addBidirectionalLinks(Long srcDpid, Long srcPortNo, Long dstDpid, Long dstPortNo) {
-        Link[] links = new Link[2];
-        final Dpid srcDpidObj = new Dpid(srcDpid);
-        final Dpid dstDpidObj = new Dpid(dstDpid);
-        final PortNumber srcPortNum = new PortNumber(srcPortNo.shortValue());
-        final PortNumber dstPortNum = new PortNumber(dstPortNo.shortValue());
-        links[0] = new LinkImpl(this,
-                getPort(srcDpidObj, srcPortNum),
-                getPort(dstDpidObj, dstPortNum));
-        links[1] = new LinkImpl(this,
-                getPort(dstDpidObj, dstPortNum),
-                getPort(srcDpidObj, srcPortNum));
+    public void addBidirectionalLinks(Long srcDpid, Long srcPortNo, Long dstDpid, Long dstPortNo) {
+        addBidirectionalLinks(srcDpid, srcPortNo, dstDpid, dstPortNo, null);
+    }
+
+    public void addBidirectionalLinks(Long srcDpid, Long srcPortNo, Long dstDpid, Long dstPortNo, Double capacity) {
+        LinkEvent[] links = new LinkEvent[2];
+        final SwitchPort src = new SwitchPort(srcDpid, srcPortNo);
+        final SwitchPort dst = new SwitchPort(dstDpid, dstPortNo);
+        links[0] = new LinkEvent(src, dst);
+        links[1] = new LinkEvent(dst, src);
+        if (capacity != null) {
+            links[0].setCapacity(capacity);
+            links[1].setCapacity(capacity);
+        }
 
         putLink(links[0]);
         putLink(links[1]);
-
-        return links;
     }
 
     /**
@@ -81,16 +83,11 @@
         addPort(sw4, 42L); // sw4 -> sw2
         addPort(sw4, 43L); // sw4 -> sw3
 
-        addBidirectionalLinks(1L, 12L, 2L, 21L);
-        addBidirectionalLinks(2L, 23L, 3L, 32L);
-        addBidirectionalLinks(3L, 34L, 4L, 43L);
-        addBidirectionalLinks(4L, 41L, 1L, 14L);
-        addBidirectionalLinks(2L, 24L, 4L, 42L);
-
-        // set capacity of all links to 1000Mbps
-        for (Link link : getLinks()) {
-            ((LinkImpl) link).setCapacity(1000.0);
-        }
+        addBidirectionalLinks(1L, 12L, 2L, 21L, 1000.0);
+        addBidirectionalLinks(2L, 23L, 3L, 32L, 1000.0);
+        addBidirectionalLinks(3L, 34L, 4L, 43L, 1000.0);
+        addBidirectionalLinks(4L, 41L, 1L, 14L, 1000.0);
+        addBidirectionalLinks(2L, 24L, 4L, 42L, 1000.0);
     }
 
     /**
@@ -128,38 +125,39 @@
         Port port43 = addPort(sw4, 43L); // sw4 -> sw3
 
         MACAddress mac1 = MACAddress.valueOf("00:44:33:22:11:00");
-        HostImpl dev1 = new HostImpl(this, mac1);
-        dev1.addAttachmentPoint(port15);
-        dev1.setLastSeenTime(1L);
-        this.putHost(dev1);
+        HostEvent host1 = new HostEvent(mac1);
+        host1.addAttachmentPoint(port15.asSwitchPort());
+        host1.setLastSeenTime(1L);
+        this.putHost(host1);
 
         MACAddress mac3 = MACAddress.valueOf("00:11:22:33:44:55");
-        HostImpl dev3 = new HostImpl(this, mac3);
-        dev3.addAttachmentPoint(port35);
-        dev3.setLastSeenTime(1L);
-        this.putHost(dev3);
+        HostEvent host3 = new HostEvent(mac3);
+        host3.addAttachmentPoint(port35.asSwitchPort());
+        host3.setLastSeenTime(1L);
+        this.putHost(host3);
 
-        addBidirectionalLinks(1L, 12L, 2L, 21L);
-        addBidirectionalLinks(2L, 23L, 3L, 32L);
-        addBidirectionalLinks(3L, 34L, 4L, 43L);
-        addBidirectionalLinks(4L, 41L, 1L, 14L);
-        addBidirectionalLinks(2L, 24L, 4L, 42L);
-
-        // set capacity of all links to 1000Mbps
-        for (Link link : getLinks()) {
-            ((LinkImpl) link).setCapacity(1000.0);
-        }
+        addBidirectionalLinks(1L, 12L, 2L, 21L, 1000.0);
+        addBidirectionalLinks(2L, 23L, 3L, 32L, 1000.0);
+        addBidirectionalLinks(3L, 34L, 4L, 43L, 1000.0);
+        addBidirectionalLinks(4L, 41L, 1L, 14L, 1000.0);
+        addBidirectionalLinks(2L, 24L, 4L, 42L, 1000.0);
     }
 
     public void removeLink(Long srcDpid, Long srcPortNo, Long dstDpid, Long dstPortNo) {
-        removeLink(getLink(new Dpid(srcDpid),
+        this.removeLink(new Dpid(srcDpid),
                            new PortNumber(srcPortNo.shortValue()),
                            new Dpid(dstDpid),
-                           new PortNumber(dstPortNo.shortValue())));
+                           new PortNumber(dstPortNo.shortValue()));
     }
 
     public void removeLink(Dpid srcDpid, PortNumber srcPortNo,
-                           Dpid dstDpid, PortNumber dstPortNo) {
-        super.removeLink(getLink(srcDpid, srcPortNo, dstDpid, dstPortNo));
+            Dpid dstDpid, PortNumber dstPortNo) {
+        this.removeLink(srcDpid, srcPortNo, dstDpid, dstPortNo,
+                TopologyElement.TYPE_PACKET_LAYER);
+    }
+    public void removeLink(Dpid srcDpid, PortNumber srcPortNo,
+                           Dpid dstDpid, PortNumber dstPortNo, String type) {
+        super.removeLink(new LinkTuple(srcDpid, srcPortNo, dstDpid, dstPortNo),
+                        type);
     }
 }
diff --git a/src/test/java/net/onrc/onos/core/topology/TopologyImplTest.java b/src/test/java/net/onrc/onos/core/topology/TopologyImplTest.java
index d02142a..df0818f 100644
--- a/src/test/java/net/onrc/onos/core/topology/TopologyImplTest.java
+++ b/src/test/java/net/onrc/onos/core/topology/TopologyImplTest.java
@@ -3,11 +3,14 @@
 import static org.junit.Assert.*;
 import static org.hamcrest.Matchers.*;
 
+import java.util.Collection;
 import java.util.Iterator;
 
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.LinkTuple;
 import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.core.util.SwitchPort;
 
 import org.junit.After;
 import org.junit.Before;
@@ -31,6 +34,7 @@
 
     // Set the test network size, it should be larger than 3
     private static final long TEST_SWITCH_NUM = 100L;
+    private static final long TEST_HOST_NUM = TEST_SWITCH_NUM;
 
     @Before
     public void setUp() throws Exception {
@@ -39,32 +43,33 @@
 
         // Create a number of switches and install two ports for each switch
         for (long switchID = 1; switchID <= TEST_SWITCH_NUM; switchID++) {
-            SwitchImpl testSwitch = new SwitchImpl(testTopology, new Dpid(switchID));
+            final Dpid dpid = new Dpid(switchID);
+            SwitchEvent testSwitch = new SwitchEvent(dpid);
             testTopology.putSwitch(testSwitch);
-            testTopology.putPort(new PortImpl(testTopology,
-                    new Dpid(switchID), PORT_NUMBER_1));
-            testTopology.putPort(new PortImpl(testTopology,
-                    new Dpid(switchID), PORT_NUMBER_2));
-            Port hostPort = new PortImpl(testTopology,
-                    new Dpid(switchID), new PortNumber(SWITCH_HOST_PORT.shortValue()));
+            testTopology.putPort(new PortEvent(dpid, PORT_NUMBER_1));
+            testTopology.putPort(new PortEvent(dpid, PORT_NUMBER_2));
+            PortEvent hostPort = new PortEvent(dpid,
+                    new PortNumber(SWITCH_HOST_PORT.shortValue()));
             testTopology.putPort(hostPort);
 
             // Create a host for each switch
             MACAddress devMac = MACAddress.valueOf(switchID);
-            HostImpl testHost = new HostImpl(testTopology, devMac);
-            testHost.addAttachmentPoint(hostPort);
+            HostEvent testHost = new HostEvent(devMac);
+            testHost.addAttachmentPoint(hostPort.getSwitchPort());
             testTopology.putHost(testHost);
         }
 
         // Create one bidirectional link b/w two switches to construct a ring topology
         for (long switchID = 1; switchID <= TEST_SWITCH_NUM; switchID++) {
-            LinkImpl testLinkEast = new LinkImpl(testTopology,
-                    testTopology.getPort(new Dpid(switchID), PORT_NUMBER_2),
-                    testTopology.getPort(new Dpid(switchID % TEST_SWITCH_NUM + 1), PORT_NUMBER_1)
+            final Dpid dpidA = new Dpid(switchID);
+            final Dpid dpidB = new Dpid(switchID % TEST_SWITCH_NUM + 1);
+            LinkEvent testLinkEast = new LinkEvent(
+                    testTopology.getPort(dpidA, PORT_NUMBER_2).asSwitchPort(),
+                    testTopology.getPort(dpidB, PORT_NUMBER_1).asSwitchPort()
                     );
-            LinkImpl testLinkWest = new LinkImpl(testTopology,
-                    testTopology.getPort(new Dpid(switchID % TEST_SWITCH_NUM + 1), PORT_NUMBER_1),
-                    testTopology.getPort(new Dpid(switchID), PORT_NUMBER_2)
+            LinkEvent testLinkWest = new LinkEvent(
+                    testTopology.getPort(dpidB, PORT_NUMBER_1).asSwitchPort(),
+                    testTopology.getPort(dpidA, PORT_NUMBER_2).asSwitchPort()
                     );
             testTopology.putLink(testLinkEast);
             testTopology.putLink(testLinkWest);
@@ -108,11 +113,12 @@
         PortNumber bogusPortNum = new PortNumber((short) (SWITCH_PORT_2 + 1));
         for (long switchID = 1; switchID <= TEST_SWITCH_NUM; switchID++) {
             // Verify ports are in the graphDB
-            assertNotNull(testTopology.getSwitch(new Dpid(switchID)).getPort(PORT_NUMBER_1));
-            assertNotNull(testTopology.getSwitch(new Dpid(switchID)).getPort(PORT_NUMBER_2));
+            final Dpid dpid = new Dpid(switchID);
+            assertNotNull(testTopology.getSwitch(dpid).getPort(PORT_NUMBER_1));
+            assertNotNull(testTopology.getSwitch(dpid).getPort(PORT_NUMBER_2));
 
             // Verify there is no such port in the graphDB
-            assertNull(testTopology.getSwitch(new Dpid(switchID)).getPort(bogusPortNum));
+            assertNull(testTopology.getSwitch(dpid).getPort(bogusPortNum));
         }
     }
 
@@ -134,6 +140,23 @@
             Switch srcSw = (objectLink.getSrcSwitch());
             Switch dstSw = (objectLink.getDstSwitch());
 
+            LinkTuple linkId = objectLink.getLinkTuple();
+            // Verify the link through #getLink
+            Link linkA = testTopology.getLink(linkId.getSrc().getDpid(),
+                    linkId.getSrc().getPortNumber(),
+                    linkId.getDst().getDpid(),
+                    linkId.getDst().getPortNumber());
+            assertEquals(linkId, linkA.getLinkTuple());
+
+            Link linkB = testTopology.getLink(linkId.getSrc().getDpid(),
+                    linkId.getSrc().getPortNumber(),
+                    linkId.getDst().getDpid(),
+                    linkId.getDst().getPortNumber(),
+                    TopologyElement.TYPE_PACKET_LAYER);
+            assertEquals(linkId, linkB.getLinkTuple());
+
+
+
             // confirm link is forming a link
             final long smallerDpid = Math.min(srcSw.getDpid().value(), dstSw.getDpid().value());
             final long largerDpid = Math.max(srcSw.getDpid().value(), dstSw.getDpid().value());
@@ -152,11 +175,22 @@
     public void testGetOutgoingLink() {
         PortNumber bogusPortNum = new PortNumber((short) (SWITCH_PORT_2 + 1));
         for (long switchID = 1; switchID <= TEST_SWITCH_NUM; switchID++) {
-            assertNotNull(testTopology.getOutgoingLink(new Dpid(switchID), PORT_NUMBER_1));
-            assertNotNull(testTopology.getOutgoingLink(new Dpid(switchID), PORT_NUMBER_2));
+            final Dpid dpid = new Dpid(switchID);
+            assertNotNull(testTopology.getOutgoingLink(dpid, PORT_NUMBER_1));
+            assertNotNull(testTopology.getOutgoingLink(dpid, PORT_NUMBER_2));
+
+            Link la = testTopology.getOutgoingLink(dpid, PORT_NUMBER_2,
+                                TopologyElement.TYPE_PACKET_LAYER);
+            Link lb = testTopology.getOutgoingLink(dpid, PORT_NUMBER_2);
+
+            assertTrue(la.getLinkTuple().equals(lb.getLinkTuple()));
+
+            Collection<Link> links = testTopology.getOutgoingLinks(
+                                        new SwitchPort(dpid, PORT_NUMBER_1));
+            assertEquals(1, links.size());
 
             // Verify there is no such link in the graphDB
-            assertNull(testTopology.getOutgoingLink(new Dpid(switchID), bogusPortNum));
+            assertNull(testTopology.getOutgoingLink(dpid, bogusPortNum));
         }
     }
 
@@ -168,22 +202,33 @@
         PortNumber bogusPortNum = new PortNumber((short) (SWITCH_PORT_2 + 1));
         for (long switchID = 1; switchID <= TEST_SWITCH_NUM; switchID++) {
             // Verify the links are in the graphDB
+            final Dpid dpid = new Dpid(switchID);
             assertNotNull(testTopology.getIncomingLink(
-                                           new Dpid(switchID), PORT_NUMBER_1));
+                                           dpid, PORT_NUMBER_1));
             assertNotNull(testTopology.getIncomingLink(
-                                           new Dpid(switchID), PORT_NUMBER_2));
+                                           dpid, PORT_NUMBER_2));
+
+            Link la = testTopology.getIncomingLink(dpid, PORT_NUMBER_2,
+                                        TopologyElement.TYPE_PACKET_LAYER);
+            Link lb = testTopology.getIncomingLink(dpid, PORT_NUMBER_2);
+
+            assertTrue(la.getLinkTuple().equals(lb.getLinkTuple()));
+
+            Collection<Link> links = testTopology.getIncomingLinks(
+                    new SwitchPort(dpid, PORT_NUMBER_1));
+            assertEquals(1, links.size());
 
             // Verify there is no such link in the graphDB
             assertNull(testTopology.getIncomingLink(
-                                        new Dpid(switchID), bogusPortNum));
+                                        dpid, bogusPortNum));
         }
     }
 
     /**
-     * Test the result of getDeviceByMac function.
+     * Test the result of getHostByMac function.
      */
     @Test
-    public void testGetDeviceByMac() {
+    public void testGetHostByMac() {
         for (long switchID = 1; switchID <= TEST_SWITCH_NUM; switchID++) {
             MACAddress devMac = MACAddress.valueOf(switchID);
 
@@ -193,21 +238,27 @@
     }
 
     /**
-     * Test the result of removeDevice function.
+     * Test the result of removeHost function.
      */
     @Test
-    public void testRemoveDevice() {
+    public void testRemoveHost() {
         int devCount = 0;
         Iterator<Host> itr = testTopology.getHosts().iterator();
         while (itr.hasNext()) {
             Host currDev = itr.next();
-            testTopology.removeHost(currDev);
-            testTopology.getHostByMac(currDev.getMacAddress());
+            final MACAddress mac = currDev.getMacAddress();
+            testTopology.removeHost(mac);
+            assertNull(testTopology.getHostByMac(mac));
             devCount++;
         }
+        for (Switch sw : testTopology.getSwitches()) {
+            for (Port port : sw.getPorts()) {
+                assertTrue(port.getHosts().isEmpty());
+            }
+        }
 
         // Verify all hosts have been removed successfully
-        assertEquals(TEST_SWITCH_NUM, devCount);
+        assertEquals(TEST_HOST_NUM, devCount);
     }
 
     /**
@@ -224,7 +275,8 @@
             Port srcPort = objectLink.getSrcPort();
             Switch dstSw = (objectLink.getDstSwitch());
             Port dstPort = objectLink.getDstPort();
-            testTopology.removeLink(objectLink);
+
+            testTopology.removeLink(objectLink.getLinkTuple());
 
             // Verify the link was removed successfully
             assertNull(testTopology.getLink(
@@ -242,14 +294,18 @@
     @Test
     public void testRemoveSwitch() {
         for (long switchID = 1; switchID <= TEST_SWITCH_NUM; switchID++) {
-            Iterator<Host> itr = testTopology.getSwitch(new Dpid(switchID)).getHosts().iterator();
+            final Dpid dpid = new Dpid(switchID);
+            Iterator<Host> itr = testTopology.getSwitch(dpid).getHosts().iterator();
             while (itr.hasNext()) {
-                testTopology.removeHost(itr.next());
+                testTopology.removeHost(itr.next().getMacAddress());
             }
-            testTopology.removeSwitch(new Dpid(switchID));
+            for (Port port : testTopology.getSwitch(dpid).getPorts()) {
+                testTopology.removePort(port.asSwitchPort());
+            }
+            testTopology.removeSwitch(dpid);
 
             // Verify the switch has been removed from the graphDB successfully
-            assertNull(testTopology.getSwitch(new Dpid(switchID)));
+            assertNull(testTopology.getSwitch(dpid));
         }
 
         // Verify all switches have been removed successfully
diff --git a/src/test/java/net/onrc/onos/core/topology/TopologyManagerTest.java b/src/test/java/net/onrc/onos/core/topology/TopologyManagerTest.java
index e4f6c8c..309bfb4 100644
--- a/src/test/java/net/onrc/onos/core/topology/TopologyManagerTest.java
+++ b/src/test/java/net/onrc/onos/core/topology/TopologyManagerTest.java
@@ -6,6 +6,8 @@
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -20,6 +22,7 @@
 import net.onrc.onos.core.util.Dpid;
 import net.onrc.onos.core.util.PortNumber;
 import net.onrc.onos.core.util.SwitchPort;
+import net.onrc.onos.core.util.TestUtils;
 
 import org.easymock.EasyMock;
 import org.junit.After;
@@ -111,8 +114,14 @@
         // Create a topologyManager object for testing
         topologyListeners = new CopyOnWriteArrayList<>();
         theTopologyManager = new TopologyManager(registryService, topologyListeners);
+
+        // replace EventHandler to avoid thread from starting
+        TestUtils.setField(theTopologyManager, "eventHandler",
+            EasyMock.createNiceMock(TopologyManager.EventHandler.class));
         theTopologyManager.startup(datagridService);
-        theTopologyManager.debugReplaceDataStore(dataStoreService);
+
+        // replace data store with Mocked object
+        TestUtils.setField(theTopologyManager, "datastore", dataStoreService);
     }
 
     @After
@@ -335,4 +344,621 @@
         verify(eventChannel);
     }
 
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testAddSwitch() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        // check events to be fired
+        List<SwitchEvent> apiAddedSwitchEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedSwitchEvents");
+        assertThat(apiAddedSwitchEvents, hasItem(sw));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testAddPort() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumber = new PortNumber((short) 2);
+        PortEvent port = new PortEvent(dpid, portNumber);
+        port.createStringAttribute("fuzz", "buzz");
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, port);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portId = new SwitchPort(dpid, portNumber);
+        PortEvent portInTopo = topology.getPortEvent(portId);
+        assertEquals(port, portInTopo);
+        assertTrue(portInTopo.isFrozen());
+        assertEquals("buzz", portInTopo.getStringAttribute("fuzz"));
+
+        // check events to be fired
+        List<PortEvent> apiAddedPortEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedPortEvents");
+        assertThat(apiAddedPortEvents, hasItem(port));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testRemovePortThenSwitch() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumber = new PortNumber((short) 2);
+        PortEvent port = new PortEvent(dpid, portNumber);
+        port.createStringAttribute("fuzz", "buzz");
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, port);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portId = new SwitchPort(dpid, portNumber);
+        PortEvent portInTopo = topology.getPortEvent(portId);
+        assertEquals(port, portInTopo);
+        assertTrue(portInTopo.isFrozen());
+        assertEquals("buzz", portInTopo.getStringAttribute("fuzz"));
+
+        // remove in proper order
+        TestUtils.callMethod(theTopologyManager, "removePort",
+                            PortEvent.class, new PortEvent(port));
+        TestUtils.callMethod(theTopologyManager, "removeSwitch",
+                            SwitchEvent.class, new SwitchEvent(sw));
+
+
+        // check events to be fired
+        List<PortEvent> apiRemovedPortEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedPortEvents");
+        assertThat(apiRemovedPortEvents, hasItem(port));
+        List<SwitchEvent> apiRemovedSwitchEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedSwitchEvents");
+        assertThat(apiRemovedSwitchEvents, hasItem(sw));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testRemoveSwitch() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumber = new PortNumber((short) 2);
+        PortEvent port = new PortEvent(dpid, portNumber);
+        port.createStringAttribute("fuzz", "buzz");
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, port);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portId = new SwitchPort(dpid, portNumber);
+        PortEvent portInTopo = topology.getPortEvent(portId);
+        assertEquals(port, portInTopo);
+        assertTrue(portInTopo.isFrozen());
+        assertEquals("buzz", portInTopo.getStringAttribute("fuzz"));
+
+        // remove in in-proper order
+//        TestUtils.callMethod(theTopologyManager, "removePort",
+//                            PortEvent.class, new PortEvent(port));
+        TestUtils.callMethod(theTopologyManager, "removeSwitch",
+                            SwitchEvent.class, new SwitchEvent(sw));
+
+
+        // check events to be fired
+        // outcome should be the same as #testRemovePortThenSwitch
+        List<PortEvent> apiRemovedPortEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedPortEvents");
+        assertThat(apiRemovedPortEvents, hasItem(port));
+        List<SwitchEvent> apiRemovedSwitchEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedSwitchEvents");
+        assertThat(apiRemovedSwitchEvents, hasItem(sw));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testAddLink() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumberA = new PortNumber((short) 2);
+        PortEvent portA = new PortEvent(dpid, portNumberA);
+        portA.createStringAttribute("fuzz", "buzz");
+
+        final PortNumber portNumberB = new PortNumber((short) 3);
+        PortEvent portB = new PortEvent(dpid, portNumberB);
+        portB.createStringAttribute("fizz", "buz");
+
+        LinkEvent linkA = new LinkEvent(portA.getSwitchPort(), portB.getSwitchPort());
+        linkA.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+        LinkEvent linkB = new LinkEvent(portB.getSwitchPort(), portA.getSwitchPort());
+        linkB.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portA);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portB);
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkA);
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkB);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portIdA = new SwitchPort(dpid, portNumberA);
+        PortEvent portAInTopo = topology.getPortEvent(portIdA);
+        assertEquals(portA, portAInTopo);
+        assertTrue(portAInTopo.isFrozen());
+        assertEquals("buzz", portAInTopo.getStringAttribute("fuzz"));
+
+        final SwitchPort portIdB = new SwitchPort(dpid, portNumberB);
+        PortEvent portBInTopo = topology.getPortEvent(portIdB);
+        assertEquals(portB, portBInTopo);
+        assertTrue(portBInTopo.isFrozen());
+        assertEquals("buz", portBInTopo.getStringAttribute("fizz"));
+
+        LinkEvent linkAInTopo = topology.getLinkEvent(linkA.getLinkTuple());
+        assertEquals(linkA, linkAInTopo);
+        assertTrue(linkAInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkAInTopo.getType());
+
+        LinkEvent linkBInTopo = topology.getLinkEvent(linkB.getLinkTuple());
+        assertEquals(linkB, linkBInTopo);
+        assertTrue(linkBInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkBInTopo.getType());
+
+        // check events to be fired
+        List<LinkEvent> apiAddedLinkEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedLinkEvents");
+        assertThat(apiAddedLinkEvents, containsInAnyOrder(linkA, linkB));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testAddLinkKickingOffHost() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumberA = new PortNumber((short) 2);
+        PortEvent portA = new PortEvent(dpid, portNumberA);
+        portA.createStringAttribute("fuzz", "buzz");
+
+        final PortNumber portNumberB = new PortNumber((short) 3);
+        PortEvent portB = new PortEvent(dpid, portNumberB);
+        portB.createStringAttribute("fizz", "buz");
+
+        final PortNumber portNumberC = new PortNumber((short) 4);
+        PortEvent portC = new PortEvent(dpid, portNumberC);
+        portC.createStringAttribute("fizz", "buz");
+
+        final MACAddress macA = MACAddress.valueOf(666L);
+        HostEvent hostA = new HostEvent(macA);
+        hostA.addAttachmentPoint(portA.getSwitchPort());
+        final long timestampA = 392893200L;
+        hostA.setLastSeenTime(timestampA);
+
+        final MACAddress macB = MACAddress.valueOf(999L);
+        HostEvent hostB = new HostEvent(macB);
+        hostB.addAttachmentPoint(portB.getSwitchPort());
+        hostB.addAttachmentPoint(portC.getSwitchPort());
+        final long timestampB = 392893201L;
+        hostB.setLastSeenTime(timestampB);
+
+
+        LinkEvent linkA = new LinkEvent(portA.getSwitchPort(), portB.getSwitchPort());
+        linkA.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+        LinkEvent linkB = new LinkEvent(portB.getSwitchPort(), portA.getSwitchPort());
+        linkB.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portA);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portB);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portC);
+        TestUtils.callMethod(theTopologyManager, "addHost", HostEvent.class, hostA);
+        TestUtils.callMethod(theTopologyManager, "addHost", HostEvent.class, hostB);
+
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkA);
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkB);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portIdA = new SwitchPort(dpid, portNumberA);
+        PortEvent portAInTopo = topology.getPortEvent(portIdA);
+        assertEquals(portA, portAInTopo);
+        assertTrue(portAInTopo.isFrozen());
+        assertEquals("buzz", portAInTopo.getStringAttribute("fuzz"));
+
+        final SwitchPort portIdB = new SwitchPort(dpid, portNumberB);
+        PortEvent portBInTopo = topology.getPortEvent(portIdB);
+        assertEquals(portB, portBInTopo);
+        assertTrue(portBInTopo.isFrozen());
+        assertEquals("buz", portBInTopo.getStringAttribute("fizz"));
+
+        // hostA expected to be removed
+        assertNull(topology.getHostEvent(macA));
+        // hostB expected to be there with reduced attachment point
+        HostEvent hostBrev = new HostEvent(macB);
+        hostBrev.addAttachmentPoint(portC.getSwitchPort());
+        hostBrev.setLastSeenTime(timestampB);
+        hostBrev.freeze();
+        assertEquals(hostBrev, topology.getHostEvent(macB));
+
+
+        LinkEvent linkAInTopo = topology.getLinkEvent(linkA.getLinkTuple());
+        assertEquals(linkA, linkAInTopo);
+        assertTrue(linkAInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkAInTopo.getType());
+
+        LinkEvent linkBInTopo = topology.getLinkEvent(linkB.getLinkTuple());
+        assertEquals(linkB, linkBInTopo);
+        assertTrue(linkBInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkBInTopo.getType());
+
+        // check events to be fired
+        List<HostEvent> apiAddedHostEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedHostEvents");
+        assertThat(apiAddedHostEvents, hasItem(hostBrev));
+
+        List<HostEvent> apiRemovedHostEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedHostEvents");
+        assertThat(apiRemovedHostEvents, hasItem(hostA));
+        List<LinkEvent> apiAddedLinkEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedLinkEvents");
+        assertThat(apiAddedLinkEvents, containsInAnyOrder(linkA, linkB));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testRemoveLink() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumberA = new PortNumber((short) 2);
+        PortEvent portA = new PortEvent(dpid, portNumberA);
+        portA.createStringAttribute("fuzz", "buzz");
+
+        final PortNumber portNumberB = new PortNumber((short) 3);
+        PortEvent portB = new PortEvent(dpid, portNumberB);
+        portB.createStringAttribute("fizz", "buz");
+
+        LinkEvent linkA = new LinkEvent(portA.getSwitchPort(), portB.getSwitchPort());
+        linkA.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+        LinkEvent linkB = new LinkEvent(portB.getSwitchPort(), portA.getSwitchPort());
+        linkB.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portA);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portB);
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkA);
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkB);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portIdA = new SwitchPort(dpid, portNumberA);
+        PortEvent portAInTopo = topology.getPortEvent(portIdA);
+        assertEquals(portA, portAInTopo);
+        assertTrue(portAInTopo.isFrozen());
+        assertEquals("buzz", portAInTopo.getStringAttribute("fuzz"));
+
+        final SwitchPort portIdB = new SwitchPort(dpid, portNumberB);
+        PortEvent portBInTopo = topology.getPortEvent(portIdB);
+        assertEquals(portB, portBInTopo);
+        assertTrue(portBInTopo.isFrozen());
+        assertEquals("buz", portBInTopo.getStringAttribute("fizz"));
+
+        LinkEvent linkAInTopo = topology.getLinkEvent(linkA.getLinkTuple());
+        assertEquals(linkA, linkAInTopo);
+        assertTrue(linkAInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkAInTopo.getType());
+
+
+        LinkEvent linkBInTopo = topology.getLinkEvent(linkB.getLinkTuple());
+        assertEquals(linkB, linkBInTopo);
+        assertTrue(linkBInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkBInTopo.getType());
+
+        // check events to be fired
+        // FIXME if link flapped (linkA in this scenario),
+        //  linkA appears in both removed and added is this expected behavior?
+        List<LinkEvent> apiAddedLinkEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedLinkEvents");
+        assertThat(apiAddedLinkEvents, containsInAnyOrder(linkA, linkB));
+
+        // clear event before removing Link
+        apiAddedLinkEvents.clear();
+
+        // remove link
+        TestUtils.callMethod(theTopologyManager, "removeLink", LinkEvent.class, new LinkEvent(linkA));
+
+        LinkEvent linkANotInTopo = topology.getLinkEvent(linkA.getLinkTuple());
+        assertNull(linkANotInTopo);
+
+        List<LinkEvent> apiRemovedLinkEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedLinkEvents");
+        assertThat(apiRemovedLinkEvents, hasItem(linkA));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testAddHostIgnoredByLink() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumberA = new PortNumber((short) 2);
+        PortEvent portA = new PortEvent(dpid, portNumberA);
+        portA.createStringAttribute("fuzz", "buzz");
+
+        final PortNumber portNumberB = new PortNumber((short) 3);
+        PortEvent portB = new PortEvent(dpid, portNumberB);
+        portB.createStringAttribute("fizz", "buz");
+
+        final PortNumber portNumberC = new PortNumber((short) 4);
+        PortEvent portC = new PortEvent(dpid, portNumberC);
+        portC.createStringAttribute("fizz", "buz");
+
+        LinkEvent linkA = new LinkEvent(portA.getSwitchPort(), portB.getSwitchPort());
+        linkA.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+        LinkEvent linkB = new LinkEvent(portB.getSwitchPort(), portA.getSwitchPort());
+        linkB.createStringAttribute(TopologyElement.TYPE,
+                                    TopologyElement.TYPE_OPTICAL_LAYER);
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portA);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portB);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portC);
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkA);
+        TestUtils.callMethod(theTopologyManager, "addLink", LinkEvent.class, linkB);
+
+        // Add hostA attached to a port which already has a link
+        final MACAddress macA = MACAddress.valueOf(666L);
+        HostEvent hostA = new HostEvent(macA);
+        hostA.addAttachmentPoint(portA.getSwitchPort());
+        final long timestampA = 392893200L;
+        hostA.setLastSeenTime(timestampA);
+
+        TestUtils.callMethod(theTopologyManager, "addHost", HostEvent.class, hostA);
+
+        // Add hostB attached to multiple ports,
+        // some of them which already has a link
+        final MACAddress macB = MACAddress.valueOf(999L);
+        HostEvent hostB = new HostEvent(macB);
+        hostB.addAttachmentPoint(portB.getSwitchPort());
+        hostB.addAttachmentPoint(portC.getSwitchPort());
+        final long timestampB = 392893201L;
+        hostB.setLastSeenTime(timestampB);
+
+        TestUtils.callMethod(theTopologyManager, "addHost", HostEvent.class, hostB);
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portIdA = new SwitchPort(dpid, portNumberA);
+        PortEvent portAInTopo = topology.getPortEvent(portIdA);
+        assertEquals(portA, portAInTopo);
+        assertTrue(portAInTopo.isFrozen());
+        assertEquals("buzz", portAInTopo.getStringAttribute("fuzz"));
+
+        final SwitchPort portIdB = new SwitchPort(dpid, portNumberB);
+        PortEvent portBInTopo = topology.getPortEvent(portIdB);
+        assertEquals(portB, portBInTopo);
+        assertTrue(portBInTopo.isFrozen());
+        assertEquals("buz", portBInTopo.getStringAttribute("fizz"));
+
+        // hostA expected to be completely ignored
+        assertNull(topology.getHostEvent(macA));
+        // hostB expected to be there with reduced attachment point
+        HostEvent hostBrev = new HostEvent(macB);
+        hostBrev.addAttachmentPoint(portC.getSwitchPort());
+        hostBrev.setLastSeenTime(timestampB);
+        hostBrev.freeze();
+        assertEquals(hostBrev, topology.getHostEvent(macB));
+
+
+        LinkEvent linkAInTopo = topology.getLinkEvent(linkA.getLinkTuple());
+        assertEquals(linkA, linkAInTopo);
+        assertTrue(linkAInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkAInTopo.getType());
+
+        LinkEvent linkBInTopo = topology.getLinkEvent(linkB.getLinkTuple());
+        assertEquals(linkB, linkBInTopo);
+        assertTrue(linkBInTopo.isFrozen());
+        assertEquals(TopologyElement.TYPE_OPTICAL_LAYER, linkBInTopo.getType());
+
+        // check events to be fired
+        // hostB should be added with reduced attachment points
+        List<HostEvent> apiAddedHostEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedHostEvents");
+        assertThat(apiAddedHostEvents, hasItem(hostBrev));
+
+        // hostA should not be ignored
+        List<HostEvent> apiRemovedHostEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedHostEvents");
+        assertThat(apiRemovedHostEvents, not(hasItem(hostA)));
+
+        List<LinkEvent> apiAddedLinkEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedLinkEvents");
+        assertThat(apiAddedLinkEvents, containsInAnyOrder(linkA, linkB));
+    }
+
+    /**
+     * Test to confirm topology replica transformation.
+     */
+    @Test
+    public void testAddHostMove() {
+        setupTopologyManager();
+
+        final Dpid dpid = new Dpid(1);
+        SwitchEvent sw = new SwitchEvent(dpid);
+        sw.createStringAttribute("foo", "bar");
+
+        final PortNumber portNumberA = new PortNumber((short) 2);
+        PortEvent portA = new PortEvent(dpid, portNumberA);
+        portA.createStringAttribute("fuzz", "buzz");
+
+        final PortNumber portNumberB = new PortNumber((short) 3);
+        PortEvent portB = new PortEvent(dpid, portNumberB);
+        portB.createStringAttribute("fizz", "buz");
+
+        final PortNumber portNumberC = new PortNumber((short) 4);
+        PortEvent portC = new PortEvent(dpid, portNumberC);
+        portC.createStringAttribute("fizz", "buz");
+
+        TestUtils.callMethod(theTopologyManager, "addSwitch", SwitchEvent.class, sw);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portA);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portB);
+        TestUtils.callMethod(theTopologyManager, "addPort", PortEvent.class, portC);
+
+        // Add hostA attached to a port which already has a link
+        final MACAddress macA = MACAddress.valueOf(666L);
+        HostEvent hostA = new HostEvent(macA);
+        hostA.addAttachmentPoint(portA.getSwitchPort());
+        final long timestampA = 392893200L;
+        hostA.setLastSeenTime(timestampA);
+
+        TestUtils.callMethod(theTopologyManager, "addHost", HostEvent.class, hostA);
+
+
+        // check topology structure
+        TopologyInternal topology = (TopologyInternal) theTopologyManager.getTopology();
+        SwitchEvent swInTopo = topology.getSwitchEvent(dpid);
+        assertEquals(sw, swInTopo);
+        assertTrue(swInTopo.isFrozen());
+        assertEquals("bar", swInTopo.getStringAttribute("foo"));
+
+        final SwitchPort portIdA = new SwitchPort(dpid, portNumberA);
+        PortEvent portAInTopo = topology.getPortEvent(portIdA);
+        assertEquals(portA, portAInTopo);
+        assertTrue(portAInTopo.isFrozen());
+        assertEquals("buzz", portAInTopo.getStringAttribute("fuzz"));
+
+        final SwitchPort portIdB = new SwitchPort(dpid, portNumberB);
+        PortEvent portBInTopo = topology.getPortEvent(portIdB);
+        assertEquals(portB, portBInTopo);
+        assertTrue(portBInTopo.isFrozen());
+        assertEquals("buz", portBInTopo.getStringAttribute("fizz"));
+
+        // hostA expected to be there
+        assertEquals(hostA, topology.getHostEvent(macA));
+        assertEquals(timestampA, topology.getHostEvent(macA).getLastSeenTime());
+
+        // check events to be fired
+        // hostA should be added
+        List<HostEvent> apiAddedHostEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedHostEvents");
+        assertThat(apiAddedHostEvents, hasItem(hostA));
+
+
+        // clear event before moving host
+        apiAddedHostEvents.clear();
+
+        HostEvent hostAmoved = new HostEvent(macA);
+        hostAmoved.addAttachmentPoint(portB.getSwitchPort());
+        final long timestampAmoved = 392893201L;
+        hostAmoved.setLastSeenTime(timestampAmoved);
+
+        TestUtils.callMethod(theTopologyManager, "addHost", HostEvent.class, hostAmoved);
+
+        assertEquals(hostAmoved, topology.getHostEvent(macA));
+        assertEquals(timestampAmoved, topology.getHostEvent(macA).getLastSeenTime());
+
+        // hostA expected to be there with new attachment point
+        apiAddedHostEvents
+            = TestUtils.getField(theTopologyManager, "apiAddedHostEvents");
+        assertThat(apiAddedHostEvents, hasItem(hostAmoved));
+
+        // hostA is updated not removed
+        List<HostEvent> apiRemovedHostEvents
+            = TestUtils.getField(theTopologyManager, "apiRemovedHostEvents");
+        assertThat(apiRemovedHostEvents, not(hasItem(hostA)));
+    }
 }