/* JFox, the OpenSource J2EE Application Server
 *
 * Copyright (C) 2002 huihoo.org
 * Distributable under GNU LGPL license
 * See the GNU Lesser General Public License for more details.
 */

package org.huihoo.jfox.jms;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Map;
import java.util.HashMap;
import java.util.Vector;
import javax.jms.Message;
import javax.jms.JMSException;
import javax.jms.Destination;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotWriteableException;

/**
 * the super class of all type message
 * 
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public abstract class AbstractMessage implements Message, Serializable, Cloneable {

  /**
   * The JMSType header field contains a message type identifier supplied by a
   * client when a message is sent.
   * Some JMS providers use a message repository that contains the definitions of
   * messages sent by applications. The type header field may reference a message?s
   * definition in the provider?s repository.
   */
  protected String type = "Default";

  /**
   * If the JMS provider accepts this hint, these messages must have the message ID set to null;
   * if the provider ignores the hint, the message ID must be set to its normal unique value.
   * All message ID values must start with the ?ID:? prefix.
   */
  protected String messageId = null;

  /**
   * The JMSTimestamp header field contains the time a message was handed off to
   * a provider to be sent. It is not the time the message was actually transmitted
   * because the actual send may occur later due to transactions or other client side
   * queueing of messages.
   * When a message is sent, JMSTimestamp is ignored. When the send method
   * returns, the field contains a a time value somewhere in the interval between
   * the call and the return.
   */
  protected long timeStamp = 0L;

  /**
   * A client can use the JMSCorrelationID header field to link one message with
   * another. A typical use is to link a response message with its request message.
   * JMSCorrelationID can hold one of the following:
   * ? A provider-specific message ID
   * ? An application-specific String (Application-specified values must not start with the ?ID:? prefix;)
   * ? A provider-native byte[] value
   */
  protected String correlationId;

  /**
   * The JMSReplyTo header field contains a Destination supplied by a client when a
   * message is sent. It is the destination where a reply to the message should be
   * sent.
   */
  protected Destination replyTo;

  /**
   * The JMSDestination header field contains the destination to which the message
   * is being sent.
   * When a message is sent, this field is ignored. After completion of the send, it
   * holds the destination object specified by the sending method.
   * When a message is received, its destination value must be equivalent to the
   * value assigned when it was sent.
   */
  protected Destination destination = null;

  /**
   * The JMSDeliveryMode header field contains the delivery mode specified when
   * the message was sent.
   * JMS supports two modes of message delivery.NON_PERSISTENT & PERSISTENT
   */
  protected int deliveryMode = Message.DEFAULT_DELIVERY_MODE;

  /**
   * If a client receives a message with the JMSRedelivered indicator set, it is likely,
   * but not guaranteed, that this message was delivered but not acknowledged in
   * the past. In general, a provider must set the JMSRedelivered message header
   */
  protected boolean redelivered;

  /**
   * When a message is sent, its expiration time is calculated as the sum of the timeto-
   * live value specified on the send method and the current GMT value. On
   * return from the send method, the message?s JMSExpiration header field
   * contains this value. When a message is received its JMSExpiration header field
   * contains this same value.
   * If the time-to-live is specified as zero, expiration is set to zero to indicate that
   * the message does not expire.
   *
   * expiration = currentTime  + timeToLive
   */
  protected long expiration = Message.DEFAULT_TIME_TO_LIVE;

  /**
   * JMS defines a ten-level priority value, with 0 as the lowest priority and 9 as the
   * highest. In addition, clients should consider priorities 0-4 as gradations of
   * normal priority and priorities 5-9 as gradations of expedited priority.
   */
  protected int priority = Message.DEFAULT_PRIORITY;

  /**
   * When a client receives a message, its properties are in read-only mode.
   * When a message is received, its header field values can be changed; however,
   * its property entries and its body are read-only.
   *
   * A consumer can modify a received message after calling either the clearBody or
   * clearProperties method to make the body or properties writable. If the consumer
   * modifies a received message, and the message is subsequently redelivered, the
   * redelivered message must be the original, unmodified message (except for
   * headers and properties modified by the JMS provider as a result of the
   * redelivery, such as the JMSRedelivered header and the JMSXDeliveryCount
   * property).
   */
  protected boolean readonly = false;

  /**
   * In addition to the header fields defined here, the Message interface contains a
   * built-in facility for supporting property values. In effect, this provides a
   * mechanism for adding optional header fields to a message.
   * Properties allow a client, via message selectors (see Section 3.8, ?Message
   * Selection?), to have a JMS provider select messages on its behalf using
   * application-specific criteria.
   * Property values can be boolean, byte, short, int, long, float, double, and String.
   * JMS reserves the ?JMSX? property name prefix for JMS defined properties.
   * The JMSX property names is case sensive
   * JMS reserves the ?JMS_<vendor_name>? property name prefix for providerspecific
   * properties. Each provider defines their own value of <vendor_name>.
   */
  protected Map properties = new HashMap();

  protected AbstractSession session = null;

  protected AbstractMessage(AbstractSession session) {
    this.session = session;
  }

  public String getJMSMessageID() throws JMSException {
    return messageId;
  }

  public void setJMSMessageID(String id) throws JMSException {
    this.messageId = id;
  }

  public long getJMSTimestamp() throws JMSException {
    return timeStamp;
  }

  public void setJMSTimestamp(long timeStamp) throws JMSException {
    this.timeStamp = timeStamp;
  }

  public byte[] getJMSCorrelationIDAsBytes() throws JMSException {
    return correlationId != null ? correlationId.getBytes() : null;
  }

  public void setJMSCorrelationIDAsBytes(byte[] correlationId) throws JMSException {
    this.correlationId = new String(correlationId);
  }

  public void setJMSCorrelationID(String correlationId) throws JMSException {
    this.correlationId = correlationId;
  }

  public String getJMSCorrelationID() throws JMSException {
    return correlationId;
  }

  public Destination getJMSReplyTo() throws JMSException {
    return replyTo;
  }

  public void setJMSReplyTo(Destination replyTo) throws JMSException {
    this.replyTo = replyTo;

  }

  public Destination getJMSDestination() throws JMSException {
    return destination;
  }

  public void setJMSDestination(Destination destination) throws JMSException {
    if(destination == null) {
      throw new JMSException("Invalid destination - null!");
    }
    this.destination = destination;
  }

  public int getJMSDeliveryMode() throws JMSException {
    return deliveryMode;
  }

  public void setJMSDeliveryMode(int deliveryMode) throws JMSException {
    this.deliveryMode = deliveryMode;

  }

  public boolean getJMSRedelivered() throws JMSException {
    return redelivered;
  }

  public void setJMSRedelivered(boolean redelivered) throws JMSException {
    this.redelivered = redelivered;
  }

  public String getJMSType() throws JMSException {
    return type;
  }

  public void setJMSType(String type) throws JMSException {
    this.type = type;
  }

  public long getJMSExpiration() throws JMSException {
    return expiration;
  }

  public void setJMSExpiration(long expiration) throws JMSException {
    if (expiration >= 0) this.expiration = expiration;
  }

  public int getJMSPriority() throws JMSException {
    return priority;
  }

  public void setJMSPriority(int priority) throws JMSException {
    if (priority >= 0 && priority <= 9) this.priority = priority;
  }

  // TODO: implements clearProperties
  public void clearProperties() throws JMSException {
    properties.clear();
    readonly = false;
  }

  public boolean propertyExists(String name) throws JMSException {
    return properties.containsKey(name);
  }

  public boolean getBooleanProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      boolean _boolean = false;
      try {
       _boolean = Boolean.valueOf(obj.toString()).booleanValue();
      }
      catch(Throwable thr){
        throw new MessageFormatException(thr.toString());
      }
      return _boolean;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public byte getByteProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      byte _byte = 0;
      try {
        _byte = Byte.valueOf(obj.toString()).byteValue();
      }
      catch(Throwable thr){
        throw new MessageFormatException(thr.toString());
      }
      return _byte;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public short getShortProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      short _short = 0;
      try {
        _short = Short.valueOf(obj.toString()).shortValue();
      }
      catch(Throwable thr){
        throw new MessageFormatException(thr.toString());
      }
      return _short;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public int getIntProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      int _int = 0;
      try {
        _int = Integer.valueOf(obj.toString()).intValue();
      }
      catch(Throwable thr){
        throw new MessageFormatException(thr.toString());
      }
      return _int;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public long getLongProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      long _long = 0;
      try {
        _long = Long.valueOf(obj.toString()).longValue();
      }
      catch(Throwable thr){
        throw new MessageFormatException(thr.toString());
      }
      return _long;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public float getFloatProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      float _float = 0;
      try {
        _float = Float.valueOf(obj.toString()).floatValue();
      }
      catch(Throwable thr){
        throw new MessageFormatException(thr.toString());
      }
      return _float;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public double getDoubleProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      double _double = 0;
      try {
        _double = Byte.valueOf(obj.toString()).doubleValue();
      }
      catch(Throwable thr){
        throw new MessageFormatException(thr.toString());
      }
      return _double;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public String getStringProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      return obj.toString();
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public Object getObjectProperty(String name) throws JMSException {
    if(this.propertyExists(name)) {
      Object obj = properties.get(name);
      return obj;
    }
    throw new JMSException("property " + name + " not exists.");
  }

  public Enumeration getPropertyNames() throws JMSException {
    return new Vector(properties.keySet()).elements();
  }

  public void setBooleanProperty(String name, boolean value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name, new Boolean(value));
  }

  public void setByteProperty(String name, byte value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name,new Byte(value));
  }

  public void setShortProperty(String name, short value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name,new Short(value));
  }

  public void setIntProperty(String name, int value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name,new Integer(value));
  }

  public void setLongProperty(String name, long value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name,new Long(value));
  }

  public void setFloatProperty(String name, float value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name,new Float(value));
  }

  public void setDoubleProperty(String name, double value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name,new Double(value));
  }

  public void setStringProperty(String name, String value) throws JMSException {
    checkWriteable();
    if(name == null || name.equals("")) throw new IllegalArgumentException("Invalid property name: " + name);
    properties.put(name,value);
  }


  /**
   * The setObjectProperty method accepts values of Boolean, Byte, Short, Integer,
   * Long, Float, Double and String. An attempt to use any other class must throw a
   * JMS MessageFormatException.
   *
   * @param name
   * @param value
   * @throws JMSException
   */
  public void setObjectProperty(String name, Object value) throws JMSException {
    checkWriteable();
    if( value != null ) {
      String  className = value.getClass().getName();
      if ((className.equals("java.lang.Boolean")) || (className.equals("java.lang.Byte"))
              || (className.equals("java.lang.Short")) || (className.equals("java.lang.Integer"))
              || (className.equals("java.lang.Long")) || (className.equals("java.lang.Float"))
              || (className.equals("java.lang.Double"))
              || (className.equals("java.lang.String"))) {
        properties.put(name,value);
      }
      else {
        throw new MessageFormatException("unsupported object type " + className);
      }
    }
    else {
    // allow object properties to be set to null
     properties.put(name,value);
    }

  }

  // TODO: implements acknowledge
  public void acknowledge() throws JMSException {
    if(session != null) session.acknowneledge();
  }

  private void checkWriteable() throws MessageNotWriteableException {
    if(readonly) throw new MessageNotWriteableException("message's properties are READ-ONLY.");
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append(super.toString());
    sb.append("\nmessageId = ");
    sb.append(messageId);
    sb.append("\ntimeStamp = ");
    sb.append(timeStamp);
    sb.append("\ncorrelationId = ");
    sb.append(correlationId);
    sb.append("\nreplyTo = ");
    sb.append(replyTo);
    sb.append("\ndestination = ");
    sb.append(destination);
    sb.append("\ndeliveryMode = ");
    sb.append(deliveryMode);
    sb.append("\nredelivered = ");
    sb.append(redelivered);
    sb.append("\ntype = ");
    sb.append(type);
    sb.append("\nexpiration = ");
    sb.append(expiration);
    sb.append("\npriority = ");
    sb.append(priority);
    sb.append("\nprops = ");
    sb.append(properties);
    sb.append("\nreadOnly = ");
    sb.append(readonly);
    return sb.toString();
  }

  

}