AbstractLocalPacket.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */
package org.waarp.openr66.protocol.localhandler.packet;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.util.IllegalReferenceCountException;
import org.waarp.common.utility.WaarpNettyUtil;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolPacketException;
import org.waarp.openr66.protocol.localhandler.LocalChannelReference;
import org.waarp.openr66.protocol.networkhandler.packet.NetworkPacket;

import static org.waarp.openr66.protocol.networkhandler.packet.NetworkPacket.*;

/**
 * This class represents Abstract Packet with its header, middle and end parts.
 * A Packet is composed of one
 * Header part, one Middle part (data), and one End part. Header: length field
 * (4 bytes) = Middle length field
 * (4 bytes), End length field (4 bytes), type field (1 byte), ...<br>
 * Middle: (Middle length field bytes)<br>
 * End: (End length field bytes) = code status field (4 bytes), ...<br>
 */
public abstract class AbstractLocalPacket {
  protected static final byte[] EMPTY_ARRAY = {};
  protected static final int LOCAL_HEADER_SIZE = 4 * 3 + 1;

  protected ByteBuf header;

  protected ByteBuf middle;

  protected ByteBuf end;

  protected ByteBuf global = null;

  protected AbstractLocalPacket() {
    header = null;
    middle = null;
    end = null;
  }

  /**
   * @return True if all buffers fit in one piece
   */
  public abstract boolean hasGlobalBuffer();

  /**
   * Prepare the 3 buffers Header, Middle and End
   *
   * @throws OpenR66ProtocolPacketException
   */
  public abstract void createAllBuffers(final LocalChannelReference lcr,
                                        final int networkHeader)
      throws OpenR66ProtocolPacketException;

  /**
   * Prepare the Header buffer
   *
   * @throws OpenR66ProtocolPacketException
   */
  public void createHeader(final LocalChannelReference lcr)
      throws OpenR66ProtocolPacketException {
    throw new IllegalStateException("Should not be called");
  }

  /**
   * Prepare the Middle buffer
   *
   * @throws OpenR66ProtocolPacketException
   */
  public void createMiddle(final LocalChannelReference lcr)
      throws OpenR66ProtocolPacketException {
    throw new IllegalStateException("Should not be called");
  }

  /**
   * Prepare the End buffer
   *
   * @throws OpenR66ProtocolPacketException
   */
  public void createEnd(final LocalChannelReference lcr)
      throws OpenR66ProtocolPacketException {
    throw new IllegalStateException("Should not be called");
  }

  /**
   * @return the type of Packet
   */
  public abstract byte getType();

  @Override
  public abstract String toString();

  /**
   * @param lcr the LocalChannelReference in use
   *
   * @return the ByteBuf as LocalPacket
   *
   * @throws OpenR66ProtocolPacketException
   */
  public final ByteBuf getLocalPacket(final LocalChannelReference lcr)
      throws OpenR66ProtocolPacketException {
    return getLocalPacketForNetworkPacket(lcr, null);
  }

  /**
   * @param lcr the LocalChannelReference in use
   *
   * @return the ByteBuf as LocalPacket
   *
   * @throws OpenR66ProtocolPacketException
   */
  public final synchronized ByteBuf getLocalPacketForNetworkPacket(
      final LocalChannelReference lcr, final NetworkPacket packet)
      throws OpenR66ProtocolPacketException {
    try {
      final ByteBuf buf;
      final int globalHeader;
      if (packet != null) {
        globalHeader = NETWORK_HEADER_SIZE;
      } else {
        globalHeader = 0;
      }
      if (hasGlobalBuffer()) {
        if (global == null) {
          createAllBuffers(lcr, globalHeader);
        } else {
          global.readerIndex(0);
          global.writerIndex(0);
        }
        buf = global;
      } else {
        // 3 header lengths+type
        buf =
            ByteBufAllocator.DEFAULT.ioBuffer(globalHeader + LOCAL_HEADER_SIZE,
                                              globalHeader + LOCAL_HEADER_SIZE);
        if (header == null) {
          createHeader(lcr);
        }
        if (middle == null) {
          createMiddle(lcr);
        }
        if (end == null) {
          createEnd(lcr);
        }
      }
      if (packet != null) {
        final int capacity =
            LOCAL_HEADER_SIZE + (header != null? header.capacity() : 0) +
            (middle != null? middle.capacity() : 0) +
            (end != null? end.capacity() : 0);
        packet.writeNetworkHeader(buf, capacity);
      }
      return getByteBuf(buf);
    } catch (final IllegalReferenceCountException e) {
      throw new OpenR66ProtocolPacketException(e);
    }
  }

  private ByteBuf getByteBuf(final ByteBuf buf) {
    final ByteBuf newHeader = header != null? header : Unpooled.EMPTY_BUFFER;
    final int headerLength = LOCAL_HEADER_SIZE - 4 + newHeader.readableBytes();
    final ByteBuf newMiddle = middle != null? middle : Unpooled.EMPTY_BUFFER;
    final int middleLength = newMiddle.readableBytes();
    final ByteBuf newEnd = end != null? end : Unpooled.EMPTY_BUFFER;
    final int endLength = newEnd.readableBytes();
    buf.writeInt(headerLength);
    buf.writeInt(middleLength);
    buf.writeInt(endLength);
    buf.writeByte(getType());
    if (hasGlobalBuffer()) {
      buf.writerIndex(buf.capacity());
      return buf;
    }
    return ByteBufAllocator.DEFAULT.compositeDirectBuffer(4)
                                   .addComponents(buf, newHeader, newMiddle,
                                                  newEnd);
  }

  public synchronized void clear() {
    if (WaarpNettyUtil.release(global)) {
      global = null;
    }
    if (hasGlobalBuffer()) {
      return;
    }
    if (WaarpNettyUtil.release(header)) {
      header = null;
    }
    if (WaarpNettyUtil.release(middle)) {
      middle = null;
    }
    if (WaarpNettyUtil.release(end)) {
      end = null;
    }
  }

  public final synchronized void retain() {
    WaarpNettyUtil.retain(global);
    if (hasGlobalBuffer()) {
      return;
    }
    WaarpNettyUtil.retain(header);
    WaarpNettyUtil.retain(middle);
    WaarpNettyUtil.retain(end);
  }
}