From b58036aeea21497797b648506b75b25b187b35a2 Mon Sep 17 00:00:00 2001 From: Richard DiCroce Date: Fri, 9 Oct 2015 19:37:37 -0400 Subject: [PATCH] Improve flexibility of EmbeddedChannel ID Motivation: EmbeddedChannelId#hashCode() and equals() do not behave correctly if an instance is serialized and then deserialized. Additionally, EmbeddedChannel does not allow use of any other type of ChannelId, and EmbeddedChannelId is (mostly) a singleton instance. This creates a problem for unit tests that construct multiple EmbeddedChannels and expect each channel to have a unique ID. Modifications: EmbeddedChannelId is modified so equals() will return true and hashCode() will return the same value for any instance of the class. EmbeddedChannel is modified to allow a ChannelId to be specified when the channel is constructed. Tests added for both changes. Result: EmbeddedChannelId now behaves correctly when deserialized, and EmbeddedChannels can now have unique IDs. --- .../channel/embedded/EmbeddedChannel.java | 27 +++++++- .../channel/embedded/EmbeddedChannelId.java | 6 +- .../channel/embedded/CustomChannelId.java | 65 +++++++++++++++++++ .../embedded/EmbeddedChannelIdTest.java | 51 +++++++++++++++ .../channel/embedded/EmbeddedChannelTest.java | 8 +++ 5 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 transport/src/test/java/io/netty/channel/embedded/CustomChannelId.java create mode 100644 transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelIdTest.java diff --git a/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java b/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java index 0fb45507a7..d115e78e30 100644 --- a/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java +++ b/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannel.java @@ -21,6 +21,7 @@ import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelMetadata; @@ -64,19 +65,39 @@ public class EmbeddedChannel extends AbstractChannel { private State state; /** - * Create a new instance with an empty pipeline. + * Create a new instance with an {@link EmbeddedChannelId} and an empty pipeline. */ public EmbeddedChannel() { this(EMPTY_HANDLERS); } + /** + * Create a new instance with the specified ID and an empty pipeline. + * + * @param channelId the {@link ChannelId} that will be used to identify this channel + */ + public EmbeddedChannel(ChannelId channelId) { + this(channelId, EMPTY_HANDLERS); + } + /** * Create a new instance with the pipeline initialized with the specified handlers. * - * @param handlers the @link ChannelHandler}s which will be add in the {@link ChannelPipeline} + * @param handlers the {@link ChannelHandler}s which will be add in the {@link ChannelPipeline} */ public EmbeddedChannel(final ChannelHandler... handlers) { - super(null, EmbeddedChannelId.INSTANCE); + this(EmbeddedChannelId.INSTANCE, handlers); + } + + /** + * Create a new instance with the channel ID set to the given ID and the pipeline + * initialized with the specified handlers. + * + * @param channelId the {@link ChannelId} that will be used to identify this channel + * @param handlers the {@link ChannelHandler}s which will be add in the {@link ChannelPipeline} + */ + public EmbeddedChannel(ChannelId channelId, final ChannelHandler... handlers) { + super(null, channelId); if (handlers == null) { throw new NullPointerException("handlers"); diff --git a/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannelId.java b/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannelId.java index 01675c2f68..0bf0b25d45 100644 --- a/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannelId.java +++ b/transport/src/main/java/io/netty/channel/embedded/EmbeddedChannelId.java @@ -41,7 +41,7 @@ final class EmbeddedChannelId implements ChannelId { @Override public int compareTo(ChannelId o) { - if (o == INSTANCE) { + if (o instanceof EmbeddedChannelId) { return 0; } @@ -50,12 +50,12 @@ final class EmbeddedChannelId implements ChannelId { @Override public int hashCode() { - return super.hashCode(); + return 0; } @Override public boolean equals(Object obj) { - return super.equals(obj); + return obj instanceof EmbeddedChannelId; } @Override diff --git a/transport/src/test/java/io/netty/channel/embedded/CustomChannelId.java b/transport/src/test/java/io/netty/channel/embedded/CustomChannelId.java new file mode 100644 index 0000000000..397df1a76e --- /dev/null +++ b/transport/src/test/java/io/netty/channel/embedded/CustomChannelId.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015 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.channel.embedded; + +import io.netty.channel.ChannelId; + +public class CustomChannelId implements ChannelId { + private static final long serialVersionUID = 1L; + + private int id; + + public CustomChannelId(int id) { + this.id = id; + } + + @Override + public int compareTo(ChannelId o) { + if (o instanceof CustomChannelId) { + return id - ((CustomChannelId) o).id; + } + return -1; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CustomChannelId) { + return id == ((CustomChannelId) obj).id; + } + return false; + } + + @Override + public String toString() { + return "CustomChannelId " + id; + } + + @Override + public String asShortText() { + return toString(); + } + + @Override + public String asLongText() { + return toString(); + } + +} diff --git a/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelIdTest.java b/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelIdTest.java new file mode 100644 index 0000000000..3755507c95 --- /dev/null +++ b/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelIdTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 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.channel.embedded; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.junit.Assert; +import org.junit.Test; + +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelId; + +public class EmbeddedChannelIdTest { + + @Test + public void testSerialization() throws IOException, ClassNotFoundException { + // test that a deserialized instance works the same as a normal instance (issue #2869) + ChannelId normalInstance = EmbeddedChannelId.INSTANCE; + + ByteBufOutputStream buffer = new ByteBufOutputStream(Unpooled.buffer()); + ObjectOutputStream outStream = new ObjectOutputStream(buffer); + outStream.writeObject(normalInstance); + outStream.close(); + + ObjectInputStream inStream = new ObjectInputStream(new ByteBufInputStream(buffer.buffer())); + ChannelId deserializedInstance = (ChannelId) inStream.readObject(); + inStream.close(); + + Assert.assertEquals(normalInstance, deserializedInstance); + Assert.assertEquals(normalInstance.hashCode(), deserializedInstance.hashCode()); + Assert.assertEquals(0, normalInstance.compareTo(deserializedInstance)); + } + +} diff --git a/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java b/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java index 12abe5fae0..9a919534b4 100644 --- a/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java +++ b/transport/src/test/java/io/netty/channel/embedded/EmbeddedChannelTest.java @@ -20,6 +20,7 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; @@ -136,6 +137,13 @@ public class EmbeddedChannelTest { Assert.assertNull(channel.readOutbound()); } + @Test + public void testConstructWithChannelId() { + ChannelId channelId = new CustomChannelId(1); + EmbeddedChannel channel = new EmbeddedChannel(channelId); + Assert.assertSame(channelId, channel.id()); + } + // See https://github.com/netty/netty/issues/4316. @Test(timeout = 2000) public void testFireChannelInactiveAndUnregisteredOnClose() throws InterruptedException {