diff --git a/src/main/java/org/jboss/netty/handler/codec/replay/ReplayingDecoder.java b/src/main/java/org/jboss/netty/handler/codec/replay/ReplayingDecoder.java
index ddbdddcc3d..0f95965d0c 100644
--- a/src/main/java/org/jboss/netty/handler/codec/replay/ReplayingDecoder.java
+++ b/src/main/java/org/jboss/netty/handler/codec/replay/ReplayingDecoder.java
@@ -35,13 +35,151 @@ import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
/**
+ * A specialized variation of {@link FrameDecoder} which enables implementation
+ * of a non-blocking decoder in the blocking I/O paradigm.
+ *
+ * The biggest difference between {@link ReplayingDecoder} and
+ * {@link FrameDecoder} is that {@link ReplayingDecoder} allows you to
+ * implement the {@code decode()} and {@code decodeLast()} methods just like
+ * all required bytes were received already, rather than checking the
+ * availability of the required bytes. For example, the following
+ * {@link FrameDecoder} implementation:
+ *
+ * public class IntegerHeaderFrameDecoder extends FrameDecoder {
+ *
+ * protected Object decode(ChannelHandlerContext ctx,
+ * Channel channel,
+ * ChannelBuffer buf) throws Exception {
+ *
+ * if (buf.readableBytes() < 4) {
+ * return null;
+ * }
+ *
+ * buf.markReaderIndex();
+ * int length = buf.readInt();
+ *
+ * if (buf.readableBytes() < length) {
+ * buf.resetReaderIndex();
+ * return null;
+ * }
+ *
+ * return buf.readBytes(length);
+ * }
+ * }
+ *
+ * is simplified like the following with {@link ReplayingDecoder}:
+ *
+ * public class IntegerHeaderFrameDecoder
+ * extends ReplayingDecoder<VoidEnum> {
+ *
+ * protected Object decode(ChannelHandlerContext ctx,
+ * Channel channel,
+ * ChannelBuffer buf,
+ * VoidEnum state) throws Exception {
+ *
+ * return buf.readBytes(buf.readInt());
+ * }
+ * }
+ *
+ *
+ * Limitations
+ *
+ * At the cost of the simplicity, {@link ReplayingDecoder} enforces you a few
+ * limitations:
+ *
+ * - Some buffer operations are prohibited.
+ * - Performance can be worse if the network is slow and the message
+ * format is complicated unlike the example above.
+ *
+ *
+ * Improving the performance
+ *
+ * Fortunately, the performance of a {@link ReplayingDecoder} implementation
+ * can be improved significantly by using the {@code checkpoint()} method.
+ *
+ *
Calling {@code checkpoint(T)} with an {@link Enum}
+ *
+ * The easiest way is to create an {@link Enum} type which represents the
+ * current state of the decoder and to call {@code checkpoint(T)} method
+ * whenever the state changes. You can have as many states as you want
+ * depending on the complexity of the message:
+ *
+ *
+ * public enum MyDecoderState {
+ * READ_LENGTH,
+ * READ_CONTENT;
+ * }
+ *
+ * public class IntegerHeaderFrameDecoder
+ * extends ReplayingDecoder<MyDecoderState> {
+ *
+ * private int length;
+ *
+ * public IntegerHeaderFrameDecoder() {
+ * // Set the initial state.
+ * super(MyDecoderState.READ_LENGTH);
+ * }
+ *
+ * protected Object decode(ChannelHandlerContext ctx,
+ * Channel channel,
+ * ChannelBuffer buf,
+ * MyDecoderState state) throws Exception {
+ * switch (state) {
+ * case READ_LENGTH:
+ * length = buf.readInt();
+ * checkpoint(MyDecoderState.READ_CONTENT);
+ * case READ_CONTENT:
+ * ChannelBuffer frame = buf.readBytes(length);
+ * checkpoint(MyDecoderState.READ_LENGTH);
+ * return frame;
+ * default:
+ * throw new Error("Shouldn't reach here.");
+ * }
+ * }
+ * }
+ *
+ *
+ * Calling {@code checkpoint()} with no parameter
+ *
+ * An alternative way to manage the decoder state is to manage it by yourself.
+ *
+ * public class IntegerHeaderFrameDecoder
+ * extends ReplayingDecoder<VoidEnum> {
+ *
+ * private boolean readLength;
+ * private int length;
+ *
+ * protected Object decode(ChannelHandlerContext ctx,
+ * Channel channel,
+ * ChannelBuffer buf,
+ * MyDecoderState state) throws Exception {
+ * if (!readLength) {
+ * length = buf.readInt();
+ * readLength = true;
+ * checkpoint();
+ * }
+ *
+ * if (readLength) {
+ * ChannelBuffer frame = buf.readBytes(length);
+ * readLength = false;
+ * checkpoint();
+ * return frame;
+ * }
+ * }
+ * }
+ *
+ *
+ *
* @author The Netty Project (netty-dev@lists.jboss.org)
* @author Trustin Lee (tlee@redhat.com)
*
* @version $Rev$, $Date$
*
+ * @param
+ * the state type; use {@link VoidEnum} if state management is unused
*/
@ChannelPipelineCoverage("one")
public abstract class ReplayingDecoder> extends SimpleChannelHandler {
@@ -51,14 +189,65 @@ public abstract class ReplayingDecoder> extends SimpleChannelH
private volatile T state;
private volatile int checkpoint;
+ /**
+ * Creates a new instance with no initial state (i.e. {@code null}).
+ */
protected ReplayingDecoder() {
this(null);
}
+ /**
+ * Creates a new instance with the specified initial state.
+ */
protected ReplayingDecoder(T initialState) {
this.state = initialState;
}
+ /**
+ * Stores the internal cumulative buffer's reader position.
+ */
+ protected void checkpoint() {
+ checkpoint = cumulation.readerIndex();
+ }
+
+ /**
+ * Stores the internal cumulative buffer's reader position and updates
+ * the current decoder state.
+ */
+ protected void checkpoint(T state) {
+ this.state = state;
+ checkpoint = cumulation.readerIndex();
+ }
+
+ /**
+ * Decodes the received packets so far into a frame.
+ *
+ * @param ctx the context of this handler
+ * @param channel the current channel
+ * @param buffer the cumulative buffer of received packets so far\
+ * @param state the current decoder state ({@code null} if unused)
+ *
+ * @return the decoded frame
+ */
+ protected abstract Object decode(ChannelHandlerContext ctx,
+ Channel channel, ChannelBuffer buffer, T state) throws Exception;
+
+ /**
+ * Decodes the received data so far into a frame when the channel is
+ * disconnected.
+ *
+ * @param ctx the context of this handler
+ * @param channel the current channel
+ * @param buffer the cumulative buffer of received packets so far
+ * @param state the current decoder state ({@code null} if unused)
+ *
+ * @return the decoded frame
+ */
+ protected Object decodeLast(
+ ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, T state) throws Exception {
+ return decode(ctx, channel, buffer, state);
+ }
+
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
@@ -155,21 +344,4 @@ public abstract class ReplayingDecoder> extends SimpleChannelH
ctx.sendUpstream(e);
}
}
-
- protected void checkpoint() {
- checkpoint = cumulation.readerIndex();
- }
-
- protected void checkpoint(T state) {
- this.state = state;
- checkpoint = cumulation.readerIndex();
- }
-
- protected abstract Object decode(ChannelHandlerContext ctx,
- Channel channel, ChannelBuffer buffer, T state) throws Exception;
-
- protected Object decodeLast(
- ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, T state) throws Exception {
- return decode(ctx, channel, buffer, state);
- }
}
diff --git a/src/main/java/org/jboss/netty/handler/codec/replay/VoidEnum.java b/src/main/java/org/jboss/netty/handler/codec/replay/VoidEnum.java
index 83b490cdc6..05ba2c4664 100644
--- a/src/main/java/org/jboss/netty/handler/codec/replay/VoidEnum.java
+++ b/src/main/java/org/jboss/netty/handler/codec/replay/VoidEnum.java
@@ -23,6 +23,10 @@
package org.jboss.netty.handler.codec.replay;
/**
+ * A placeholder {@link Enum} which could be specified as a type parameter of
+ * {@link ReplayingDecoder} when a user wants to manage the decoder state by
+ * oneself or there's no state to manage.
+ *
* @author The Netty Project (netty-dev@lists.jboss.org)
* @author Trustin Lee (tlee@redhat.com)
*
diff --git a/src/main/java/org/jboss/netty/handler/codec/replay/package-info.java b/src/main/java/org/jboss/netty/handler/codec/replay/package-info.java
index 0b010915ae..2ac7ddeb1d 100644
--- a/src/main/java/org/jboss/netty/handler/codec/replay/package-info.java
+++ b/src/main/java/org/jboss/netty/handler/codec/replay/package-info.java
@@ -22,7 +22,9 @@
*/
/**
- * Specialized decoder which enables implementation of a non-blocking decoder
- * with blocking I/O paradigm.
+ * Specialized variation of {@link org.jboss.netty.handler.codec.frame.FrameDecoder}
+ * which enables implementation of a non-blocking decoder in the blocking I/O
+ * paradigm.
*/
-package org.jboss.netty.handler.codec.replay;
\ No newline at end of file
+package org.jboss.netty.handler.codec.replay;
+