/** 
 * 
 * Copyright 2004 Protique Ltd
 * 
 * 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.codehaus.activemq.message;

import javax.jms.JMSException;
import javax.jms.MessageNotWriteableException;
import javax.jms.TextMessage;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.UTFDataFormatException;

/**
 * A <CODE>TextMessage</CODE> object is used to send a message containing a
 * <CODE>java.lang.String</CODE>.
 * It inherits from the <CODE>Message</CODE> interface and adds a text message
 * body.
 * <p/>
 * <P>This message type can be used to transport text-based messages, including
 * those with XML content.
 * <p/>
 * <P>When a client receives a <CODE>TextMessage</CODE>, it is in read-only
 * mode. If a client attempts to write to the message at this point, a
 * <CODE>MessageNotWriteableException</CODE> is thrown. If
 * <CODE>clearBody</CODE> is
 * called, the message can now be both read from and written to.
 *
 * @see javax.jms.Session#createTextMessage()
 * @see javax.jms.Session#createTextMessage(String)
 * @see javax.jms.BytesMessage
 * @see javax.jms.MapMessage
 * @see javax.jms.Message
 * @see javax.jms.ObjectMessage
 * @see javax.jms.StreamMessage
 * @see java.lang.String
 */

public class ActiveMQTextMessage extends ActiveMQMessage implements TextMessage {
    private String text;


    public String toString() {
        String payload = null;
        try {
            payload = getText();
        }
        catch (JMSException e) {
            payload = "could not read payload: " + e.toString();
        }
        return super.toString() + ", text = " + payload;
    }

    /**
     * Return the type of Packet
     *
     * @return integer representation of the type of Packet
     */

    public int getPacketType() {
        return ACTIVEMQ_TEXT_MESSAGE;
    }

    /**
     * @return Returns a shallow copy of the message instance
     * @throws JMSException
     */

    public ActiveMQMessage shallowCopy() throws JMSException {
        ActiveMQTextMessage other = new ActiveMQTextMessage();
        this.initializeOther(other);
        other.text = this.text;
        return other;
    }

    /**
     * @return Returns a deep copy of the message - note the header fields are only shallow copied
     * @throws JMSException
     */

    public ActiveMQMessage deepCopy() throws JMSException {
        return shallowCopy();
    }

    /**
     * Clears out the message body. Clearing a message's body does not clear
     * its header values or property entries.
     * <p/>
     * <P>If this message body was read-only, calling this method leaves
     * the message body in the same state as an empty body in a newly
     * created message.
     *
     * @throws JMSException if the JMS provider fails to clear the message
     *                      body due to some internal error.
     */

    public void clearBody() throws JMSException {
        super.clearBody();
        this.text = null;
    }

    /**
     * Sets the string containing this message's data.
     *
     * @param string the <CODE>String</CODE> containing the message's data
     * @throws JMSException                 if the JMS provider fails to set the text due to
     *                                      some internal error.
     * @throws MessageNotWriteableException if the message is in read-only
     *                                      mode.
     */

    public void setText(String string) throws JMSException {
        if (super.readOnlyMessage) {
            throw new MessageNotWriteableException("The message is read only");
        }
        // lets flush the byte memory if available
        clearBody();
        this.text = string;
    }


    /**
     * Gets the string containing this message's data.  The default
     * value is null.
     *
     * @return the <CODE>String</CODE> containing the message's data
     * @throws JMSException
     */

    public String getText() throws JMSException {
        if (this.text == null) {
            try {
                super.buildBodyFromBytes();
            }
            catch (IOException ioe) {
                JMSException jmsEx = new JMSException("failed to build body from bytes");
                jmsEx.setLinkedException(ioe);
                throw jmsEx;
            }
        }
        return this.text;
    }

    /**
     * Used serialize the message body to an output stream
     *
     * @param dataOut
     * @throws IOException
     */

    protected void writeBody(DataOutput dataOut) throws IOException {
        if (text != null) {
            int strlen = text.length();
            int utflen = 0;
            char[] charr = new char[strlen];
            int c, count = 0;

            text.getChars(0, strlen, charr, 0);

            for (int i = 0; i < strlen; i++) {
                c = charr[i];
                if ((c >= 0x0001) && (c <= 0x007F)) {
                    utflen++;
                }
                else if (c > 0x07FF) {
                    utflen += 3;
                }
                else {
                    utflen += 2;
                }
            }

            byte[] bytearr = new byte[utflen + 4];
            bytearr[count++] = (byte) ((utflen >>> 24) & 0xFF);
            bytearr[count++] = (byte) ((utflen >>> 16) & 0xFF);
            bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
            bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
            for (int i = 0; i < strlen; i++) {
                c = charr[i];
                if ((c >= 0x0001) && (c <= 0x007F)) {
                    bytearr[count++] = (byte) c;
                }
                else if (c > 0x07FF) {
                    bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                    bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
                    bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
                }
                else {
                    bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
                    bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
                }
            }
            dataOut.write(bytearr);

        }
        else {
            dataOut.writeInt(-1);
        }
    }

    /**
     * Used to help build the body from an input stream
     *
     * @param dataIn
     * @throws IOException
     */

    protected void readBody(DataInput dataIn) throws IOException {
        int utflen = dataIn.readInt();
        if (utflen > -1) {
            StringBuffer str = new StringBuffer(utflen);
            byte bytearr[] = new byte[utflen];
            int c, char2, char3;
            int count = 0;

            dataIn.readFully(bytearr, 0, utflen);

            while (count < utflen) {
                c = bytearr[count] & 0xff;
                switch (c >> 4) {
                    case 0:
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                        /* 0xxxxxxx */
                        count++;
                        str.append((char) c);
                        break;
                    case 12:
                    case 13:
                        /* 110x xxxx 10xx xxxx */
                        count += 2;
                        if (count > utflen) {
                            throw new UTFDataFormatException();
                        }
                        char2 = bytearr[count - 1];
                        if ((char2 & 0xC0) != 0x80) {
                            throw new UTFDataFormatException();
                        }
                        str.append((char) (((c & 0x1F) << 6) | (char2 & 0x3F)));
                        break;
                    case 14:
                        /* 1110 xxxx 10xx xxxx 10xx xxxx */
                        count += 3;
                        if (count > utflen) {
                            throw new UTFDataFormatException();
                        }
                        char2 = bytearr[count - 2];
                        char3 = bytearr[count - 1];
                        if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) {
                            throw new UTFDataFormatException();
                        }
                        str.append((char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)));
                        break;
                    default :
                        /* 10xx xxxx, 1111 xxxx */
                        throw new UTFDataFormatException();
                }
            }
            // The number of chars produced may be less than utflen
            this.text = new String(str);
        }
    }
}
