Repository: netty/netty
Issue: Expose Bootstrap getter methods and add some additional ones
Labels: improvement
feature

The `Bootstrap` class (applies also to `AbstractBootstrap` and `ServerBootstrap`) has a few package private getter methods and some things such as `#attr()` and `#options()` aren't exposed at all. I'd argue there should be public getter methods for all of them.

Background: Our application is written in Java but we use Clojure as a configuration language (instead of XML, JSON, YAML) to skip the object mapping indirection. 

This creates a slight problem for partial configurations. Let's take the following example:

``` clojure
(def myClientBootstrap (-> (Bootstrap.)
  (.option ChannelOption/SO_REUSEADDR true)
  (.remoteAddress "host" 8080)))
```

``` java
Bootstrap myClientBootstrap = getClientBootstrapFromClojureLand();
```

There is no way for me to test if a `ChannelFactory` is configured and fill the blanks if it isn't for example (NIO on non-Linux and EPOLL on Linux). Ditto, I can't test if a remote address isn't configured and blow up. 

We're currently using a wrapper objects around `Bootstrap` and `ServerBootstrap` that provides getter methods but that seems unnecessary and adds only indirection.


Issue comments:
the underlying storage is currently exposed by `options` and `attr` and there is synchronization that must be applied when dealing with these collections. I'm guessing they are not exposed so the storage can be directly exposed (no copy etc...) and its assumed users of the interface "play nice" with synchronization and modifiability. We should consider these factors before making changes to these interfaces.

Yeah I think the synchronisation is an issue. That said maybe we could just expose a copy of the attributes / options. Let me cook up something...

Fixed by https://github.com/netty/netty/pull/5243


Pull request review comments:
![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

`toString()` please

`AbstractBootstrapConfig` and make it top-level?

Could we move this class top-level?

Could we move this to top level?

`toString()`

- u -> U
- `{@link #config()}.` -> `{@link #config()} instead.`

- u -> U
- . -> instead.

Global comment: Return's'

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)

![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) Remove usage of generic wildcard type. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1452)


Patch that resolved the issue:
diff --git a/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java
index a10c0d0fc94..7559dd3ba59 100644
--- a/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java
+++ b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java
@@ -35,6 +35,7 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -313,7 +314,7 @@ public void operationComplete(ChannelFuture future) throws Exception {
     }
 
     final ChannelFuture initAndRegister() {
-        final Channel channel = channelFactory().newChannel();
+        final Channel channel = channelFactory.newChannel();
         try {
             init(channel);
         } catch (Throwable t) {
@@ -375,6 +376,41 @@ public B handler(ChannelHandler handler) {
         return (B) this;
     }
 
+    /**
+     * Returns the configured {@link EventLoopGroup} or {@code null} if non is configured yet.
+     *
+     * @deprecated Use {@link #config()} instead.
+     */
+    @Deprecated
+    public final EventLoopGroup group() {
+        return group;
+    }
+
+    /**
+     * Returns the {@link AbstractBootstrapConfig} object that can be used to obtain the current config
+     * of the bootstrap.
+     */
+    public abstract AbstractBootstrapConfig<B, C> config();
+
+    static <K, V> Map<K, V> copiedMap(Map<K, V> map) {
+        final Map<K, V> copied;
+        synchronized (map) {
+            if (map.isEmpty()) {
+                return Collections.emptyMap();
+            }
+            copied = new LinkedHashMap<K, V>(map);
+        }
+        return Collections.unmodifiableMap(copied);
+    }
+
+    final Map<ChannelOption<?>, Object> options0() {
+        return options;
+    }
+
+    final Map<AttributeKey<?>, Object> attrs0() {
+        return attrs;
+    }
+
     final SocketAddress localAddress() {
         return localAddress;
     }
@@ -388,66 +424,19 @@ final ChannelHandler handler() {
         return handler;
     }
 
-    /**
-     * Return the configured {@link EventLoopGroup} or {@code null} if non is configured yet.
-     */
-    public EventLoopGroup group() {
-        return group;
-    }
-
     final Map<ChannelOption<?>, Object> options() {
-        return options;
+        return copiedMap(options);
     }
 
     final Map<AttributeKey<?>, Object> attrs() {
-        return attrs;
+        return copiedMap(attrs);
     }
 
     @Override
     public String toString() {
         StringBuilder buf = new StringBuilder()
             .append(StringUtil.simpleClassName(this))
-            .append('(');
-        if (group != null) {
-            buf.append("group: ")
-               .append(StringUtil.simpleClassName(group))
-               .append(", ");
-        }
-        if (channelFactory != null) {
-            buf.append("channelFactory: ")
-               .append(channelFactory)
-               .append(", ");
-        }
-        if (localAddress != null) {
-            buf.append("localAddress: ")
-               .append(localAddress)
-               .append(", ");
-        }
-        synchronized (options) {
-            if (!options.isEmpty()) {
-                buf.append("options: ")
-                   .append(options)
-                   .append(", ");
-            }
-        }
-        synchronized (attrs) {
-            if (!attrs.isEmpty()) {
-                buf.append("attrs: ")
-                   .append(attrs)
-                   .append(", ");
-            }
-        }
-        if (handler != null) {
-            buf.append("handler: ")
-               .append(handler)
-               .append(", ");
-        }
-        if (buf.charAt(buf.length() - 1) == '(') {
-            buf.append(')');
-        } else {
-            buf.setCharAt(buf.length() - 2, ')');
-            buf.setLength(buf.length() - 1);
-        }
+            .append('(').append(config()).append(')');
         return buf.toString();
     }
 
diff --git a/transport/src/main/java/io/netty/bootstrap/AbstractBootstrapConfig.java b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrapConfig.java
new file mode 100644
index 00000000000..976ec94640b
--- /dev/null
+++ b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrapConfig.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2016 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 io.netty.bootstrap;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.util.AttributeKey;
+import io.netty.util.internal.ObjectUtil;
+import io.netty.util.internal.StringUtil;
+
+import java.net.SocketAddress;
+import java.util.Map;
+
+/**
+ * Exposes the configuration of an {@link AbstractBootstrap}.
+ */
+public abstract class AbstractBootstrapConfig<B extends AbstractBootstrap<B, C>, C extends Channel> {
+
+    protected final B bootstrap;
+
+    protected AbstractBootstrapConfig(B bootstrap) {
+        this.bootstrap = ObjectUtil.checkNotNull(bootstrap, "bootstrap");
+    }
+
+    /**
+     * Returns the configured local address or {@code null} if non is configured yet.
+     */
+    public final SocketAddress localAddress() {
+        return bootstrap.localAddress();
+    }
+
+    /**
+     * Returns the configured {@link ChannelFactory} or {@code null} if non is configured yet.
+     */
+    @SuppressWarnings("deprecation")
+    public final ChannelFactory<? extends C> channelFactory() {
+        return bootstrap.channelFactory();
+    }
+
+    /**
+     * Returns the configured {@link ChannelHandler} or {@code null} if non is configured yet.
+     */
+    public final ChannelHandler handler() {
+        return bootstrap.handler();
+    }
+
+    /**
+     * Returns a copy of the configured options.
+     */
+    public final Map<ChannelOption<?>, Object> options() {
+        return bootstrap.options();
+    }
+
+    /**
+     * Returns a copy of the configured attributes.
+     */
+    public final Map<AttributeKey<?>, Object> attrs() {
+        return bootstrap.attrs();
+    }
+
+    /**
+     * Returns the configured {@link EventLoopGroup} or {@code null} if non is configured yet.
+     */
+    @SuppressWarnings("deprecation")
+    public final EventLoopGroup group() {
+        return bootstrap.group();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder()
+                .append(StringUtil.simpleClassName(this))
+                .append('(');
+        EventLoopGroup group = group();
+        if (group != null) {
+            buf.append("group: ")
+                    .append(StringUtil.simpleClassName(group))
+                    .append(", ");
+        }
+        @SuppressWarnings("deprecation")
+        ChannelFactory<? extends C> factory = channelFactory();
+        if (factory != null) {
+            buf.append("channelFactory: ")
+                    .append(factory)
+                    .append(", ");
+        }
+        SocketAddress localAddress = localAddress();
+        if (localAddress != null) {
+            buf.append("localAddress: ")
+                    .append(localAddress)
+                    .append(", ");
+        }
+
+        Map<ChannelOption<?>, Object> options = options();
+        if (!options.isEmpty()) {
+            buf.append("options: ")
+                    .append(options)
+                    .append(", ");
+        }
+        Map<AttributeKey<?>, Object> attrs = attrs();
+        if (!attrs.isEmpty()) {
+            buf.append("attrs: ")
+                    .append(attrs)
+                    .append(", ");
+        }
+        ChannelHandler handler = handler();
+        if (handler != null) {
+            buf.append("handler: ")
+                    .append(handler)
+                    .append(", ");
+        }
+        if (buf.charAt(buf.length() - 1) == '(') {
+            buf.append(')');
+        } else {
+            buf.setCharAt(buf.length() - 2, ')');
+            buf.setLength(buf.length() - 1);
+        }
+        return buf.toString();
+    }
+}
diff --git a/transport/src/main/java/io/netty/bootstrap/Bootstrap.java b/transport/src/main/java/io/netty/bootstrap/Bootstrap.java
index 408214393d1..7aa5d111ea5 100644
--- a/transport/src/main/java/io/netty/bootstrap/Bootstrap.java
+++ b/transport/src/main/java/io/netty/bootstrap/Bootstrap.java
@@ -53,6 +53,8 @@ public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
 
     private static final AddressResolverGroup<?> DEFAULT_RESOLVER = DefaultAddressResolverGroup.INSTANCE;
 
+    private final BootstrapConfig config = new BootstrapConfig(this);
+
     @SuppressWarnings("unchecked")
     private volatile AddressResolverGroup<SocketAddress> resolver =
             (AddressResolverGroup<SocketAddress>) DEFAULT_RESOLVER;
@@ -113,7 +115,7 @@ public ChannelFuture connect() {
             throw new IllegalStateException("remoteAddress not set");
         }
 
-        return doResolveAndConnect(remoteAddress, localAddress());
+        return doResolveAndConnect(remoteAddress, config.localAddress());
     }
 
     /**
@@ -139,7 +141,7 @@ public ChannelFuture connect(SocketAddress remoteAddress) {
         }
 
         validate();
-        return doResolveAndConnect(remoteAddress, localAddress());
+        return doResolveAndConnect(remoteAddress, config.localAddress());
     }
 
     /**
@@ -248,9 +250,9 @@ public void run() {
     @SuppressWarnings("unchecked")
     void init(Channel channel) throws Exception {
         ChannelPipeline p = channel.pipeline();
-        p.addLast(handler());
+        p.addLast(config.handler());
 
-        final Map<ChannelOption<?>, Object> options = options();
+        final Map<ChannelOption<?>, Object> options = options0();
         synchronized (options) {
             for (Entry<ChannelOption<?>, Object> e: options.entrySet()) {
                 try {
@@ -263,7 +265,7 @@ void init(Channel channel) throws Exception {
             }
         }
 
-        final Map<AttributeKey<?>, Object> attrs = attrs();
+        final Map<AttributeKey<?>, Object> attrs = attrs0();
         synchronized (attrs) {
             for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                 channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
@@ -274,7 +276,7 @@ void init(Channel channel) throws Exception {
     @Override
     public Bootstrap validate() {
         super.validate();
-        if (handler() == null) {
+        if (config.handler() == null) {
             throw new IllegalStateException("handler not set");
         }
         return this;
@@ -298,17 +300,15 @@ public Bootstrap clone(EventLoopGroup group) {
     }
 
     @Override
-    public String toString() {
-        if (remoteAddress == null) {
-            return super.toString();
-        }
+    public final BootstrapConfig config() {
+        return config;
+    }
 
-        StringBuilder buf = new StringBuilder(super.toString());
-        buf.setLength(buf.length() - 1);
+    final SocketAddress remoteAddress() {
+        return remoteAddress;
+    }
 
-        return buf.append(", remoteAddress: ")
-                  .append(remoteAddress)
-                  .append(')')
-                  .toString();
+    final AddressResolverGroup<?> resolver() {
+        return resolver;
     }
 }
diff --git a/transport/src/main/java/io/netty/bootstrap/BootstrapConfig.java b/transport/src/main/java/io/netty/bootstrap/BootstrapConfig.java
new file mode 100644
index 00000000000..24d9fa45e18
--- /dev/null
+++ b/transport/src/main/java/io/netty/bootstrap/BootstrapConfig.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 io.netty.bootstrap;
+
+import io.netty.channel.Channel;
+import io.netty.resolver.AddressResolverGroup;
+
+import java.net.SocketAddress;
+
+/**
+ * Exposes the configuration of a {@link Bootstrap}.
+ */
+public final class BootstrapConfig extends AbstractBootstrapConfig<Bootstrap, Channel> {
+
+    BootstrapConfig(Bootstrap bootstrap) {
+        super(bootstrap);
+    }
+
+    /**
+     * Returns the configured remote address or {@code null} if non is configured yet.
+     */
+    public SocketAddress remoteAddress() {
+        return bootstrap.remoteAddress();
+    }
+
+    /**
+     * Returns the configured {@link AddressResolverGroup} or the default if non is configured yet.
+     */
+    public AddressResolverGroup<?> resolver() {
+        return bootstrap.resolver();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder(super.toString());
+        buf.setLength(buf.length() - 1);
+        buf.append(", resolver: ").append(resolver());
+        SocketAddress remoteAddress = remoteAddress();
+        if (remoteAddress != null) {
+            buf.append(", remoteAddress: ")
+                    .append(remoteAddress);
+        }
+        return buf.append(')').toString();
+    }
+}
diff --git a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java
index 38929b1b71d..822a1cd133d 100644
--- a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java
+++ b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java
@@ -29,7 +29,6 @@
 import io.netty.channel.ServerChannel;
 import io.netty.util.AttributeKey;
 import io.netty.util.internal.OneTimeTask;
-import io.netty.util.internal.StringUtil;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
 
@@ -48,6 +47,7 @@ public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerCh
 
     private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
     private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
+    private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
     private volatile EventLoopGroup childGroup;
     private volatile ChannelHandler childHandler;
 
@@ -138,22 +138,14 @@ public ServerBootstrap childHandler(ChannelHandler childHandler) {
         return this;
     }
 
-    /**
-     * Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
-     * if non is configured yet.
-     */
-    public EventLoopGroup childGroup() {
-        return childGroup;
-    }
-
     @Override
     void init(Channel channel) throws Exception {
-        final Map<ChannelOption<?>, Object> options = options();
+        final Map<ChannelOption<?>, Object> options = options0();
         synchronized (options) {
             channel.config().setOptions(options);
         }
 
-        final Map<AttributeKey<?>, Object> attrs = attrs();
+        final Map<AttributeKey<?>, Object> attrs = attrs0();
         synchronized (attrs) {
             for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                 @SuppressWarnings("unchecked")
@@ -179,7 +171,7 @@ void init(Channel channel) throws Exception {
             @Override
             public void initChannel(Channel ch) throws Exception {
                 ChannelPipeline pipeline = ch.pipeline();
-                ChannelHandler handler = handler();
+                ChannelHandler handler = config.handler();
                 if (handler != null) {
                     pipeline.addLast(handler);
                 }
@@ -294,42 +286,31 @@ public ServerBootstrap clone() {
         return new ServerBootstrap(this);
     }
 
-    @Override
-    public String toString() {
-        StringBuilder buf = new StringBuilder(super.toString());
-        buf.setLength(buf.length() - 1);
-        buf.append(", ");
-        if (childGroup != null) {
-            buf.append("childGroup: ");
-            buf.append(StringUtil.simpleClassName(childGroup));
-            buf.append(", ");
-        }
-        synchronized (childOptions) {
-            if (!childOptions.isEmpty()) {
-                buf.append("childOptions: ");
-                buf.append(childOptions);
-                buf.append(", ");
-            }
-        }
-        synchronized (childAttrs) {
-            if (!childAttrs.isEmpty()) {
-                buf.append("childAttrs: ");
-                buf.append(childAttrs);
-                buf.append(", ");
-            }
-        }
-        if (childHandler != null) {
-            buf.append("childHandler: ");
-            buf.append(childHandler);
-            buf.append(", ");
-        }
-        if (buf.charAt(buf.length() - 1) == '(') {
-            buf.append(')');
-        } else {
-            buf.setCharAt(buf.length() - 2, ')');
-            buf.setLength(buf.length() - 1);
-        }
+    /**
+     * Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
+     * if non is configured yet.
+     *
+     * @deprecated Use {@link #config()} instead.
+     */
+    @Deprecated
+    public EventLoopGroup childGroup() {
+        return childGroup;
+    }
+
+    final ChannelHandler childHandler() {
+        return childHandler;
+    }
+
+    final Map<ChannelOption<?>, Object> childOptions() {
+        return copiedMap(childOptions);
+    }
+
+    final Map<AttributeKey<?>, Object> childAttrs() {
+        return copiedMap(childAttrs);
+    }
 
-        return buf.toString();
+    @Override
+    public final ServerBootstrapConfig config() {
+        return config;
     }
 }
diff --git a/transport/src/main/java/io/netty/bootstrap/ServerBootstrapConfig.java b/transport/src/main/java/io/netty/bootstrap/ServerBootstrapConfig.java
new file mode 100644
index 00000000000..1401d59eb33
--- /dev/null
+++ b/transport/src/main/java/io/netty/bootstrap/ServerBootstrapConfig.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2016 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 io.netty.bootstrap;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.util.AttributeKey;
+import io.netty.util.internal.StringUtil;
+
+import java.util.Map;
+
+/**
+ * Exposes the configuration of a {@link ServerBootstrapConfig}.
+ */
+public final class ServerBootstrapConfig extends AbstractBootstrapConfig<ServerBootstrap, ServerChannel> {
+
+    ServerBootstrapConfig(ServerBootstrap bootstrap) {
+        super(bootstrap);
+    }
+
+    /**
+     * Returns the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
+     * if non is configured yet.
+     */
+    @SuppressWarnings("deprecation")
+    public EventLoopGroup childGroup() {
+        return bootstrap.childGroup();
+    }
+
+    /**
+     * Returns the configured {@link ChannelHandler} be used for the child channels or {@code null}
+     * if non is configured yet.
+     */
+    public ChannelHandler childHandler() {
+        return bootstrap.childHandler();
+    }
+
+    /**
+     * Returns a copy of the configured options which will be used for the child channels.
+     */
+    public Map<ChannelOption<?>, Object> childOptions() {
+        return bootstrap.childOptions();
+    }
+
+    /**
+     * Returns a copy of the configured attributes which will be used for the child channels.
+     */
+    public Map<AttributeKey<?>, Object> childAttrs() {
+        return bootstrap.childAttrs();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder(super.toString());
+        buf.setLength(buf.length() - 1);
+        buf.append(", ");
+        EventLoopGroup childGroup = childGroup();
+        if (childGroup != null) {
+            buf.append("childGroup: ");
+            buf.append(StringUtil.simpleClassName(childGroup));
+            buf.append(", ");
+        }
+        Map<ChannelOption<?>, Object> childOptions = childOptions();
+        if (!childOptions.isEmpty()) {
+            buf.append("childOptions: ");
+            buf.append(childOptions);
+            buf.append(", ");
+        }
+        Map<AttributeKey<?>, Object> childAttrs = childAttrs();
+        if (!childAttrs.isEmpty()) {
+            buf.append("childAttrs: ");
+            buf.append(childAttrs);
+            buf.append(", ");
+        }
+        ChannelHandler childHandler = childHandler();
+        if (childHandler != null) {
+            buf.append("childHandler: ");
+            buf.append(childHandler);
+            buf.append(", ");
+        }
+        if (buf.charAt(buf.length() - 1) == '(') {
+            buf.append(')');
+        } else {
+            buf.setCharAt(buf.length() - 2, ')');
+            buf.setLength(buf.length() - 1);
+        }
+
+        return buf.toString();
+    }
+}


Test patch: