1 /*
2 * Copyright 2022 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.channel.ChannelHandler;
20 import io.netty5.channel.ChannelHandlerAdapter;
21 import io.netty5.channel.ChannelHandlerContext;
22 import io.netty5.channel.ChannelPipeline;
23 import io.netty5.util.concurrent.Future;
24 import io.netty5.util.internal.TypeParameterMatcher;
25
26 import static io.netty5.util.internal.SilentDispose.autoClosing;
27 import static java.util.Objects.requireNonNull;
28
29 /**
30 * {@link ChannelHandler} which encodes message in a stream-like fashion from one message to a {@link Buffer}.
31 *
32 * <p>Example implementation which encodes {@link Integer}s to a {@link Buffer}.
33 *
34 * <pre>
35 * public class IntegerEncoder extends {@link MessageToByteEncoder}<{@link Integer}> {
36 * {@code @Override}
37 * public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link Buffer} out)
38 * throws {@link Exception} {
39 * out.writeInt(msg);
40 * }
41 * }
42 * </pre>
43 */
44 public abstract class MessageToByteEncoder<I> extends ChannelHandlerAdapter {
45
46 private final TypeParameterMatcher matcher;
47
48 /**
49 * Create a new instance which will try to detect the types to match out of the type parameter of the class.
50 */
51 protected MessageToByteEncoder() {
52 matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
53 }
54
55 /**
56 * Create a new instance.
57 *
58 * @param outboundMessageType The type of messages to match.
59 */
60 protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {
61 matcher = TypeParameterMatcher.get(requireNonNull(outboundMessageType, "outboundMessageType"));
62 }
63
64 /**
65 * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
66 * {@link ChannelHandler} in the {@link ChannelPipeline}.
67 */
68 public boolean acceptOutboundMessage(Object msg) throws Exception {
69 return matcher.match(msg);
70 }
71
72 @Override
73 public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
74 Buffer buf = null;
75 try {
76 if (acceptOutboundMessage(msg)) {
77 @SuppressWarnings("unchecked")
78 I cast = (I) msg;
79 buf = allocateBuffer(ctx, cast);
80 try (AutoCloseable ignore = autoClosing(cast)) {
81 encode(ctx, cast, buf);
82 }
83
84 if (buf.readableBytes() > 0) {
85 Future<Void> f = ctx.write(buf);
86 buf = null;
87 return f;
88 }
89 return ctx.write(ctx.bufferAllocator().allocate(0));
90 }
91 return ctx.write(msg);
92 } catch (EncoderException e) {
93 return ctx.newFailedFuture(e);
94 } catch (Throwable e) {
95 return ctx.newFailedFuture(new EncoderException(e));
96 } finally {
97 if (buf != null) {
98 buf.close();
99 }
100 }
101 }
102
103 /**
104 * Allocate a {@link Buffer} which will be used as argument of {@link #encode(ChannelHandlerContext, I, Buffer)}.
105 *
106 * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to
107 * @param msg the message to be encoded
108 */
109 protected abstract Buffer allocateBuffer(ChannelHandlerContext ctx, I msg) throws Exception;
110
111 /**
112 * Encode a message into a {@link Buffer}. This method will be called for each written message that can be handled
113 * by this encoder.
114 *
115 * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to
116 * @param msg the message to encode
117 * @param out the {@link Buffer} into which the encoded message will be written
118 * @throws Exception is thrown if an error occurs
119 */
120 protected abstract void encode(ChannelHandlerContext ctx, I msg, Buffer out) throws Exception;
121 }