Package marauroa.common.net

Source Code of marauroa.common.net.Decoder$MessageParts

/* $Id: Decoder.java,v 1.31 2010/05/27 19:13:32 nhnb Exp $ */
/***************************************************************************
*            (C) Copyright 2003 - Marauroa             *
***************************************************************************
***************************************************************************
*                                       *
*   This program 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 2 of the License, or     *
*   (at your option) any later version.                   *
*                                       *
***************************************************************************/
package marauroa.common.net;

import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import marauroa.common.Log4J;
import marauroa.common.net.message.Message;

/**
* This class decodes a stream of bytes and builds a Marauroa message with it.
* Decoder follows singleton pattern.
*
* @author miguel, hendrik
*/
public class Decoder {

  /** the logger instance. */
  private static final marauroa.common.Logger logger = Log4J.getLogger(Decoder.class);

  /**
   * This class handles incomplete messages.
   */
  class MessageParts {

    public Vector<byte[]> parts;

    public MessageParts() {
      parts = new Vector<byte[]>();
    }

    /**
     * Adds a new part to complete the message
     *
     * @param data data to add
     */
    public void add(byte[] data) {
      parts.add(data);
    }
   
    public boolean isEmpty() {
      return parts.isEmpty();
    }

    /**
     * Try to build the message for the channel using the existing parts or
     * return null if it is not completed yet.
     *
     * @param channel SocketChannel to read from
     * @return Message read message
     * @throws IOException in case of an input / output error
     * @throws InvalidVersionException in case the protocol version is not supported
     */
    public Message build(SocketChannel channel) throws IOException, InvalidVersionException {
      int length = 0;
      for (byte[] p : parts) {
        length += p.length;
      }

      // the first 4 bytes are the size
      if (length < 4) {
        return null;
      }
     
      int size = readSizeOfMessage();
     
      /*
       * If length is bigger than size that means that two messages on
       * a row... so we need to run until the end of the first one.
       */
      if (length < size) {
        /*
         * Still missing parts, let's wait
         */
        return null;
      }

      byte[] data = new byte[size];
     
      int remaining=size;

      int offset = 0;
      Iterator<byte[]> it=parts.iterator();
      while(it.hasNext()) {
        byte[] p=it.next();
       
        if(remaining-p.length<0) {
          /*
           * This part completes first message and has stuff from the second one.
           */
          System.arraycopy(p, 0, data, offset, remaining);
         
          /*
           * Copy the rest of the array to a new array.           
           */
          byte[] rest = new byte[p.length-remaining];
          System.arraycopy(p, remaining, rest, 0, p.length-remaining);
          parts.set(0, rest);

          /*
           * Stop iterating and process the currently complete message.
           */
          break;
        } else {
          System.arraycopy(p, 0, data, offset, p.length);
          offset += p.length;
          remaining -=p.length;
         
          it.remove();
        }
      }

      Message msg = msgFactory.getMessage(data, channel, 4);
      return msg;
    }

    /**
     * reads the size of the message. Important: It expects at least 4 bytes in the buffer.
     *
     * @return size
     */
    private int readSizeOfMessage() {
      byte[] size = new byte[4];
      int offset = 0;

      loops:
      for (byte[] part : parts) {
        for (int i = 0; i < part.length; i++) {
          size[offset] = part[i];

          // if we have read four bytes, break
          if (offset == 3) {
            break loops;
          }
          offset++;
        }
      }

      return getSizeOfMessage(size);
    }
  }

  /** We map each channel with the sent content */
  private Map<SocketChannel, MessageParts> content;

  /** MessageFactory */
  private MessageFactory msgFactory;

  /** singleton instance */
  private static Decoder instance;

  /**
   * Returns an unique instance of decoder
   *
   * @return an unique instance of decoder
   */
  public static Decoder get() {
    if (instance == null) {
      instance = new Decoder();
    }

    return instance;
  }

  /**
   * Constructor
   *
   */
  private Decoder() {
    content = new HashMap<SocketChannel, MessageParts>();
    msgFactory = MessageFactory.getFactory();
  }

  /**
   * Removes any pending data from a connection
   *
   * @param channel
   *            the channel to clear data from.
   */
  public void clear(SocketChannel channel) {
    content.remove(channel);
  }
 
  static int getSizeOfMessage(byte[] data) {
    return (data[0] & 0xFF) + ((data[1] & 0xFF) << 8) + ((data[2] & 0xFF) << 16)
        + ((data[3] & 0xFF) << 24);
  }

  /**
   * Decodes a message from a stream of bytes received from channel
   *
   * @param channel
   *            the socket from where data was received
   * @param data
   *            the data received
   * @return a message or null if it was not possible
   * @throws IOException
   *             if there is a problem building the message
   * @throws InvalidVersionException
   *             if the message version mismatch the expected version
   */
  public List<Message> decode(SocketChannel channel, byte[] data) throws IOException,
          InvalidVersionException {
    MessageParts buffers = content.get(channel);

    if (buffers == null) {
      /* First part of the message */
      /*
       * We need to be *sure* that 4 bytes are at least to
       * be received...
       */
      if(data.length<4) {
        throw new IOException("Message is too short. Missing mandatory fields.");
      }
     
      int size = getSizeOfMessage(data);

      if(size<0) {
        throw new IOException("Message size is negative. Message ignored.");
      }

      if (data.length == size) {
        /* If we have the full data build the message */
        Message msg = msgFactory.getMessage(data, channel, 4);
        List<Message> list=new LinkedList<Message>();
        list.add(msg);
       
        return list;
      } else {
        logger.debug("Message full body missing ("+size+"), waiting for more data ("+data.length+").");
        /* If there is still data to store it. */
        buffers = new MessageParts();
        content.put(channel, buffers);
      }
    } else {
      logger.debug("Existing data, trying to complete Message full body");
    }

    buffers.add(data);
    List<Message> list = new LinkedList<Message>();

    while (!buffers.isEmpty()) {
      Message msg = buffers.build(channel);

      if (msg != null) {
        list.add(msg);
      } else {
        if (list.isEmpty()) {
          return null;
        }
        return list;
      }
    }
   
    content.remove(channel);

    return list;
  }
}
TOP

Related Classes of marauroa.common.net.Decoder$MessageParts

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.