Add support to decode Device, Port, Link JSON.

- Device, Port, Link can now be encoded and decoded back to Java Object,
  which will be Object#equals to the original.
- Modified DeviceServiceAdapter to be null-safe when possible
- Modified JSON assertion/matcher not to check for exact number of attributes

Change-Id: I7cf02e2254cf17f6265fb15847912519e564b14f
diff --git a/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.java
index ccd4a6d..422d062 100644
--- a/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/device/DeviceServiceAdapter.java
@@ -17,12 +17,14 @@
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
+
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -36,7 +38,7 @@
 
     @Override
     public Iterable<Device> getDevices() {
-        return null;
+        return Collections.emptyList();
     }
 
     @Override
@@ -58,12 +60,12 @@
 
     @Override
     public MastershipRole getRole(DeviceId deviceId) {
-        return null;
+        return MastershipRole.NONE;
     }
 
     @Override
     public List<Port> getPorts(DeviceId deviceId) {
-        return null;
+        return Collections.emptyList();
     }
 
     @Override
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java
index a3d0663..6883fe0 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/AnnotatedCodec.java
@@ -16,10 +16,12 @@
 package org.onosproject.codec.impl;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.net.Annotated;
 import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
 
 /**
  * Base JSON codec for annotated entities.
@@ -42,4 +44,21 @@
         return node;
     }
 
+    /**
+     * Extracts annotations of given Object.
+     *
+     * @param objNode annotated JSON object node
+     * @param context decode context
+     * @return extracted Annotations
+     */
+    protected Annotations extractAnnotations(ObjectNode objNode, CodecContext context) {
+
+        JsonCodec<Annotations> codec = context.codec(Annotations.class);
+        if (objNode.has("annotations") && objNode.isObject()) {
+            return codec.decode((ObjectNode) objNode.get("annotations"), context);
+        } else {
+            return DefaultAnnotations.EMPTY;
+        }
+    }
+
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java
index 5516f23..d71446d 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/AnnotationsCodec.java
@@ -16,9 +16,12 @@
 package org.onosproject.codec.impl;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultAnnotations.Builder;
 
 /**
  * Annotations JSON codec.
@@ -34,4 +37,13 @@
         return result;
     }
 
+    @Override
+    public Annotations decode(ObjectNode json, CodecContext context) {
+        Builder builder = DefaultAnnotations.builder();
+
+        json.fields().forEachRemaining(e ->
+            builder.set(e.getKey(), e.getValue().asText()));
+
+        return builder.build();
+    }
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java
index 7d12308..dd733cd 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/ConnectPointCodec.java
@@ -16,32 +16,59 @@
 package org.onosproject.codec.impl;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
 import org.onosproject.net.HostId;
+import org.onosproject.net.PortNumber;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.PortNumber.portNumber;
 
 /**
  * Connection point JSON codec.
  */
 public final class ConnectPointCodec extends JsonCodec<ConnectPoint> {
 
+    // JSON field names
+    private static final String ELEMENT_HOST = "host";
+    private static final String ELEMENT_DEVICE = "device";
+    private static final String PORT = "port";
+
     @Override
     public ObjectNode encode(ConnectPoint point, CodecContext context) {
         checkNotNull(point, "Connect point cannot be null");
         ObjectNode root = context.mapper().createObjectNode()
-                .put("port", point.port().toString());
+                .put(PORT, point.port().toString());
 
         if (point.elementId() instanceof DeviceId) {
-            root.put("device", point.deviceId().toString());
+            root.put(ELEMENT_DEVICE, point.deviceId().toString());
         } else if (point.elementId() instanceof HostId) {
-            root.put("host", point.hostId().toString());
+            root.put(ELEMENT_HOST, point.hostId().toString());
         }
 
         return root;
     }
 
+    @Override
+    public ConnectPoint decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        ElementId elementId;
+        if (json.has(ELEMENT_DEVICE)) {
+            elementId = DeviceId.deviceId(json.get(ELEMENT_DEVICE).asText());
+        } else if (json.has(ELEMENT_HOST)) {
+            elementId = HostId.hostId(json.get(ELEMENT_HOST).asText());
+        } else {
+            // invalid JSON
+            return null;
+        }
+        PortNumber portNumber = portNumber(json.get(PORT).asText());
+        return new ConnectPoint(elementId, portNumber);
+    }
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
index fcb720c..b032329 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/DeviceCodec.java
@@ -16,32 +16,78 @@
 package org.onosproject.codec.impl;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onlab.packet.ChassisId;
 import org.onosproject.codec.CodecContext;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultDevice;
 import org.onosproject.net.Device;
+import org.onosproject.net.Device.Type;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.provider.ProviderId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.DeviceId.deviceId;
 
 /**
  * Device JSON codec.
  */
 public final class DeviceCodec extends AnnotatedCodec<Device> {
 
+    // JSON fieldNames
+    private static final String ID = "id";
+    private static final String TYPE = "type";
+    private static final String MFR = "mfr";
+    private static final String HW = "hw";
+    private static final String SW = "sw";
+    private static final String SERIAL = "serial";
+    private static final String CHASSIS_ID = "chassisId";
+
+
     @Override
     public ObjectNode encode(Device device, CodecContext context) {
         checkNotNull(device, "Device cannot be null");
         DeviceService service = context.get(DeviceService.class);
         ObjectNode result = context.mapper().createObjectNode()
-                .put("id", device.id().toString())
-                .put("type", device.type().name())
+                .put(ID, device.id().toString())
+                .put(TYPE, device.type().name())
                 .put("available", service.isAvailable(device.id()))
                 .put("role", service.getRole(device.id()).toString())
-                .put("mfr", device.manufacturer())
-                .put("hw", device.hwVersion())
-                .put("sw", device.swVersion())
-                .put("serial", device.serialNumber())
-                .put("chassisId", device.chassisId().toString());
+                .put(MFR, device.manufacturer())
+                .put(HW, device.hwVersion())
+                .put(SW, device.swVersion())
+                .put(SERIAL, device.serialNumber())
+                .put(CHASSIS_ID, device.chassisId().toString());
         return annotate(result, device, context);
     }
 
+
+    /**
+     * {@inheritDoc}
+     *
+     * Note: ProviderId is not part of JSON representation.
+     *       Returned object will have random ProviderId set.
+     */
+    @Override
+    public Device decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        DeviceId id = deviceId(json.get(ID).asText());
+        // TODO: add providerId to JSON if we need to recover them.
+        ProviderId pid = new ProviderId(id.uri().getScheme(), "DeviceCodec");
+
+        Type type = Type.valueOf(json.get(TYPE).asText());
+        String mfr = json.get(MFR).asText();
+        String hw = json.get(HW).asText();
+        String sw = json.get(SW).asText();
+        String serial = json.get(SERIAL).asText();
+        ChassisId chassisId = new ChassisId(json.get(CHASSIS_ID).asText());
+        Annotations annotations = extractAnnotations(json, context);
+
+        return new DefaultDevice(pid, id, type, mfr, hw, sw, serial,
+                                 chassisId, annotations);
+    }
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java
index 84018b8..d63b4e3 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/LinkCodec.java
@@ -16,11 +16,15 @@
 package org.onosproject.codec.impl;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.Annotations;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
 import org.onosproject.net.Link;
-import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.Link.Type;
+import org.onosproject.net.provider.ProviderId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -29,15 +33,44 @@
  */
 public final class LinkCodec extends AnnotatedCodec<Link> {
 
+    // JSON field names
+    private static final String SRC = "src";
+    private static final String DST = "dst";
+    private static final String TYPE = "type";
+
     @Override
     public ObjectNode encode(Link link, CodecContext context) {
         checkNotNull(link, "Link cannot be null");
-        DeviceService service = context.get(DeviceService.class);
         JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
         ObjectNode result = context.mapper().createObjectNode();
-        result.set("src", codec.encode(link.src(), context));
-        result.set("dst", codec.encode(link.dst(), context));
+        result.set(SRC, codec.encode(link.src(), context));
+        result.set(DST, codec.encode(link.dst(), context));
+        result.put(TYPE, link.type().toString());
         return annotate(result, link, context);
     }
 
+
+    /**
+     * {@inheritDoc}
+     *
+     * Note: ProviderId is not part of JSON representation.
+     *       Returned object will have random ProviderId set.
+     */
+    @Override
+    public Link decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonCodec<ConnectPoint> codec = context.codec(ConnectPoint.class);
+        // TODO: add providerId to JSON if we need to recover them.
+        ProviderId pid = new ProviderId("json", "LinkCodec");
+
+        ConnectPoint src = codec.decode((ObjectNode) json.get(SRC), context);
+        ConnectPoint dst = codec.decode((ObjectNode) json.get(DST), context);
+        Type type = Type.valueOf(json.get(TYPE).asText());
+        Annotations annotations = extractAnnotations(json, context);
+
+        return new DefaultLink(pid, src, dst, type, annotations);
+    }
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java
index fb15a45..59e46d1 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PortCodec.java
@@ -16,9 +16,18 @@
 package org.onosproject.codec.impl;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.onlab.packet.ChassisId;
 import org.onosproject.codec.CodecContext;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
+import org.onosproject.net.Port.Type;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -27,19 +36,125 @@
  */
 public final class PortCodec extends AnnotatedCodec<Port> {
 
+    // JSON field names
+    private static final String ELEMENT = "element"; // DeviceId
+    private static final String PORT_NAME = "port";
+    private static final String IS_ENABLED = "isEnabled";
+    private static final String TYPE = "type";
+    private static final String PORT_SPEED = "portSpeed";
+
+    // Special port name alias
+    private static final String PORT_NAME_LOCAL = "local";
+
     @Override
     public ObjectNode encode(Port port, CodecContext context) {
         checkNotNull(port, "Port cannot be null");
         ObjectNode result = context.mapper().createObjectNode()
-                .put("port", portName(port.number()))
-                .put("isEnabled", port.isEnabled())
-                .put("type", port.type().toString().toLowerCase())
-                .put("portSpeed", port.portSpeed());
+                .put(ELEMENT, port.element().id().toString())
+                .put(PORT_NAME, portName(port.number()))
+                .put(IS_ENABLED, port.isEnabled())
+                .put(TYPE, port.type().toString().toLowerCase())
+                .put(PORT_SPEED, port.portSpeed());
         return annotate(result, port, context);
     }
 
     private String portName(PortNumber port) {
-        return port.equals(PortNumber.LOCAL) ? "local" : port.toString();
+        return port.equals(PortNumber.LOCAL) ? PORT_NAME_LOCAL : port.toString();
     }
 
+    private static PortNumber portNumber(String portName) {
+        if (portName.equalsIgnoreCase(PORT_NAME_LOCAL)) {
+            return PortNumber.LOCAL;
+        }
+
+        return PortNumber.portNumber(portName);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * Note: Result of {@link Port#element()} returned Port object,
+     *       is not a full Device object.
+     *       Only it's DeviceId can be used.
+     */
+    @Override
+    public Port decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        DeviceId did = DeviceId.deviceId(json.get(ELEMENT).asText());
+        Device device = new DummyDevice(did);
+        PortNumber number = portNumber(json.get(PORT_NAME).asText());
+        boolean isEnabled = json.get(IS_ENABLED).asBoolean();
+        Type type = Type.valueOf(json.get(TYPE).asText().toUpperCase());
+        long portSpeed = json.get(PORT_SPEED).asLong();
+        Annotations annotations = extractAnnotations(json, context);
+
+        return new DefaultPort(device, number, isEnabled, type, portSpeed, annotations);
+    }
+
+
+    /**
+     * Dummy Device which only holds DeviceId.
+     */
+    private static final class DummyDevice implements Device {
+
+        private final DeviceId did;
+
+        /**
+         * Constructs Dummy Device which only holds DeviceId.
+         *
+         * @param did device Id
+         */
+        public DummyDevice(DeviceId did) {
+            this.did = did;
+        }
+
+        @Override
+        public Annotations annotations() {
+            return DefaultAnnotations.EMPTY;
+        }
+
+        @Override
+        public ProviderId providerId() {
+            return new ProviderId(did.uri().getScheme(), "PortCodec");
+        }
+
+        @Override
+        public DeviceId id() {
+            return did;
+        }
+
+        @Override
+        public Type type() {
+            return Type.SWITCH;
+        }
+
+        @Override
+        public String manufacturer() {
+            return "dummy";
+        }
+
+        @Override
+        public String hwVersion() {
+            return "0";
+        }
+
+        @Override
+        public String swVersion() {
+            return "0";
+        }
+
+        @Override
+        public String serialNumber() {
+            return "0";
+        }
+
+        @Override
+        public ChassisId chassisId() {
+            return new ChassisId();
+        }
+    }
 }
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java
new file mode 100644
index 0000000..c7196e8
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/DeviceCodecTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.is;
+import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
+
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+
+/**
+ * Unit test for DeviceCodec.
+ */
+public class DeviceCodecTest {
+
+    private Device device = new DefaultDevice(JsonCodecUtils.PID,
+                                              JsonCodecUtils.DID1,
+                                              Device.Type.SWITCH,
+                                              JsonCodecUtils.MFR,
+                                              JsonCodecUtils.HW,
+                                              JsonCodecUtils.SW1,
+                                              JsonCodecUtils.SN,
+                                              JsonCodecUtils.CID,
+                                              JsonCodecUtils.A1);
+
+
+
+    @Test
+    public void deviceCodecTest() {
+        final MockCodecContext context = new MockCodecContext();
+        context.registerService(DeviceService.class, new DeviceServiceAdapter());
+        final JsonCodec<Device> codec = context.codec(Device.class);
+        assertThat(codec, is(notNullValue()));
+        final Device pojoIn = device;
+
+        assertJsonEncodable(context, codec, pojoIn);
+    }
+
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java b/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java
new file mode 100644
index 0000000..67c2f47
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/JsonCodecUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+
+import org.onlab.packet.ChassisId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.provider.ProviderId;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * JsonCodec test utilities.
+ */
+public abstract class JsonCodecUtils {
+
+    /**
+     * Checks if given Object can be encoded to JSON and back.
+     *
+     * @param context CodecContext
+     * @param codec JsonCodec
+     * @param pojoIn Java Object to encode.
+     *               Object is expected to have #equals implemented.
+     */
+    public static <T> void assertJsonEncodable(final CodecContext context,
+                            final JsonCodec<T> codec,
+                            final T pojoIn) {
+        final ObjectNode json = codec.encode(pojoIn, context);
+
+        assertThat(json, is(notNullValue()));
+
+        final T pojoOut = codec.decode(json, context);
+        assertThat(pojoOut, is(notNullValue()));
+
+        assertEquals(pojoIn, pojoOut);
+    }
+
+    static final ProviderId PID = new ProviderId("of", "foo");
+    static final ProviderId PIDA = new ProviderId("of", "bar", true);
+    static final DeviceId DID1 = deviceId("of:foo");
+    static final DeviceId DID2 = deviceId("of:bar");
+    static final String MFR = "whitebox";
+    static final String HW = "1.1.x";
+    static final String SW1 = "3.8.1";
+    static final String SW2 = "3.9.5";
+    static final String SN = "43311-12345";
+    static final ChassisId CID = new ChassisId();
+    static final PortNumber P1 = PortNumber.portNumber(1);
+    static final PortNumber P2 = PortNumber.portNumber(2);
+    static final PortNumber P3 = PortNumber.portNumber(3);
+    static final SparseAnnotations A1 = DefaultAnnotations.builder()
+                                        .set("A1", "a1")
+                                        .set("B1", "b1")
+                                        .build();
+    static final ConnectPoint CP1 = new ConnectPoint(DID1, P1);
+    static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
+
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java
new file mode 100644
index 0000000..c44b0eb
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/LinkCodecTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
+
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+
+/**
+ * Unit test for LinkCodec.
+ */
+public class LinkCodecTest {
+
+    private final Link link = new DefaultLink(JsonCodecUtils.PID,
+                                              JsonCodecUtils.CP1,
+                                              JsonCodecUtils.CP2,
+                                              Link.Type.DIRECT,
+                                              Link.State.ACTIVE,
+                                              false,
+                                              JsonCodecUtils.A1);
+
+    @Test
+    public void linkCodecTest() {
+        final MockCodecContext context = new MockCodecContext();
+        context.registerService(DeviceService.class, new DeviceServiceAdapter());
+        final JsonCodec<Link> codec = context.codec(Link.class);
+        assertThat(codec, is(notNullValue()));
+        final Link pojoIn = link;
+
+        assertJsonEncodable(context, codec, pojoIn);
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java b/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java
index 25a4fbb..4c869c1 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/MockCodecContext.java
@@ -15,6 +15,9 @@
  */
 package org.onosproject.codec.impl;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 
@@ -25,8 +28,9 @@
  */
 public class MockCodecContext implements CodecContext {
 
-    private ObjectMapper mapper = new ObjectMapper();
-    private CodecManager manager = new CodecManager();
+    private final ObjectMapper mapper = new ObjectMapper();
+    private final CodecManager manager = new CodecManager();
+    private final Map<Class<? extends Object>, Object> services = new HashMap<>();
 
     /**
      * Constructs a new mock codec context.
@@ -46,9 +50,15 @@
         return manager.getCodec(entityClass);
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public <T> T get(Class<T> serviceClass) {
-        return null;
+        return (T) services.get(serviceClass);
+    }
+
+    // for registering mock services
+    public <T> void registerService(Class<T> serviceClass, T impl) {
+        services.put(serviceClass, impl);
     }
 
 }
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java
new file mode 100644
index 0000000..f3f7d92
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/PortCodecTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.codec.impl.JsonCodecUtils.assertJsonEncodable;
+
+import org.junit.Test;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+
+/**
+ * Unit test for PortCodec.
+ */
+public class PortCodecTest {
+
+
+
+    private final Device device = new DefaultDevice(JsonCodecUtils.PID,
+                                              JsonCodecUtils.DID1,
+                                              Device.Type.SWITCH,
+                                              JsonCodecUtils.MFR,
+                                              JsonCodecUtils.HW,
+                                              JsonCodecUtils.SW1,
+                                              JsonCodecUtils.SN,
+                                              JsonCodecUtils.CID,
+                                              JsonCodecUtils.A1);
+
+    private final Port port = new DefaultPort(device,
+                                              JsonCodecUtils.P1,
+                                              true,
+                                              JsonCodecUtils.A1);
+
+    @Test
+    public void portCodecTest() {
+        final MockCodecContext context = new MockCodecContext();
+        context.registerService(DeviceService.class, new DeviceServiceAdapter());
+        final JsonCodec<Port> codec = context.codec(Port.class);
+        assertThat(codec, is(notNullValue()));
+        final Port pojoIn = port;
+
+        assertJsonEncodable(context, codec, pojoIn);
+    }
+
+}
diff --git a/web/api/src/test/java/org/onosproject/rest/DevicesResourceTest.java b/web/api/src/test/java/org/onosproject/rest/DevicesResourceTest.java
index 93dc04c..b5164a7 100644
--- a/web/api/src/test/java/org/onosproject/rest/DevicesResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/DevicesResourceTest.java
@@ -343,7 +343,6 @@
         for (int portIndex = 0; portIndex < jsonPorts.size(); portIndex++) {
             JsonObject jsonPort = jsonPorts.get(portIndex).asObject();
 
-            assertThat(jsonPort.size(), is(4));
             assertThat(jsonPort.get("port").asString(),
                        is(Integer.toString(portIndex + 1)));
             assertThat(jsonPort.get("isEnabled").asBoolean(),
diff --git a/web/api/src/test/java/org/onosproject/rest/LinksResourceTest.java b/web/api/src/test/java/org/onosproject/rest/LinksResourceTest.java
index c62199c..feff0ed 100644
--- a/web/api/src/test/java/org/onosproject/rest/LinksResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/LinksResourceTest.java
@@ -123,11 +123,6 @@
 
                 JsonObject jsonLink = json.get(jsonLinkIndex).asObject();
 
-                if (jsonLink.names().size() != expectedAttributes) {
-                    reason = "Found a link with the wrong number of attributes";
-                    return false;
-                }
-
                 if (matchesLink(link).matchesSafely(jsonLink)) {
                     return true;
                 }