/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * @(#)GPacket.java	1.5 06/27/07
 */ 

package com.sun.messaging.jmq.io;

import java.io.*;
import java.nio.*;
import java.util.*;

/**
 *
 * A Generic binary packet format. This class encapsulates a simple
 * binary packet format. The packet is made up of a fixed header,
 * a marshalled properties section, and an opaque payload. 
 */
public class GPacket {

    /* The 32 bit flags */
    public static final int A_BIT = 0x00000001;
    public static final int B_BIT = 0x00000002;
    public static final int C_BIT = 0x00000004;
    public static final int D_BIT = 0x00000008;
    public static final int E_BIT = 0x00000010;
    public static final int F_BIT = 0x00000020;
    public static final int G_BIT = 0x00000040;
    public static final int H_BIT = 0x00000080;
    public static final int I_BIT = 0x00000100;
    public static final int J_BIT = 0x00000200;
    public static final int K_BIT = 0x00000400;
    public static final int L_BIT = 0x00000800;
    public static final int M_BIT = 0x00001000;
    public static final int N_BIT = 0x00002000;
    public static final int O_BIT = 0x00004000;
    public static final int P_BIT = 0x00008000;
    public static final int Q_BIT = 0x00010000;
    public static final int R_BIT = 0x00020000;
    public static final int S_BIT = 0x00040000;
    public static final int T_BIT = 0x00080000;
    public static final int U_BIT = 0x00100000;
    public static final int V_BIT = 0x00200000;
    public static final int W_BIT = 0x00400000;
    public static final int X_BIT = 0x00800000;
    public static final int Y_BIT = 0x01000000;
    public static final int Z_BIT = 0x02000000;
    public static final int a_BIT = 0x04000000;
    public static final int b_BIT = 0x08000000;
    public static final int c_BIT = 0x10000000;
    public static final int d_BIT = 0x20000000;
    public static final int e_BIT = 0x40000000;
    public static final int f_BIT = 0x80000000;

    public static final int VERSION350  = 350;
    public static final int VERSION     = VERSION350;
    public static final int MAGIC       = 2147476418;
    public static final int HEADER_SIZE = 36;

    private boolean genTimestamp = true;
    private boolean genSequenceNumber  = true;

    protected static long sequenceCounter = 0;

    protected short version       = VERSION;
    protected short type          = 0;
    protected int   size          = 0;
    protected int   propsByteSize = 0;
    protected int   magic         = MAGIC;
    protected long  sequence      = 0;
    protected long  timestamp     = 0;
    protected int   bitFlags      = 0;

    protected ByteBuffer propsBuf = null;
    protected Map      props      = null;
    protected boolean  propsDirty = false;
    protected boolean  versionMismatch = false;

    protected ByteBuffer headerBuf = ByteBuffer.allocate(HEADER_SIZE);
    protected boolean    headerDirty = false;

    protected ByteBuffer payload  = null;

    /**
     * Get an instance of an empty packet
     */
    public static GPacket getInstance() {
        return new GPacket();
    }

    private GPacket() {
    }

    /**
     * Parse the header buffer into instance variables
     */
    private void unmarshallHeader()
        throws StreamCorruptedException, IOException {

        headerBuf.rewind();

        version = headerBuf.getShort();
        type    = headerBuf.getShort();
        size    = headerBuf.getInt();
        magic   = headerBuf.getInt();

        if (version != VERSION350) {
            versionMismatch = true;
        } else {
            versionMismatch = false;
        }

        if (magic != MAGIC) {
            throw new StreamCorruptedException("Bad packet magic number: " +
                magic + ". Expecting: " + MAGIC);
        }

        propsByteSize = headerBuf.getInt();
        timestamp     = headerBuf.getLong();
        sequence      = headerBuf.getLong();
        bitFlags      = headerBuf.getInt();

        headerBuf.rewind();

        headerDirty = false;
    }

    /**
     * Prepare the header buffer for writing by marshalling the
     * instance variables into the header buffer.
     */
    private void marshallHeader() 
        throws IOException {

        if (propsDirty) {
            // Will set headerDirty 
            marshallProperties();
        }

        if (!headerDirty) {
            return;
        }

        size = headerBuf.capacity() + propsByteSize;

        if (payload != null) {
            size += payload.capacity();
        }

        headerBuf.rewind();
        headerBuf.putShort(version);
        headerBuf.putShort(type);
        headerBuf.putInt(size);
        headerBuf.putInt(magic);
        headerBuf.putInt(propsByteSize);
        headerBuf.putLong(timestamp);
        headerBuf.putLong(sequence);
        headerBuf.putInt(bitFlags);

        headerBuf.rewind();

        headerDirty = false;
    }

    /**
     * Parse the properties buffer into a hash table
     */
    private void unmarshallProperties() 
        throws IOException, ClassNotFoundException {

        if (propsByteSize > 0) {
            props = PacketProperties.parseProperties(
                    new JMQByteBufferInputStream(propsBuf));
            propsBuf.rewind();
        } else {
            props = new Hashtable();
        }
        propsDirty = false;
        return;
    }

    /**
     * Prepare the property buffer by marshalling the properties hash
     * table into the property buffer.
     */
    private void marshallProperties()
        throws IOException {

        if (!propsDirty) {
            return;
        }

        // Backing byte array will grow if needed
        JMQByteArrayOutputStream bos =
                            new JMQByteArrayOutputStream(new byte[256]);
        PacketProperties.write(props, bos);

        propsBuf = ByteBuffer.wrap(bos.getBuf(), 0, bos.getCount());
        propsBuf.rewind();
        propsByteSize = bos.getCount();
        propsDirty = false;
        headerDirty = true;
    }

    /**
     * Set the packet's payload. The passed buffer is not copied, it
     * is sliced with ByteBuffer.slice().
     *
     * @param   b   ByteBuffer containing payload. The buffer is not
     *              copied it is sliced. The slice starts at the
     *              buffer's current <code>position</code>. 
     *              <code>b</code> may be <code>null</code> which 
     *              essentially removes any payload that was previously set.
     */
    public synchronized void setPayload(ByteBuffer b) {
        if (b == null) {
            payload = null;
        } else {
            payload = b.slice();
        }
        headerDirty = true;
    }

    /**
     * Return the packet's payload. The returned buffer is not a copy,
     * it is a slice.
     *
     * @return ByteBuffer containing the payload. Note the returned buffer
     *                    is not a copy, it is a slice.
     */
    public synchronized ByteBuffer getPayload() {
        if (payload == null) {
            return null;
        } else {
            return payload.slice();
        }
    }

    /**
     * Set packet version. Default is current version
     *
     * @param   v   Set packet version. Default is the most recent version
     *              which is defined by GPacket.VERSION
     */
    public synchronized void setVersion(short v) {
        version = v;
        headerDirty = true;
    }

    /**
     * Get packet version.
     *
     * @return  Version of this packet.
     */
    public synchronized short getVersion() {
        return version;
    }

    /**
     * Set packet type. This is an application specific value
     *
     * @param   t   The packet type. This is an application defined value.
     *              Default is 0.
     */
    public synchronized void setType(short t) {
        type = t;
        headerDirty = true;
    }

    /**
     * Get packet type
     *
     * @return  Packet type.
     */
    public synchronized short getType() {
        return type;
    }


    /**
     * Set packet timestamp. By default it's generated
     * when the packet is written. If you want to set the timestamp
     * using this method you must turn off timestamp generation
     * using <code>generateTimestamp()</code>, otherwise
     * what you set will be overwritten when the packet is written.
     *
     * @param   t   Timestamp to set.
     *
     * @see generateTimestamp()
     * @see updateTimestamp()
     */
    public synchronized void setTimestamp(long t) {
        timestamp = t;
        headerDirty = true;
    }


    /**
     * Get packet timestamp.
     *
     * @return Packet's timestamp
     */
    public synchronized long getTimestamp() {
        return timestamp;
    }

    /**
     * Set packet sequence number. By default it's generated
     * when the packet is written. If you want to set the sequence number
     * using this method you must turn off sequence number generation
     * using <code>generateSequenceNumber()</code>, otherwise
     * what you set will be overwritten when the packet is written.
     *
     * @param s Sequence number to set on packet.
     *
     * @see generateSequenceNumber()
     * @see updateSequence()
     */
    public synchronized void setSequence(long s) {
        sequence = s;
        headerDirty = true;
    }

    /**
     * Get packet sequence number
     *
     * @return Packet sequence number
     */
    public synchronized long getSequence() {
        return sequence;
    }

    /**
     * Get size of packet in bytes
     *
     * @return Size of fully marshalled packet in bytes.
     */
    public synchronized int getSize() {
        return size;
    }

    /**
     * Get size of marshalled properties in bytes. First
     * call to this may be expensive if the properties need
     * to be marshalled to compute the size.
     *
     * @return Size of marshalled properties in bytes.
     *
     * @throws IOException  If properties could not be marshalled.
     */
    public synchronized int getPropsByteSize() throws
        IOException {
        getProperties();
        if (propsDirty) {
            marshallProperties();
        }
        return propsByteSize;
    }

    /**
     * Get size of payload in bytes
     *
     * @return Size of payload in bytes
     */
    public synchronized int getPayloadSize() {
        if (payload == null) {
            return 0;
        } else {
            return payload.capacity();
        }
    }

    /**
     * Get packets magic number
     *
     * @return Packet's magic number. This should always be GPacket.MAGIC
     */
    public synchronized int getMagic() {
        return magic;
    }

    /**
     * Clear packet properties.
     */
    public synchronized void clearProps() {
        getProperties();
        propsDirty = true;
        props.clear();
    }

    /**
     * Get a packet property
     *
     * @param key   Property key
     * @return      Property value
     */
    public synchronized Object getProp(Object key) {
        getProperties();
        return props.get(key);
    }

    /**
     * Put a packet property
     *
     * @param key       Property key
     * @param value     Property value
     */
    public synchronized void putProp(Object key, Object value) {
        getProperties();
        propsDirty = true;
        props.put(key, value);
    }


    /**
     * Put a map of properties
     *
     * @param t     Map of properties to put
     */
    public synchronized void putAllProps(Map t) {
        getProperties();
        propsDirty = true;
        props.putAll(t);
    }

    /**
     * Remove a property
     *
     * @param   key     Key of property to remove
     */
    public synchronized Object removeProp(Object key) {
        propsDirty = true;
        getProperties();
        propsDirty = true;
        return props.remove(key);
    }

    /**
     * Check if packet has properties
     *
     * return   True if the packet has any properties, else false.
     */
    public synchronized boolean isEmptyProps() {
        if (props == null && propsByteSize == 0) {
            return true;
        } else {
            getProperties();
            return props.isEmpty();
        }
    }

    /**
     * Get the number of properties
     *
     * @return  The number of properties the packet has
     */
    public synchronized int propsSize() {
        if (props == null && propsByteSize == 0) {
            return 0;
        }
        getProperties();
        return props.size();
    }

    /**
     * Returns a Set of keys.
     *
     * @return Set of keys
     *
     * @see java.util.Map.keySet()
     */
    public synchronized Set propsKeySet() {
        getProperties();
        return props.keySet();
    }

    /**
     * Returns a Set of entries (Map.Entry)
     *
     * @return Set of entries (Map.Entry)
     *
     * @see java.util.Map.entrySet()
     */
    public synchronized Set propsEntrySet() {
        getProperties();
        return props.entrySet();
    }

    /**
     * Set a bit flag. There are 32 flags available. 
     *
     * @param   bit     The bit to set. Should be one of GPacket.A_BIT
     *                  through GPacket.Z_Bit, or GPacket.a_BIT through
     *                  GPacket.f_Bit.
     *
     * @param   on      <code>true</code> to turn bit on, else
     *                  <code>false</code>
     */
    public synchronized void setBit (int bit, boolean on) {
        if (on) {
            bitFlags = bitFlags | bit;
        } else {
            bitFlags = bitFlags & ~bit;
        }
        headerDirty = true;
    }

    /**
     * Get a bit flag.
     *
     * @param   bit     The bit to get. Should be one of GPacket.A_BIT
     *                  through GPacket.Z_Bit, or GPacket.a_BIT through
     *                  GPacket.f_Bit.
     * @return  <code>true</code> if bit is set, else <code>false</code>
     */
    public synchronized boolean getBit (int bit) {
        return((bitFlags & bit) == bit);
    }

    /**
     * Disable (and enable) sequence number generation. The GPacket
     * specification defines a "sequence number" field that is defined
     * to be a monotonically increasing sequence number. By default GPacket
     * will automatically increment the sequence number and set it
     * on the packet every time writePacket() is called.
     * The sequence number is a class variable so all packets in a VM
     * share the same sequence.
     *
     * @param    generate    <code>true</code> to have the packet
     *                       automatically generate
     *                       sequence numbers for you, <code>false</code> to
     *                       not. Default is <code>true</code>.
     */
    public void generateSequenceNumber(boolean generate) {
        genSequenceNumber = generate;
    }

    /**
     * Disable (and enable) timestamp generation. The GPacket specification
     * specifies a "timestamp" field that is defined to be the time the
     * packet was sent. By default GPacket will automatically 
     * generate a timestamp and set it on the packet every time
     * writePacket() is called.
     *
     * @param    generate    <code>true</code> to have the packet
     *                       automatically generate a timestamp when the
     *                       packet is written, <code>false</code> to not.
     *                       Default is <code>true</code>.
     */
    public void generateTimestamp(boolean generate) {
	genTimestamp = generate;
    }

    /**
     * Update the timestamp on the packet. The will stamp the packet
     * with the current time. If you do this
     * you should call generateTimestamp(false) before writing the
     * packet, otherwise the timestamp will be overwritten when
     * writePacket() is called.
     */
    public synchronized void updateTimestamp() {
        setTimestamp(System.currentTimeMillis());
    }

    /**
     * Update the sequence number on the packet. If you do this
     * you should call generateSequenceNumber(false) before writing the
     * packet, otherwise the sequence number will be overwritten when
     * writePacket() is called.
     */
    public synchronized void updateSequenceNumber() {
	synchronized(getClass()) {
            sequenceCounter++;
	    setSequence(sequenceCounter);
	}
    }

    /**
     * Read packet from an InputStream. This method reads one packet
     * from the InputStream and sets the state of this object to
     * reflect the packet read.
     *
     * If we read a packet with a bad magic number (ie it looks like
     * bogus data), we give up and throw a StreamCorruptedException. 
     *
     * If we read a packet that does not match our packet version
     * we attempt to read the entire packet and throw an
     * IllegalArgumentException.
     *
     * This method blocks until a full packet can be read.
     *
     * @param is            the InputStream to read the packet from
     *
     * @throws IOException  if there was an error reading
     *
     * @throws EOFException if the connection has been closed
     *
     * @throws StreamCorruptedException if the packet data appears corrupted.
     *                                  If this is thrown then either there
     *                                  is bad data on the wire or GPacket 
     *                                  has a bug and got in the wrong spot
     *                                  in the stream. In either case 
     *                                  subsequent reads will most likely
     *                                  fail.
     *
     * @throws IllegalArgumentException if the packet read has a bad version.
     *                                  In this case the entire packet is
     *                                  read, but can't be parsed. Subusequent
     *                                  reads should still work, but you
     *                                  may continue to encounter bogus packet
     *                                  versions.
     *                                  
     */
    public synchronized void read(InputStream is)
	throws IOException, EOFException, StreamCorruptedException, IllegalArgumentException {

        // ReadFixed buffer
        headerBuf.rewind();
        readFully(is, headerBuf);
        headerBuf.rewind();

        // At this point we know the full header has been read. Parse it.
        unmarshallHeader();

        props = null;
        payload = null;

        if (propsByteSize > 0) {
            // Packet has properties
            if (propsBuf != null && propsBuf.capacity() >= propsByteSize) {
                // Reuse buffer if it is big enough
                propsBuf.clear();
                propsBuf.limit(propsByteSize);
            } else {
                // allocate new
                propsBuf = ByteBuffer.allocate(propsByteSize);
            }
            readFully(is, propsBuf);
            propsBuf.rewind();
        } else {
            propsBuf = null;
        }

        int l = size - propsByteSize - HEADER_SIZE;

        if (l > 0) {
            payload = ByteBuffer.allocate(l);
            readFully(is, payload);
        } else {
            payload = null;
        }

        if (versionMismatch) {
            throw new IllegalArgumentException("Bad packet version number: " +
                version + ". Expecting: " + VERSION350);
        }

	return;
    }

   /**
     * Write the packet to an OutputStream. This method blocks until
     * the packet can be written. This method will also generate packet
     * sequence and timestamps if so configured.
     *
     * @param os                The OutputStream to write the packet to
     *
     * @throws IOException      if there was an error on write
     *
     * @see generateTimestamp()
     * @see generateSequenceNumber()
     *
     */
    public synchronized void write(OutputStream os)
        throws IOException {

        if (genSequenceNumber) {
            updateSequenceNumber();
        }

        if (genTimestamp) {
            updateTimestamp();
        }

        // This wil update the property buffer too
        marshallHeader();

        headerBuf.rewind();
        writeFully(os, headerBuf);
        headerBuf.rewind();

        if (propsByteSize > 0) {
            propsBuf.rewind();
            writeFully(os, propsBuf);
            propsBuf.rewind();
        }

        if (payload != null) {
            payload.rewind();
            writeFully(os, payload);
            payload.rewind();
        }

        os.flush();
    }


    /** 
     * Get all properties from the packet. 
     *
     */
    private Map getProperties() {
        if (props == null) {
            try {
                unmarshallProperties();
            } catch (IOException e) {
                throw new RuntimeException("Can't unmarshall pkt", e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("Can't unmarshall pkt", e);
            }
        }
        return props;
    }

    /**
     * Write a ByteBuffer to an OutputStream. Blocking.
     */
    private static void writeFully(OutputStream os, ByteBuffer buf)
        throws IOException {
        byte[] b = buf.array();
        int    offset = buf.arrayOffset() + buf.position();
        int    length = buf.remaining();
        os.write(b, offset, length);
    }


    /**
     * Read fully into a ByteBuffer from an InputStream. Blocking.
     */
    private static void readFully(InputStream in, ByteBuffer buf)
        throws IOException {

        byte[] b = buf.array();
        int offset = buf.arrayOffset() + buf.position();
        int length = buf.remaining();

        readFully(in, b, offset, length, true);
    }


    /**
     * Our own version of readFully(). This is identical to DataInputStream's
     * except that we handle InterruptedIOException by doing a yield, and
     * continuing trying to read. This is to handle the case where the
     * socket has an SO_TIMEOUT set.
     *
     * If retry is false we abandon the read if it times out and we haven't
     * read anything. If it is true we continue to retry the read.
     */
    private static void readFully(InputStream in, byte b[], int off, int len, 
			boolean retry)
	throws IOException, EOFException, InterruptedIOException {

        if (len < 0)
            throw new IndexOutOfBoundsException();

        //System.out.println("readFully(off=" + off + ", len=" + len);
        int n = 0;
	int count;
        while (n < len) {
	    count = 0;
            try {
                count = in.read(b, off + n, len - n);
            } catch (InterruptedIOException e) {
                // if we really have read nothing .. throw an ex
                if (!retry && n == 0 && count == 0 && e.bytesTransferred == 0) {
                    throw new InterruptedIOException("no data available");
		}

		count = e.bytesTransferred;

		Thread.currentThread().yield();
	    }
            if (count < 0) {
                throw new EOFException("Trying to read " + (len - n) +
                    " bytes. Already read " + n + " bytes.");
            }
            n += count;
        }
    }

    /**
     *
     * Check if two packets are equal.
     *
     * Two packets are considered equal if all the header fields are
     * equal, and the properties are equal. Note that this does NOT
     * compare the payload. If the payloads are of the same size they
     * will be considered equal. If you need to differentiate on payload
     * then set a property that uniquely identifies the payload (for
     * example a checksum of the payload).
     */
    public synchronized boolean equals(Object o) {
        if (this == o) return true;

        if (!(o instanceof GPacket)) {
            return false;
        }

        GPacket obj = (GPacket)o;

        // Need to ensure packet is marshalled so size and propsByteSize
        // are correctly updated.
        try {
            marshallHeader();
            obj.marshallHeader();
        } catch (IOException e) {
            throw new RuntimeException("Can't marshall pkt", e);
        }

        if (
            timestamp       != obj.timestamp ||
            sequence        != obj.sequence ||
            size            != obj.size ||
            type            != obj.type ||
            bitFlags        != obj.bitFlags ||
            propsByteSize   != obj.propsByteSize ||
            version         != obj.version ||
            magic           != obj.magic) {

            return false;
        }

        // OK, the packets look the same. Now compare properties
        if (propsByteSize > 0) {
            // Check if marshalled properties are equal
            if (!propsBuf.equals(obj.propsBuf)) {
                return false;
            }
        }

        // We don't check payload.

        return true;
    }

    public int hashCode() {
        // for now, just compare sysid
        // this really should include property and body comparison
        return (int)(timestamp + sequence + size + type + bitFlags
               + propsByteSize + version + magic);
    }

    public synchronized String toString() {

        getProperties();
        try {
            marshallHeader();
        } catch (IOException e) {
        }

        String s =
        type + ": v=" + version + ",sz=" + size + ",mg=" + magic + 
        ",ts=" + timestamp + ",sq=" + sequence + ",prop_sz=" + propsByteSize +
        ",pay_sz=" + (size - propsByteSize - HEADER_SIZE);


        return s;
    }

    public synchronized String toLongString() {

        getProperties();
        try {
            marshallHeader();
        } catch (IOException e) {
        }

        String s =

        "    Packet: " + type + "\n" +
        "   Version: " + version +       "\t\t        Size: " + size +
            "\tMagic: " + magic+"\n"+
        " Timestamp: " + timestamp +     "\tSequence: " + sequence + 
            "\t Bits: " + bitsToString() +"\n" +
        " Prop Size: " + propsByteSize + ": " +
                            (props == null ? "null" : props.toString()) + "\n" +
        "Payload Size: " + (size - propsByteSize - HEADER_SIZE)
        ;

        return s;
    }

    protected String bitsToString() {

        String s =
            (getBit(A_BIT) ? "A" : "") +
            (getBit(B_BIT) ? "B" : "") +
            (getBit(C_BIT) ? "C" : "") +
            (getBit(D_BIT) ? "D" : "") +
            (getBit(E_BIT) ? "E" : "") +
            (getBit(F_BIT) ? "F" : "") +
            (getBit(G_BIT) ? "G" : "") +
            (getBit(H_BIT) ? "H" : "") +
            (getBit(I_BIT) ? "I" : "") +
            (getBit(J_BIT) ? "J" : "") +
            (getBit(K_BIT) ? "K" : "") +
            (getBit(L_BIT) ? "L" : "") +
            (getBit(M_BIT) ? "M" : "") +
            (getBit(N_BIT) ? "N" : "") +
            (getBit(O_BIT) ? "O" : "") +
            (getBit(P_BIT) ? "P" : "") +
            (getBit(Q_BIT) ? "Q" : "") +
            (getBit(R_BIT) ? "R" : "") +
            (getBit(S_BIT) ? "S" : "") +
            (getBit(T_BIT) ? "T" : "") +
            (getBit(U_BIT) ? "U" : "") +
            (getBit(V_BIT) ? "V" : "") +
            (getBit(W_BIT) ? "W" : "") +
            (getBit(X_BIT) ? "X" : "") +
            (getBit(Y_BIT) ? "Y" : "") +
            (getBit(Z_BIT) ? "Z" : "") +
            (getBit(a_BIT) ? "a" : "") +
            (getBit(b_BIT) ? "b" : "") +
            (getBit(c_BIT) ? "c" : "") +
            (getBit(d_BIT) ? "d" : "") +
            (getBit(e_BIT) ? "e" : "") +
            (getBit(f_BIT) ? "f" : "") 
            ;

        return s;
    }

    public static void main(String args[]) {

        try {

        GPacket outPkt1 = GPacket.getInstance();
        GPacket outPkt2 = GPacket.getInstance();

        outPkt1.putProp("name", "joe");
        outPkt1.putProp("seq", new Integer(1));
        outPkt1.setType((short)100);
        outPkt1.setBit(A_BIT, true);
        outPkt1.setBit(Z_BIT, true);
        outPkt1.setBit(a_BIT, true);
        outPkt1.setBit(f_BIT, true);

        ByteBuffer payload = ByteBuffer.allocate(128);
        payload.putChar('j');
        payload.putChar('o');
        payload.putChar('e');
        payload.limit(payload.position());
        payload.rewind();
        outPkt1.setPayload(payload);

        ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
        outPkt1.write(out);
        System.out.println("Out:\n" + outPkt1.toLongString());


        outPkt2.putProp("seq", new Integer(2));
        outPkt2.setType((short)104);
        outPkt2.setBit(f_BIT, true);
        outPkt2.write(out);
        System.out.println("Out:\n" + outPkt2.toLongString());


        GPacket inPkt = GPacket.getInstance();

        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());

        inPkt.read(in);
        System.out.println(" In:\n" + inPkt.toLongString());

        if (!outPkt1.equals(inPkt)) {
            throw new Exception("Packet '" + outPkt1 + "' != " +
                                "Packet '" + inPkt + "'");
        }

        inPkt.read(in);
        System.out.println(" In:\n" + inPkt.toLongString());

        if (!outPkt2.equals(inPkt)) {
            throw new Exception("Packet '" + outPkt2 + "' != " +
                                "Packet '" + inPkt + "'");
        }


        } catch (Exception e) {
            System.out.println("FAILED!");
            System.out.println("Exception: " + e);
            e.printStackTrace();

            System.exit(1);
        }
        System.out.println("PASSED");
        System.exit(1);
    }
}
