Message.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smack.packet;

  18. import java.util.Locale;

  19. import javax.xml.namespace.QName;

  20. import org.jivesoftware.smack.XMPPConnection;
  21. import org.jivesoftware.smack.util.EqualsUtil;
  22. import org.jivesoftware.smack.util.HashCode;
  23. import org.jivesoftware.smack.util.Objects;
  24. import org.jivesoftware.smack.util.StringUtils;
  25. import org.jivesoftware.smack.util.XmlStringBuilder;

  26. /**
  27.  * Represents XMPP message packets. A message can be one of several types:
  28.  *
  29.  * <ul>
  30.  *      <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface.
  31.  *      <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces.
  32.  *      <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats.
  33.  *      <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays.
  34.  *      <li>Message.Type.ERROR -- indicates a messaging error.
  35.  * </ul>
  36.  *
  37.  * For each message type, different message fields are typically used as follows:
  38.  * <table border="1">
  39.  * <caption>Message Types</caption>
  40.  * <tr><td>&nbsp;</td><td colspan="5"><b>Message type</b></td></tr>
  41.  * <tr><td><i>Field</i></td><td><b>Normal</b></td><td><b>Chat</b></td><td><b>Group Chat</b></td><td><b>Headline</b></td><td><b>XMPPError</b></td></tr>
  42.  * <tr><td><i>subject</i></td> <td>SHOULD</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td></tr>
  43.  * <tr><td><i>thread</i></td>  <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr>
  44.  * <tr><td><i>body</i></td>    <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr>
  45.  * <tr><td><i>error</i></td>   <td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST</td></tr>
  46.  * </table>
  47.  *
  48.  * @author Matt Tucker
  49.  */
  50. public final class Message extends MessageOrPresence<MessageBuilder>
  51.                 implements MessageView {

  52.     public static final String ELEMENT = "message";
  53.     public static final String BODY = "body";

  54.     private final Type type;

  55.     Message(MessageBuilder messageBuilder) {
  56.         super(messageBuilder);
  57.         type = messageBuilder.type;
  58.     }

  59.     /**
  60.      * Copy constructor.
  61.      * <p>
  62.      * This does not perform a deep clone, as extension elements are shared between the new and old
  63.      * instance.
  64.      * </p>
  65.      *
  66.      * @param other TODO javadoc me please
  67.      */
  68.     public Message(Message other) {
  69.         super(other);
  70.         this.type = other.type;
  71.     }

  72.     @Override
  73.     public Type getType() {
  74.         if (type == null) {
  75.             return Type.normal;
  76.         }
  77.         return type;
  78.     }

  79.     @Override
  80.     public String getElementName() {
  81.         return ELEMENT;
  82.     }

  83.     @Override
  84.     public MessageBuilder asBuilder() {
  85.         return StanzaBuilder.buildMessageFrom(this, getStanzaId());
  86.     }

  87.     @Override
  88.     public MessageBuilder asBuilder(String id) {
  89.         return StanzaBuilder.buildMessageFrom(this, id);
  90.     }

  91.     @Override
  92.     public MessageBuilder asBuilder(XMPPConnection connection) {
  93.         return connection.getStanzaFactory().buildMessageStanzaFrom(this);
  94.     }

  95.     @Override
  96.     public String toString() {
  97.         StringBuilder sb = new StringBuilder();
  98.         sb.append("Message Stanza [");
  99.         logCommonAttributes(sb);
  100.         if (type != null) {
  101.             sb.append("type=").append(type).append(',');
  102.         }
  103.         sb.append(']');
  104.         return sb.toString();
  105.     }

  106.     @Override
  107.     public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) {
  108.         XmlStringBuilder buf = new XmlStringBuilder(this, enclosingXmlEnvironment);
  109.         addCommonAttributes(buf);
  110.         buf.optAttribute("type", type);
  111.         buf.rightAngleBracket();

  112.         // Append the error subpacket if the message type is an error.
  113.         if (type == Type.error) {
  114.             appendErrorIfExists(buf);
  115.         }

  116.         // Add extension elements, if any are defined.
  117.         buf.append(getExtensions());

  118.         buf.closeElement(ELEMENT);
  119.         return buf;
  120.     }

  121.     /**
  122.      * Represents a message subject, its language and the content of the subject.
  123.      */
  124.     public static final class Subject implements ExtensionElement {

  125.         public static final String ELEMENT = "subject";
  126.         public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE;

  127.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  128.         private final String subject;
  129.         private final String language;

  130.         public Subject(String language, String subject) {
  131.             if (subject == null) {
  132.                 throw new NullPointerException("Subject cannot be null.");
  133.             }
  134.             this.language = language;
  135.             this.subject = subject;
  136.         }

  137.         @Override
  138.         public String getLanguage() {
  139.             return language;
  140.         }

  141.         /**
  142.          * Returns the subject content.
  143.          *
  144.          * @return the content of the subject.
  145.          */
  146.         public String getSubject() {
  147.             return subject;
  148.         }

  149.         private final HashCode.Cache hashCodeCache = new HashCode.Cache();

  150.         @Override
  151.         public int hashCode() {
  152.             return hashCodeCache.getHashCode(c ->
  153.                 c.append(language)
  154.                  .append(subject)
  155.             );
  156.         }

  157.         @Override
  158.         public boolean equals(Object obj) {
  159.             return EqualsUtil.equals(this, obj, (e, o) ->
  160.                 e.append(language, o.language)
  161.                  .append(subject, o.subject)
  162.             );
  163.         }

  164.         @Override
  165.         public String getElementName() {
  166.             return ELEMENT;
  167.         }

  168.         @Override
  169.         public String getNamespace() {
  170.             return NAMESPACE;
  171.         }

  172.         @Override
  173.         public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
  174.             XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
  175.             xml.rightAngleBracket();
  176.             xml.escape(subject);
  177.             xml.closeElement(getElementName());
  178.             return xml;
  179.         }

  180.     }

  181.     /**
  182.      * Represents a message body, its language and the content of the message.
  183.      */
  184.     public static final class Body implements ExtensionElement {

  185.         public static final String ELEMENT = "body";
  186.         public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE;
  187.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  188.         enum BodyElementNamespace {
  189.             client(StreamOpen.CLIENT_NAMESPACE),
  190.             server(StreamOpen.SERVER_NAMESPACE),
  191.             ;

  192.             private final String xmlNamespace;

  193.             BodyElementNamespace(String xmlNamespace) {
  194.                 this.xmlNamespace = xmlNamespace;
  195.             }

  196.             public String getNamespace() {
  197.                 return xmlNamespace;
  198.             }
  199.         }

  200.         private final String message;
  201.         private final String language;
  202.         private final BodyElementNamespace namespace;

  203.         public Body(String language, String message) {
  204.             this(language, message, BodyElementNamespace.client);
  205.         }

  206.         public Body(String language, String message, BodyElementNamespace namespace) {
  207.             if (message == null) {
  208.                 throw new NullPointerException("Message cannot be null.");
  209.             }
  210.             this.language = language;
  211.             this.message = message;
  212.             this.namespace = Objects.requireNonNull(namespace);
  213.         }

  214.         @Override
  215.         public String getLanguage() {
  216.             return language;
  217.         }

  218.         /**
  219.          * Returns the message content.
  220.          *
  221.          * @return the content of the message.
  222.          */
  223.         public String getMessage() {
  224.             return message;
  225.         }

  226.         private final HashCode.Cache hashCodeCache = new HashCode.Cache();

  227.         @Override
  228.         public int hashCode() {
  229.             return hashCodeCache.getHashCode(c ->
  230.                 c.append(language)
  231.                 .append(message)
  232.             );
  233.         }

  234.         @Override
  235.         public boolean equals(Object obj) {
  236.             return EqualsUtil.equals(this, obj, (e, o) ->
  237.                 e.append(language, o.language)
  238.                  .append(message, o.message)
  239.             );
  240.         }

  241.         @Override
  242.         public String getElementName() {
  243.             return ELEMENT;
  244.         }

  245.         @Override
  246.         public String getNamespace() {
  247.             return namespace.xmlNamespace;
  248.         }

  249.         @Override
  250.         public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) {
  251.             XmlStringBuilder xml = new XmlStringBuilder(this, enclosingXmlEnvironment);
  252.             xml.rightAngleBracket();
  253.             xml.text(message);
  254.             xml.closeElement(getElementName());
  255.             return xml;
  256.         }

  257.     }

  258.     @SuppressWarnings("JavaLangClash")
  259.     public static class Thread implements ExtensionElement {
  260.         public static final String ELEMENT = "thread";
  261.         public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE;
  262.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  263.         public static final String PARENT_ATTRIBUTE_NAME = "parent";

  264.         private final String thread;
  265.         private final String parent;

  266.         public Thread(String thread) {
  267.             this(thread, null);
  268.         }

  269.         public Thread(String thread, String parent) {
  270.             this.thread = StringUtils.requireNotNullNorEmpty(thread, "thread must not be null nor empty");
  271.             this.parent = StringUtils.requireNullOrNotEmpty(parent, "parent must be null or not empty");
  272.         }

  273.         public String getThread() {
  274.             return thread;
  275.         }

  276.         public String getParent() {
  277.             return parent;
  278.         }

  279.         @Override
  280.         public String getElementName() {
  281.             return ELEMENT;
  282.         }

  283.         @Override
  284.         public String getNamespace() {
  285.             return NAMESPACE;
  286.         }

  287.         @Override
  288.         public QName getQName() {
  289.             return QNAME;
  290.         }

  291.         @Override
  292.         public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
  293.             XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
  294.             xml.optAttribute(PARENT_ATTRIBUTE_NAME, parent);
  295.             xml.rightAngleBracket();
  296.             xml.escape(thread);
  297.             xml.closeElement(this);
  298.             return xml;
  299.         }
  300.     }

  301.     /**
  302.      * Represents the type of a message.
  303.      */
  304.     public enum Type {

  305.         /**
  306.          * (Default) a normal text message used in email like interface.
  307.          */
  308.         normal,

  309.         /**
  310.          * Typically short text message used in line-by-line chat interfaces.
  311.          */
  312.         chat,

  313.         /**
  314.          * Chat message sent to a groupchat server for group chats.
  315.          */
  316.         groupchat,

  317.         /**
  318.          * Text message to be displayed in scrolling marquee displays.
  319.          */
  320.         headline,

  321.         /**
  322.          * indicates a messaging error.
  323.          */
  324.         error;

  325.         /**
  326.          * Converts a String into the corresponding types. Valid String values that can be converted
  327.          * to types are: "normal", "chat", "groupchat", "headline" and "error".
  328.          *
  329.          * @param string the String value to covert.
  330.          * @return the corresponding Type.
  331.          * @throws IllegalArgumentException when not able to parse the string parameter
  332.          * @throws NullPointerException if the string is null
  333.          */
  334.         public static Type fromString(String string) {
  335.             return Type.valueOf(string.toLowerCase(Locale.US));
  336.         }

  337.     }
  338. }