/* * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.io; import io.netty.buffer.ByteBuf; import io.netty.buffer.CompositeByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; public abstract class Decoder extends ChannelInboundHandlerAdapter { // Netty MessageAggregator default value protected static final int DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS = 1024; private ByteBuf cumulation; @Override public final void channelRead(ChannelHandlerContext context, Object message) throws Exception { if (message instanceof ByteBuf) { ByteBuf input = (ByteBuf)message; try { messageReceived(context, input); } finally { // client should release buffer as soon as possible, so, input could be released already if (input.refCnt() > 0) { input.release(); } } } else { context.fireChannelRead(message); } } protected abstract void messageReceived(@NotNull ChannelHandlerContext context, @NotNull ByteBuf input) throws Exception; public interface FullMessageConsumer<T> { T contentReceived(@NotNull ByteBuf input, @NotNull ChannelHandlerContext context, boolean isCumulateBuffer) throws IOException; } @Nullable protected final <T> T readContent(@NotNull ByteBuf input, @NotNull ChannelHandlerContext context, int contentLength, @NotNull FullMessageConsumer<T> fullMessageConsumer) throws IOException { ByteBuf buffer = getBufferIfSufficient(input, contentLength, context); if (buffer == null) { return null; } boolean isCumulateBuffer = buffer != input; int oldReaderIndex = input.readerIndex(); try { return fullMessageConsumer.contentReceived(buffer, context, isCumulateBuffer); } finally { if (isCumulateBuffer) { // cumulation buffer - release it buffer.release(); } else { buffer.readerIndex(oldReaderIndex + contentLength); } } } @Nullable protected final ByteBuf getBufferIfSufficient(@NotNull ByteBuf input, int requiredLength, @NotNull ChannelHandlerContext context) { if (!input.isReadable()) { return null; } if (cumulation == null) { if (input.readableBytes() < requiredLength) { cumulation = input; input.retain(); input.touch(); return null; } else { return input; } } else { int currentAccumulatedByteCount = cumulation.readableBytes(); if ((currentAccumulatedByteCount + input.readableBytes()) < requiredLength) { CompositeByteBuf compositeByteBuf; if ((cumulation instanceof CompositeByteBuf)) { compositeByteBuf = (CompositeByteBuf)cumulation; } else { compositeByteBuf = context.alloc().compositeBuffer(DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS); compositeByteBuf.addComponent(cumulation); cumulation = compositeByteBuf; } compositeByteBuf.addComponent(input); input.retain(); input.touch(); return null; } else { CompositeByteBuf buffer; if (cumulation instanceof CompositeByteBuf) { buffer = (CompositeByteBuf)cumulation; buffer.addComponent(input); } else { // may be it will be used by client to cumulate something - don't set artificial restriction (2) buffer = context.alloc().compositeBuffer(DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS); buffer.addComponents(cumulation, input); } // we don't set writerIndex on addComponent, it is clear to set it to requiredLength here buffer.writerIndex(requiredLength); input.skipBytes(requiredLength - currentAccumulatedByteCount); input.retain(); input.touch(); cumulation = null; return buffer; } } } @Override public void channelInactive(ChannelHandlerContext context) throws Exception { try { if (cumulation != null) { cumulation.release(); cumulation = null; } } finally { super.channelInactive(context); } } }