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.channel.ChannelHandler; 19 import io.netty5.channel.ChannelHandlerAdapter; 20 import io.netty5.channel.ChannelHandlerContext; 21 import io.netty5.channel.ChannelPipeline; 22 import io.netty5.util.Resource; 23 import io.netty5.util.concurrent.Future; 24 import io.netty5.util.concurrent.Promise; 25 import io.netty5.util.concurrent.PromiseCombiner; 26 import io.netty5.util.internal.StringUtil; 27 import io.netty5.util.internal.TypeParameterMatcher; 28 29 import java.util.List; 30 31 import static io.netty5.util.internal.SilentDispose.autoClosing; 32 33 /** 34 * {@link ChannelHandler} which encodes from one message to another message 35 * 36 * For example here is an implementation which decodes an {@link Integer} to an {@link String}. 37 * 38 * <pre>{@code 39 * public class IntegerToStringEncoder extends 40 * MessageToMessageEncoder<Integer> { 41 * 42 * @Override 43 * public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out) 44 * throws Exception { 45 * out.add(message.toString()); 46 * } 47 * } 48 * }</pre> 49 * 50 * Note that messages passed to {@link #encode(ChannelHandlerContext, Object, List)} will be 51 * {@linkplain Resource#dispose(Object) disposed of} automatically. 52 * <p> 53 * To take control of the message lifetime, you should instead override the 54 * {@link #encodeAndClose(ChannelHandlerContext, Object, List)} method. 55 * <p> 56 * Do not override both. 57 */ 58 public abstract class MessageToMessageEncoder<I> extends ChannelHandlerAdapter { 59 60 private final TypeParameterMatcher matcher; 61 62 /** 63 * Create a new instance which will try to detect the types to match out of the type parameter of the class. 64 */ 65 protected MessageToMessageEncoder() { 66 matcher = TypeParameterMatcher.find(this, MessageToMessageEncoder.class, "I"); 67 } 68 69 /** 70 * Create a new instance 71 * 72 * @param outboundMessageType The type of messages to match and so encode 73 */ 74 protected MessageToMessageEncoder(Class<? extends I> outboundMessageType) { 75 matcher = TypeParameterMatcher.get(outboundMessageType); 76 } 77 78 /** 79 * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next 80 * {@link ChannelHandler} in the {@link ChannelPipeline}. 81 */ 82 public boolean acceptOutboundMessage(Object msg) throws Exception { 83 return matcher.match(msg); 84 } 85 86 @Override 87 public Future<Void> write(ChannelHandlerContext ctx, Object msg) { 88 CodecOutputList out = null; 89 try { 90 if (acceptOutboundMessage(msg)) { 91 out = CodecOutputList.newInstance(); 92 @SuppressWarnings("unchecked") 93 I cast = (I) msg; 94 Promise<Void> promise = ctx.newPromise(); 95 try { 96 encodeAndClose(ctx, cast, out); 97 98 if (out.isEmpty()) { 99 throw new EncoderException( 100 StringUtil.simpleClassName(this) + " must produce at least one message."); 101 } 102 } finally { 103 if (out.size() == 1) { 104 ctx.write(out.getUnsafe(0)).cascadeTo(promise); 105 } else { 106 writePromiseCombiner(ctx, out, promise); 107 } 108 } 109 return promise.asFuture(); 110 } else { 111 return ctx.write(msg); 112 } 113 } catch (EncoderException e) { 114 return ctx.newFailedFuture(e); 115 } catch (Throwable t) { 116 return ctx.newFailedFuture(new EncoderException( 117 "Unhandled exception in encoder " + getClass().getName(), t)); 118 } finally { 119 if (out != null) { 120 out.recycle(); 121 } 122 } 123 } 124 125 private static void writePromiseCombiner(ChannelHandlerContext ctx, CodecOutputList out, Promise<Void> promise) { 126 final PromiseCombiner combiner = new PromiseCombiner(ctx.executor()); 127 for (int i = 0; i < out.size(); i++) { 128 combiner.add(ctx.write(out.getUnsafe(i))); 129 } 130 combiner.finish(promise); 131 } 132 133 /** 134 * Encode from one message to another. This method will be called for each written message that can be handled 135 * by this encoder. 136 * <p> 137 * The message will be {@linkplain Resource#dispose(Object) disposed of} after this call. 138 * <p> 139 * Subclasses that wish to sometimes pass messages through, should instead override the 140 * {@link #encodeAndClose(ChannelHandlerContext, Object, List)} method. 141 * 142 * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to. 143 * @param msg the message to encode to another one. 144 * @param out the {@link List} into which produced output messages should be added. 145 * @throws Exception is thrown if an error occurs. 146 */ 147 protected void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception { 148 throw new CodecException(getClass().getName() + " must override either encode() or encodeAndClose()."); 149 } 150 151 /** 152 * Encode from one message to another. This method will be called for each written message that can be handled 153 * by this encoder. 154 * <p> 155 * The message will not be automatically {@linkplain Resource#dispose(Object) disposed of} after this call. 156 * Instead, the responsibility of ensuring that messages are disposed of falls upon the implementor of this method. 157 * <p> 158 * Subclasses that wish to have incoming messages automatically disposed of should instead override the 159 * {@link #encode(ChannelHandlerContext, Object, List)} method. 160 * 161 * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to. 162 * @param msg the message to encode to another one. 163 * @param out the {@link List} into which produced output messages should be added. 164 * @throws Exception is thrown if an error occurs. 165 */ 166 protected void encodeAndClose(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception { 167 try (AutoCloseable ignore = autoClosing(msg)) { 168 encode(ctx, msg, out); 169 } 170 } 171 }