Repository: docker-java/docker-java
Pull request #197 (2015-04-13 05:00:13)

Allow for null bindings
Null bindings is possible, see the following example.
```
chenchun:project $ docker run -d redis
0efc652e829b0aca40aa70fa8000bc22837cae0cb42e26b7d3ceb8b65b65f889
chenchun:project $ docker inspect 0efc652e829b0aca40aa70fa8000bc22837cae0cb42e26b7d3ceb8b65b65f889
[{
    "Args": [
        "redis-server"
    ],
    "Config": {
        "AttachStderr": false,
        "AttachStdin": false,
        "AttachStdout": false,
        "Cmd": [
            "redis-server"
        ],
        "CpuShares": 0,
        "Cpuset": "",
        "Domainname": "",
        "Entrypoint": [
            "/entrypoint.sh"
        ],
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "REDIS_VERSION=2.8.17",
            "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-2.8.17.tar.gz",
            "REDIS_DOWNLOAD_SHA1=913479f9d2a283bfaadd1444e17e7bab560e5d1e"
        ],
        "ExposedPorts": {
            "6379/tcp": {}
        },
        "Hostname": "0efc652e829b",
        "Image": "redis",
        "Memory": 0,
        "MemorySwap": 0,
        "NetworkDisabled": false,
        "OnBuild": null,
        "OpenStdin": false,
        "PortSpecs": null,
        "SecurityOpt": null,
        "StdinOnce": false,
        "Tty": false,
        "User": "",
        "Volumes": {
            "/data": {}
        },
        "WorkingDir": "/data"
    },
    "Created": "2015-04-13T04:56:02.003752286Z",
    "Driver": "aufs",
    "ExecDriver": "native-0.2",
    "HostConfig": {
        "Binds": null,
        "CapAdd": null,
        "CapDrop": null,
        "ContainerIDFile": "",
        "Devices": [],
        "Dns": null,
        "DnsSearch": null,
        "ExtraHosts": null,
        "Links": null,
        "LxcConf": [],
        "NetworkMode": "bridge",
        "PortBindings": {},
        "Privileged": false,
        "PublishAllPorts": false,
        "RestartPolicy": {
            "MaximumRetryCount": 0,
            "Name": ""
        },
        "VolumesFrom": null
    },
    "HostnamePath": "/mnt/sda1/var/lib/docker/containers/0efc652e829b0aca40aa70fa8000bc22837cae0cb42e26b7d3ceb8b65b65f889/hostname",
    "HostsPath": "/mnt/sda1/var/lib/docker/containers/0efc652e829b0aca40aa70fa8000bc22837cae0cb42e26b7d3ceb8b65b65f889/hosts",
    "Id": "0efc652e829b0aca40aa70fa8000bc22837cae0cb42e26b7d3ceb8b65b65f889",
    "Image": "3ce54e911389a2b08207b0a4d01c3131ce01b617ecc1c248f6d81ffdbebd628d",
    "MountLabel": "",
    "Name": "/berserk_bell",
    "NetworkSettings": {
        "Bridge": "docker0",
        "Gateway": "172.17.42.1",
        "IPAddress": "172.17.0.6",
        "IPPrefixLen": 16,
        "MacAddress": "02:42:ac:11:00:06",
        "PortMapping": null,
        "Ports": {
            "6379/tcp": null
        }
    },
    "Path": "/entrypoint.sh",
    "ProcessLabel": "",
    "ResolvConfPath": "/mnt/sda1/var/lib/docker/containers/0efc652e829b0aca40aa70fa8000bc22837cae0cb42e26b7d3ceb8b65b65f889/resolv.conf",
    "State": {
        "ExitCode": 0,
        "FinishedAt": "0001-01-01T00:00:00Z",
        "Paused": false,
        "Pid": 988,
        "Restarting": false,
        "Running": true,
        "StartedAt": "2015-04-13T04:56:02.252112678Z"
    },
    "Volumes": {
        "/data": "/mnt/sda1/var/lib/docker/vfs/dir/d6441492325f772922ebb313020e98df1b318c3b6217dd9f9c40fe54b28a81d4"
    },
    "VolumesRW": {
        "/data": true
    }
}
]chenchun:project $ 
```


Patch:
diff --git a/src/main/java/com/github/dockerjava/api/model/Ports.java b/src/main/java/com/github/dockerjava/api/model/Ports.java
index d7c8a324..0c2df94a 100644
--- a/src/main/java/com/github/dockerjava/api/model/Ports.java
+++ b/src/main/java/com/github/dockerjava/api/model/Ports.java
@@ -75,7 +75,11 @@ public class Ports {
             Binding[] bindings = ports.get(exposedPort);
             ports.put(exposedPort, (Binding[]) ArrayUtils.add(bindings, binding));
         } else {
-            ports.put(exposedPort, new Binding[]{binding});
+            if (binding == null) {
+                ports.put(exposedPort, null);
+            } else {
+                ports.put(exposedPort, new Binding[]{binding});
+            }
         }
     }
 
@@ -282,12 +286,16 @@ public class Ports {
 
                 Map.Entry<String, JsonNode> portNode = it.next();
                 JsonNode bindingsArray = portNode.getValue();
-                for (int i = 0; i < bindingsArray.size(); i++) {
-                    JsonNode bindingNode = bindingsArray.get(i);
-                    if (!bindingNode.equals(NullNode.getInstance())) {
-                        String hostIp = bindingNode.get("HostIp").textValue();
-                        int hostPort = bindingNode.get("HostPort").asInt();
-                        out.bind(ExposedPort.parse(portNode.getKey()), new Binding(hostIp, hostPort));
+                if (bindingsArray.equals(NullNode.getInstance())) {
+                    out.bind(ExposedPort.parse(portNode.getKey()), null);
+                } else {
+                    for (int i = 0; i < bindingsArray.size(); i++) {
+                        JsonNode bindingNode = bindingsArray.get(i);
+                        if (!bindingNode.equals(NullNode.getInstance())) {
+                            String hostIp = bindingNode.get("HostIp").textValue();
+                            int hostPort = bindingNode.get("HostPort").asInt();
+                            out.bind(ExposedPort.parse(portNode.getKey()), new Binding(hostIp, hostPort));
+                        }
                     }
                 }
             }
@@ -304,14 +312,18 @@ public class Ports {
             jsonGen.writeStartObject();
             for(Entry<ExposedPort, Binding[]> entry : portBindings.getBindings().entrySet()){
                 jsonGen.writeFieldName(entry.getKey().toString());
-                jsonGen.writeStartArray();
-                for (Binding binding : entry.getValue()) {
-                    jsonGen.writeStartObject();
-                    jsonGen.writeStringField("HostIp", binding.getHostIp() == null ? "" : binding.getHostIp());
-                    jsonGen.writeStringField("HostPort", binding.getHostPort() == null ? "" : binding.getHostPort().toString());
-                    jsonGen.writeEndObject();
+                if (entry.getValue() != null) {
+                    jsonGen.writeStartArray();
+                    for (Binding binding : entry.getValue()) {
+                        jsonGen.writeStartObject();
+                        jsonGen.writeStringField("HostIp", binding.getHostIp() == null ? "" : binding.getHostIp());
+                        jsonGen.writeStringField("HostPort", binding.getHostPort() == null ? "" : binding.getHostPort().toString());
+                        jsonGen.writeEndObject();
+                    }
+                    jsonGen.writeEndArray();
+                } else {
+                    jsonGen.writeNull();
                 }
-                jsonGen.writeEndArray();
             }
             jsonGen.writeEndObject();
         }


Test patch:
diff --git a/src/test/java/com/github/dockerjava/api/model/Ports_SerializingTest.java b/src/test/java/com/github/dockerjava/api/model/Ports_SerializingTest.java
index 25931069..9190eefd 100644
--- a/src/test/java/com/github/dockerjava/api/model/Ports_SerializingTest.java
+++ b/src/test/java/com/github/dockerjava/api/model/Ports_SerializingTest.java
@@ -13,6 +13,7 @@ public class Ports_SerializingTest {
 	private final ObjectMapper objectMapper = new ObjectMapper();
 	private final String jsonWithDoubleBindingForOnePort = 
 			"{\"80/tcp\":[{\"HostIp\":\"10.0.0.1\",\"HostPort\":\"80\"},{\"HostIp\":\"10.0.0.2\",\"HostPort\":\"80\"}]}";
+	private final String jsonWithNullBindingForOnePort = "{\"80/tcp\":null}";
 
 	@Test
 	public void deserializingPortWithMultipleBindings() throws Exception {
@@ -39,4 +40,20 @@ public class Ports_SerializingTest {
 		Ports ports = new Ports(ExposedPort.tcp(80), new Binding(null, null));
 		assertEquals(objectMapper.writeValueAsString(ports), "{\"80/tcp\":[{\"HostIp\":\"\",\"HostPort\":\"\"}]}");
 	}
+
+	@Test
+	public void deserializingPortWithNullBindings() throws Exception {
+		Ports ports = objectMapper.readValue(jsonWithNullBindingForOnePort, Ports.class);
+		Map<ExposedPort, Binding[]> map = ports.getBindings();
+		assertEquals(map.size(), 1);
+
+		assertEquals(map.get(ExposedPort.tcp(80)), null);
+	}
+
+	@Test
+	public void serializingWithNullBindings() throws Exception {
+		Ports ports = new Ports();
+		ports.bind(ExposedPort.tcp(80), null);
+		assertEquals(objectMapper.writeValueAsString(ports), jsonWithNullBindingForOnePort);
+	}
 }
diff --git a/src/test/java/com/github/dockerjava/api/model/Ports_addBindingsTest.java b/src/test/java/com/github/dockerjava/api/model/Ports_addBindingsTest.java
index 20e47df9..18c7f0f0 100644
--- a/src/test/java/com/github/dockerjava/api/model/Ports_addBindingsTest.java
+++ b/src/test/java/com/github/dockerjava/api/model/Ports_addBindingsTest.java
@@ -1,6 +1,7 @@
 package com.github.dockerjava.api.model;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
 
 import java.util.Map;
 
@@ -54,4 +55,12 @@ public class Ports_addBindingsTest {
 		assertEquals(bindings.get(TCP_80), new Binding[] { BINDING_8080, BINDING_9090 });
 	}
 
+	@Test
+	public void addNullBindings() {
+		ports.add(new PortBinding(null, TCP_80));
+		Map<ExposedPort, Binding[]> bindings = ports.getBindings();
+		// one key with two values
+		assertEquals(bindings.size(), 1);
+		assertEquals(bindings.get(TCP_80), null);
+	}
 }