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.netty.handler.codec; 17 18 import io.netty.buffer.ByteBuf; 19 import io.netty.buffer.Unpooled; 20 import io.netty.channel.ChannelHandlerContext; 21 import io.netty.channel.ChannelOutboundHandler; 22 import io.netty.channel.ChannelOutboundHandlerAdapter; 23 import io.netty.channel.ChannelPipeline; 24 import io.netty.channel.ChannelPromise; 25 import io.netty.util.ReferenceCountUtil; 26 import io.netty.util.internal.TypeParameterMatcher; 27 28 29 /** 30 * {@link ChannelOutboundHandlerAdapter} which encodes message in a stream-like fashion from one message to an 31 * {@link ByteBuf}. 32 * 33 * 34 * Example implementation which encodes {@link Integer}s to a {@link ByteBuf}. 35 * 36 * <pre> 37 * public class IntegerEncoder extends {@link MessageToByteEncoder}<{@link Integer}> { 38 * {@code @Override} 39 * public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link ByteBuf} out) 40 * throws {@link Exception} { 41 * out.writeInt(msg); 42 * } 43 * } 44 * </pre> 45 */ 46 public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter { 47 48 private final TypeParameterMatcher matcher; 49 private final boolean preferDirect; 50 51 /** 52 * see {@link #MessageToByteEncoder(boolean)} with {@code true} as boolean parameter. 53 */ 54 protected MessageToByteEncoder() { 55 this(true); 56 } 57 58 /** 59 * see {@link #MessageToByteEncoder(Class, boolean)} with {@code true} as boolean value. 60 */ 61 protected MessageToByteEncoder(Class<? extends I> outboundMessageType) { 62 this(outboundMessageType, true); 63 } 64 65 /** 66 * Create a new instance which will try to detect the types to match out of the type parameter of the class. 67 * 68 * @param preferDirect {@code true} if a direct {@link ByteBuf} should be tried to be used as target for 69 * the encoded messages. If {@code false} is used it will allocate a heap 70 * {@link ByteBuf}, which is backed by an byte array. 71 */ 72 protected MessageToByteEncoder(boolean preferDirect) { 73 matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I"); 74 this.preferDirect = preferDirect; 75 } 76 77 /** 78 * Create a new instance 79 * 80 * @param outboundMessageType The type of messages to match 81 * @param preferDirect {@code true} if a direct {@link ByteBuf} should be tried to be used as target for 82 * the encoded messages. If {@code false} is used it will allocate a heap 83 * {@link ByteBuf}, which is backed by an byte array. 84 */ 85 protected MessageToByteEncoder(Class<? extends I> outboundMessageType, boolean preferDirect) { 86 matcher = TypeParameterMatcher.get(outboundMessageType); 87 this.preferDirect = preferDirect; 88 } 89 90 /** 91 * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next 92 * {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. 93 */ 94 public boolean acceptOutboundMessage(Object msg) throws Exception { 95 return matcher.match(msg); 96 } 97 98 @Override 99 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 100 ByteBuf buf = null; 101 try { 102 if (acceptOutboundMessage(msg)) { 103 @SuppressWarnings("unchecked") 104 I cast = (I) msg; 105 buf = allocateBuffer(ctx, cast, preferDirect); 106 try { 107 encode(ctx, cast, buf); 108 } finally { 109 ReferenceCountUtil.release(cast); 110 } 111 112 if (buf.isReadable()) { 113 ctx.write(buf, promise); 114 } else { 115 buf.release(); 116 ctx.write(Unpooled.EMPTY_BUFFER, promise); 117 } 118 buf = null; 119 } else { 120 ctx.write(msg, promise); 121 } 122 } catch (EncoderException e) { 123 throw e; 124 } catch (Throwable e) { 125 throw new EncoderException(e); 126 } finally { 127 if (buf != null) { 128 buf.release(); 129 } 130 } 131 } 132 133 /** 134 * Allocate a {@link ByteBuf} which will be used as argument of {@link #encode(ChannelHandlerContext, I, ByteBuf)}. 135 * Sub-classes may override this method to return {@link ByteBuf} with a perfect matching {@code initialCapacity}. 136 */ 137 protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg, 138 boolean preferDirect) throws Exception { 139 if (preferDirect) { 140 return ctx.alloc().ioBuffer(); 141 } else { 142 return ctx.alloc().heapBuffer(); 143 } 144 } 145 146 /** 147 * Encode a message into a {@link ByteBuf}. This method will be called for each written message that can be handled 148 * by this encoder. 149 * 150 * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to 151 * @param msg the message to encode 152 * @param out the {@link ByteBuf} into which the encoded message will be written 153 * @throws Exception is thrown if an error occurs 154 */ 155 protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception; 156 157 protected boolean isPreferDirect() { 158 return preferDirect; 159 } 160 }