KryoNamespace improvements

- Ignore duplicate Namespace registration
- Add friendly name for helping logging

- ONOS-4528

Change-Id: Id78f2a0f6e9715a7880875039825e294a68592a9
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index c1d25b7..4073804 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -277,7 +277,7 @@
             .register(char[].class)
             .register(String[].class)
             .register(boolean[].class)
-            .build();
+            .build("BASIC");
 
     /**
      * KryoNamespace which can serialize ON.lab misc classes.
@@ -297,7 +297,7 @@
             .register(Bandwidth.class)
             .register(Bandwidth.bps(1L).getClass())
             .register(Bandwidth.bps(1.0).getClass())
-            .build();
+            .build("MISC");
 
     /**
      * Kryo registration Id for user custom registration.
@@ -542,7 +542,7 @@
             .register(VlanCodec.class)
             .register(MplsCodec.class)
             .register(NoOpCodec.class)
-            .build();
+            .build("API");
 
 
     // not to be instantiated
diff --git a/utils/misc/src/main/java/org/onlab/util/KryoNamespace.java b/utils/misc/src/main/java/org/onlab/util/KryoNamespace.java
index a8b1e16..70fdfc8 100644
--- a/utils/misc/src/main/java/org/onlab/util/KryoNamespace.java
+++ b/utils/misc/src/main/java/org/onlab/util/KryoNamespace.java
@@ -35,7 +35,9 @@
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -62,9 +64,12 @@
      */
     public static final int INITIAL_ID = 16;
 
+    private static final String NO_NAME = "(no name)";
+
     private static final Logger log = getLogger(KryoNamespace.class);
 
 
+
     private final KryoPool pool = new KryoPool.Builder(this)
                                         .softReferences()
                                         .build();
@@ -72,6 +77,7 @@
     private final ImmutableList<RegistrationBlock> registeredBlocks;
 
     private final boolean registrationRequired;
+    private final String friendlyName;
 
 
     /**
@@ -91,10 +97,20 @@
          * @return KryoNamespace
          */
         public KryoNamespace build() {
+            return build(NO_NAME);
+        }
+
+        /**
+         * Builds a {@link KryoNamespace} instance.
+         *
+         * @param friendlyName friendly name for the namespace
+         * @return KryoNamespace
+         */
+        public KryoNamespace build(String friendlyName) {
             if (!types.isEmpty()) {
                 blocks.add(new RegistrationBlock(this.blockHeadId, types));
             }
-            return new KryoNamespace(blocks, registrationRequired).populate(1);
+            return new KryoNamespace(blocks, registrationRequired, friendlyName).populate(1);
         }
 
         /**
@@ -109,7 +125,7 @@
             if (!types.isEmpty()) {
                 if (id != FLOATING_ID && id < blockHeadId + types.size()) {
 
-                    log.warn("requested nextId {} could potentially overlap" +
+                    log.warn("requested nextId {} could potentially overlap " +
                              "with existing registrations {}+{} ",
                              id, blockHeadId, types.size());
                 }
@@ -170,6 +186,12 @@
          * @return this
          */
         public Builder register(final KryoNamespace ns) {
+
+            if (blocks.containsAll(ns.registeredBlocks)) {
+                // Everything was already registered.
+                log.debug("Ignoring {}, already registered.", ns);
+                return this;
+            }
             for (RegistrationBlock block : ns.registeredBlocks) {
                 this.register(block);
             }
@@ -204,10 +226,14 @@
      *
      * @param registeredTypes types to register
      * @param registrationRequired
+     * @param friendlyName friendly name for the namespace
      */
-    private KryoNamespace(final List<RegistrationBlock> registeredTypes, boolean registrationRequired) {
+    private KryoNamespace(final List<RegistrationBlock> registeredTypes,
+                          boolean registrationRequired,
+                          String friendlyName) {
         this.registeredBlocks = ImmutableList.copyOf(registeredTypes);
         this.registrationRequired = registrationRequired;
+        this.friendlyName =  checkNotNull(friendlyName);
     }
 
     /**
@@ -373,6 +399,10 @@
         }
     }
 
+    private String friendlyName() {
+        return friendlyName;
+    }
+
     /**
      * Creates a Kryo instance.
      *
@@ -408,12 +438,12 @@
      * @param serializer Specific serializer to register or null to use default.
      * @param id         type registration id to use
      */
-    private static void register(Kryo kryo, Class<?> type, Serializer<?> serializer, int id) {
+    private void register(Kryo kryo, Class<?> type, Serializer<?> serializer, int id) {
         Registration existing = kryo.getRegistration(id);
         if (existing != null) {
             if (existing.getType() != type) {
-                log.error("Failed to register {} as {}, {} was already registered.",
-                          type, id, existing.getType());
+                log.error("{}: Failed to register {} as {}, {} was already registered.",
+                          friendlyName(), type, id, existing.getType());
 
                 throw new IllegalStateException(String.format(
                           "Failed to register %s as %s, %s was already registered.",
@@ -430,8 +460,8 @@
             r = kryo.register(type, serializer, id);
         }
         if (r.getId() != id) {
-            log.debug("{} already registed as {}. Skipping {}.",
-                     r.getType(), r.getId(), id);
+            log.warn("{}: {} already registed as {}. Skipping {}.",
+                     friendlyName(), r.getType(), r.getId(), id);
         }
         log.trace("{} registered as {}", r.getType(), r.getId());
     }
@@ -453,6 +483,13 @@
 
     @Override
     public String toString() {
+        if (friendlyName != NO_NAME) {
+            return MoreObjects.toStringHelper(getClass())
+                    .omitNullValues()
+                    .add("friendlyName", friendlyName)
+                    // omit lengthy detail, when there's a name
+                    .toString();
+        }
         return MoreObjects.toStringHelper(getClass())
                     .add("registeredBlocks", registeredBlocks)
                     .toString();
@@ -482,5 +519,24 @@
                     .add("types", types)
                     .toString();
         }
+
+        @Override
+        public int hashCode() {
+            return types.hashCode();
+        }
+
+        // Only the registered types are used for equality.
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+
+            if (obj instanceof RegistrationBlock) {
+                RegistrationBlock that = (RegistrationBlock) obj;
+                return Objects.equals(this.types, that.types);
+            }
+            return false;
+        }
     }
 }