View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.handler.codec;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.buffer.api.BufferAllocator;
20  import io.netty5.channel.ChannelHandlerAdapter;
21  import io.netty5.channel.ChannelHandlerContext;
22  import io.netty5.util.concurrent.Future;
23  import io.netty5.util.internal.TypeParameterMatcher;
24  
25  /**
26   * A Codec for on-the-fly encoding/decoding of bytes to messages and vise-versa.
27   *
28   * This can be thought of as a combination of {@link ByteToMessageDecoder} and {@link MessageToByteEncoder}.
29   *
30   * Be aware that sub-classes of {@link ByteToMessageCodec} <strong>MUST NOT</strong>
31   * annotated with {@link @Sharable}.
32   */
33  public abstract class ByteToMessageCodec<I> extends ChannelHandlerAdapter {
34  
35      private final TypeParameterMatcher outboundMsgMatcher;
36      private final MessageToByteEncoder<I> encoder;
37  
38      private final ByteToMessageDecoder decoder = new ByteToMessageDecoder() {
39          @Override
40          public void decode(ChannelHandlerContext ctx, Buffer in) throws Exception {
41              ByteToMessageCodec.this.decode(ctx, in);
42          }
43  
44          @Override
45          protected void decodeLast(ChannelHandlerContext ctx, Buffer in) throws Exception {
46              ByteToMessageCodec.this.decodeLast(ctx, in);
47          }
48      };
49  
50      /**
51       * see {@link #ByteToMessageCodec(BufferAllocator)} with {@code true} as boolean parameter.
52       */
53      protected ByteToMessageCodec() {
54          this((BufferAllocator) null);
55      }
56  
57      /**
58       * see {@link #ByteToMessageCodec(Class, BufferAllocator)} with {@code true} as boolean value.
59       */
60      protected ByteToMessageCodec(Class<? extends I> outboundMessageType) {
61          this(outboundMessageType, null);
62      }
63  
64      /**
65       * Create a new instance which will try to detect the types to match out of the type parameter of the class.
66       *
67       * @param allocator             The allocator to use for allocating buffers.
68       *                              If {@code null}, the channel context allocator will be used.
69       */
70      protected ByteToMessageCodec(BufferAllocator allocator) {
71          outboundMsgMatcher = TypeParameterMatcher.find(this, ByteToMessageCodec.class, "I");
72          encoder = new Encoder(allocator);
73      }
74  
75      /**
76       * Create a new instance
77       *
78       * @param outboundMessageType   The type of messages to match.
79       * @param allocator             The allocator to use for allocating buffers.
80       *                              If {@code null}, the channel context allocator will be used.
81       */
82      protected ByteToMessageCodec(Class<? extends I> outboundMessageType, BufferAllocator allocator) {
83          outboundMsgMatcher = TypeParameterMatcher.get(outboundMessageType);
84          encoder = new Encoder(allocator);
85      }
86  
87      @Override
88      public final boolean isSharable() {
89          // Can't be sharable as we keep state.
90          return false;
91      }
92  
93      /**
94       * Returns {@code true} if and only if the specified message can be encoded by this codec.
95       *
96       * @param msg the message
97       */
98      public boolean acceptOutboundMessage(Object msg) throws Exception {
99          return outboundMsgMatcher.match(msg);
100     }
101 
102     @Override
103     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
104         decoder.channelRead(ctx, msg);
105     }
106 
107     @Override
108     public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
109         return encoder.write(ctx, msg);
110     }
111 
112     @Override
113     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
114         decoder.channelReadComplete(ctx);
115     }
116 
117     @Override
118     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
119         decoder.channelInactive(ctx);
120     }
121 
122     @Override
123     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
124         try {
125             decoder.handlerAdded(ctx);
126         } finally {
127             encoder.handlerAdded(ctx);
128         }
129     }
130 
131     @Override
132     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
133         try {
134             decoder.handlerRemoved(ctx);
135         } finally {
136             encoder.handlerRemoved(ctx);
137         }
138     }
139 
140     /**
141      * @see MessageToByteEncoder#encode(ChannelHandlerContext, Object, Buffer)
142      */
143     protected abstract void encode(ChannelHandlerContext ctx, I msg, Buffer out) throws Exception;
144 
145     /**
146      * @see ByteToMessageDecoder#decode(ChannelHandlerContext, Buffer)
147      */
148     protected abstract void decode(ChannelHandlerContext ctx, Buffer in) throws Exception;
149 
150     /**
151      * @see ByteToMessageDecoder#decodeLast(ChannelHandlerContext, Buffer)
152      */
153     protected void decodeLast(ChannelHandlerContext ctx, Buffer in) throws Exception {
154         if (in.readableBytes() > 0) {
155             // Only call decode() if there is something left in the buffer to decode.
156             // See https://github.com/netty/netty/issues/4386
157             decode(ctx, in);
158         }
159     }
160 
161     private final class Encoder extends MessageToByteEncoder<I> {
162         private final BufferAllocator allocator;
163 
164         Encoder(BufferAllocator allocator) {
165             this.allocator = allocator;
166         }
167 
168         @Override
169         public boolean acceptOutboundMessage(Object msg) throws Exception {
170             return ByteToMessageCodec.this.acceptOutboundMessage(msg);
171         }
172 
173         @Override
174         protected Buffer allocateBuffer(ChannelHandlerContext ctx, I msg) throws Exception {
175             BufferAllocator alloc = allocator != null? allocator : ctx.bufferAllocator();
176             return alloc.allocate(256);
177         }
178 
179         @Override
180         protected void encode(ChannelHandlerContext ctx, I msg, Buffer out) throws Exception {
181             ByteToMessageCodec.this.encode(ctx, msg, out);
182         }
183     }
184 }