Add string attributes to self-containd objects.

- Add common super class to *Event classes, which handles attributes.
- Add string attributes to *Event classes (ONOS-1564)
- Populate string attributes.
  Picked random attribute obtained from OF, just to initially populate attrs.
  Attributes to be used for each elements should be revisited later.
- *Impl class to use/reference self-contained objects
   prep-work for snapshot in mind.
- unified equals implementations
- Add unfrozen Copy constructor.
- Add freeze check to fixed attributes.
- Remove get*Impl which was not really adding value.

Change-Id: I10f9538f87d133a22237bd8ab97b8de421d3930b
diff --git a/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java b/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java
index 02041eb..110aef5 100644
--- a/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java
+++ b/src/main/java/net/onrc/onos/core/topology/DeviceEvent.java
@@ -1,8 +1,11 @@
 package net.onrc.onos.core.topology;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.core.topology.web.serializers.DeviceEventSerializer;
@@ -20,19 +23,22 @@
  * Multiple attachmentPoints can be specified to batch events into 1 object.
  * Each should be treated as independent events.
  * <p/>
- * TODO: We probably want common base class/interface for Self-Contained Event Object
+ * TODO: Rename to match what it is. (Switch/Port/Link/Device)Snapshot?
+ * FIXME: Current implementation directly use this object as
+ *        Replication message, but should be sending update operation info.
  */
 @JsonSerialize(using = DeviceEventSerializer.class)
-public class DeviceEvent {
+public class DeviceEvent extends TopologyElement<DeviceEvent> {
+
     private final MACAddress mac;
-    protected List<SwitchPort> attachmentPoints;
+    private List<SwitchPort> attachmentPoints;
     private long lastSeenTime;
 
     /**
      * Default constructor for Serializer to use.
      */
     @Deprecated
-    public DeviceEvent() {
+    protected DeviceEvent() {
         mac = null;
     }
 
@@ -44,31 +50,103 @@
         this.attachmentPoints = new LinkedList<>();
     }
 
+    /**
+     * Create an unfrozen copy of given Object.
+     *
+     * @param original to make copy of.
+     */
+    public DeviceEvent(DeviceEvent original) {
+        super(original);
+        this.mac = original.mac;
+        this.attachmentPoints = new ArrayList<>(original.attachmentPoints);
+    }
+
+
     public MACAddress getMac() {
         return mac;
     }
 
     public List<SwitchPort> getAttachmentPoints() {
-        return attachmentPoints;
+        return Collections.unmodifiableList(attachmentPoints);
     }
 
     public void setAttachmentPoints(List<SwitchPort> attachmentPoints) {
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
         this.attachmentPoints = attachmentPoints;
     }
 
     public void addAttachmentPoint(SwitchPort attachmentPoint) {
-        // may need to maintain uniqness
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
+        // may need to maintain uniqueness
         this.attachmentPoints.add(0, attachmentPoint);
     }
 
+    public boolean removeAttachmentPoint(SwitchPort attachmentPoint) {
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
+        return this.attachmentPoints.remove(attachmentPoint);
+    }
+
+
+    public long getLastSeenTime() {
+        return lastSeenTime;
+    }
+
+    public void setLastSeenTime(long lastSeenTime) {
+        if (isFrozen()) {
+            throw new IllegalStateException("Tried to modify frozen instance: " + this);
+        }
+        this.lastSeenTime = lastSeenTime;
+    }
+
     @Override
     public String toString() {
         return "[DeviceEvent " + mac + " attachmentPoints:" + attachmentPoints + "]";
     }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result
+                + ((attachmentPoints == null) ? 0 : attachmentPoints.hashCode());
+        result = prime * result + ((mac == null) ? 0 : mac.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!super.equals(obj)) {
+            return false;
+        }
+
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        DeviceEvent other = (DeviceEvent) obj;
+
+        if (!super.equals(obj)) {
+            return false;
+        }
+
+        // XXX lastSeenTime excluded from Equality condition, is it OK?
+        return Objects.equals(mac, other.mac) &&
+                Objects.equals(this.attachmentPoints, other.attachmentPoints);
+    }
+
     // Assuming mac is unique cluster-wide
     public static ByteBuffer getDeviceID(final byte[] mac) {
-        return (ByteBuffer) ByteBuffer.allocate(2 + mac.length).putChar('D').put(mac).flip();
+        return (ByteBuffer) ByteBuffer.allocate(2 + mac.length)
+                .putChar('D').put(mac).flip();
     }
 
     public byte[] getID() {
@@ -78,12 +156,4 @@
     public ByteBuffer getIDasByteBuffer() {
         return getDeviceID(mac.toBytes());
     }
-
-    public long getLastSeenTime() {
-        return lastSeenTime;
-    }
-
-    public void setLastSeenTime(long lastSeenTime) {
-        this.lastSeenTime = lastSeenTime;
-    }
 }