Home | History | Annotate | Download | only in packet
      1 /**
      2  * $RCSfile$
      3  * $Revision$
      4  * $Date$
      5  *
      6  * Copyright 2003-2007 Jive Software.
      7  *
      8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      9  * you may not use this file except in compliance with the License.
     10  * You may obtain a copy of the License at
     11  *
     12  *     http://www.apache.org/licenses/LICENSE-2.0
     13  *
     14  * Unless required by applicable law or agreed to in writing, software
     15  * distributed under the License is distributed on an "AS IS" BASIS,
     16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     17  * See the License for the specific language governing permissions and
     18  * limitations under the License.
     19  */
     20 
     21 package org.jivesoftware.smack.packet;
     22 
     23 import org.jivesoftware.smack.util.StringUtils;
     24 
     25 import java.util.*;
     26 
     27 /**
     28  * Represents XMPP message packets. A message can be one of several types:
     29  *
     30  * <ul>
     31  *      <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface.
     32  *      <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces.
     33  *      <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats.
     34  *      <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays.
     35  *      <li>Message.Type.ERROR -- indicates a messaging error.
     36  * </ul>
     37  *
     38  * For each message type, different message fields are typically used as follows:
     39  * <p>
     40  * <table border="1">
     41  * <tr><td>&nbsp;</td><td colspan="5"><b>Message type</b></td></tr>
     42  * <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>
     43  * <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>
     44  * <tr><td><i>thread</i></td>  <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr>
     45  * <tr><td><i>body</i></td>    <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr>
     46  * <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>
     47  * </table>
     48  *
     49  * @author Matt Tucker
     50  */
     51 public class Message extends Packet {
     52 
     53     private Type type = Type.normal;
     54     private String thread = null;
     55     private String language;
     56 
     57     private final Set<Subject> subjects = new HashSet<Subject>();
     58     private final Set<Body> bodies = new HashSet<Body>();
     59 
     60     /**
     61      * Creates a new, "normal" message.
     62      */
     63     public Message() {
     64     }
     65 
     66     /**
     67      * Creates a new "normal" message to the specified recipient.
     68      *
     69      * @param to the recipient of the message.
     70      */
     71     public Message(String to) {
     72         setTo(to);
     73     }
     74 
     75     /**
     76      * Creates a new message of the specified type to a recipient.
     77      *
     78      * @param to the user to send the message to.
     79      * @param type the message type.
     80      */
     81     public Message(String to, Type type) {
     82         setTo(to);
     83         this.type = type;
     84     }
     85 
     86     /**
     87      * Returns the type of the message. If no type has been set this method will return {@link
     88      * org.jivesoftware.smack.packet.Message.Type#normal}.
     89      *
     90      * @return the type of the message.
     91      */
     92     public Type getType() {
     93         return type;
     94     }
     95 
     96     /**
     97      * Sets the type of the message.
     98      *
     99      * @param type the type of the message.
    100      * @throws IllegalArgumentException if null is passed in as the type
    101      */
    102     public void setType(Type type) {
    103         if (type == null) {
    104             throw new IllegalArgumentException("Type cannot be null.");
    105         }
    106         this.type = type;
    107     }
    108 
    109     /**
    110      * Returns the default subject of the message, or null if the subject has not been set.
    111      * The subject is a short description of message contents.
    112      * <p>
    113      * The default subject of a message is the subject that corresponds to the message's language.
    114      * (see {@link #getLanguage()}) or if no language is set to the applications default
    115      * language (see {@link Packet#getDefaultLanguage()}).
    116      *
    117      * @return the subject of the message.
    118      */
    119     public String getSubject() {
    120         return getSubject(null);
    121     }
    122 
    123     /**
    124      * Returns the subject corresponding to the language. If the language is null, the method result
    125      * will be the same as {@link #getSubject()}. Null will be returned if the language does not have
    126      * a corresponding subject.
    127      *
    128      * @param language the language of the subject to return.
    129      * @return the subject related to the passed in language.
    130      */
    131     public String getSubject(String language) {
    132         Subject subject = getMessageSubject(language);
    133         return subject == null ? null : subject.subject;
    134     }
    135 
    136     private Subject getMessageSubject(String language) {
    137         language = determineLanguage(language);
    138         for (Subject subject : subjects) {
    139             if (language.equals(subject.language)) {
    140                 return subject;
    141             }
    142         }
    143         return null;
    144     }
    145 
    146     /**
    147      * Returns a set of all subjects in this Message, including the default message subject accessible
    148      * from {@link #getSubject()}.
    149      *
    150      * @return a collection of all subjects in this message.
    151      */
    152     public Collection<Subject> getSubjects() {
    153         return Collections.unmodifiableCollection(subjects);
    154     }
    155 
    156     /**
    157      * Sets the subject of the message. The subject is a short description of
    158      * message contents.
    159      *
    160      * @param subject the subject of the message.
    161      */
    162     public void setSubject(String subject) {
    163         if (subject == null) {
    164             removeSubject(""); // use empty string because #removeSubject(null) is ambiguous
    165             return;
    166         }
    167         addSubject(null, subject);
    168     }
    169 
    170     /**
    171      * Adds a subject with a corresponding language.
    172      *
    173      * @param language the language of the subject being added.
    174      * @param subject the subject being added to the message.
    175      * @return the new {@link org.jivesoftware.smack.packet.Message.Subject}
    176      * @throws NullPointerException if the subject is null, a null pointer exception is thrown
    177      */
    178     public Subject addSubject(String language, String subject) {
    179         language = determineLanguage(language);
    180         Subject messageSubject = new Subject(language, subject);
    181         subjects.add(messageSubject);
    182         return messageSubject;
    183     }
    184 
    185     /**
    186      * Removes the subject with the given language from the message.
    187      *
    188      * @param language the language of the subject which is to be removed
    189      * @return true if a subject was removed and false if it was not.
    190      */
    191     public boolean removeSubject(String language) {
    192         language = determineLanguage(language);
    193         for (Subject subject : subjects) {
    194             if (language.equals(subject.language)) {
    195                 return subjects.remove(subject);
    196             }
    197         }
    198         return false;
    199     }
    200 
    201     /**
    202      * Removes the subject from the message and returns true if the subject was removed.
    203      *
    204      * @param subject the subject being removed from the message.
    205      * @return true if the subject was successfully removed and false if it was not.
    206      */
    207     public boolean removeSubject(Subject subject) {
    208         return subjects.remove(subject);
    209     }
    210 
    211     /**
    212      * Returns all the languages being used for the subjects, not including the default subject.
    213      *
    214      * @return the languages being used for the subjects.
    215      */
    216     public Collection<String> getSubjectLanguages() {
    217         Subject defaultSubject = getMessageSubject(null);
    218         List<String> languages = new ArrayList<String>();
    219         for (Subject subject : subjects) {
    220             if (!subject.equals(defaultSubject)) {
    221                 languages.add(subject.language);
    222             }
    223         }
    224         return Collections.unmodifiableCollection(languages);
    225     }
    226 
    227     /**
    228      * Returns the default body of the message, or null if the body has not been set. The body
    229      * is the main message contents.
    230      * <p>
    231      * The default body of a message is the body that corresponds to the message's language.
    232      * (see {@link #getLanguage()}) or if no language is set to the applications default
    233      * language (see {@link Packet#getDefaultLanguage()}).
    234      *
    235      * @return the body of the message.
    236      */
    237     public String getBody() {
    238         return getBody(null);
    239     }
    240 
    241     /**
    242      * Returns the body corresponding to the language. If the language is null, the method result
    243      * will be the same as {@link #getBody()}. Null will be returned if the language does not have
    244      * a corresponding body.
    245      *
    246      * @param language the language of the body to return.
    247      * @return the body related to the passed in language.
    248      * @since 3.0.2
    249      */
    250     public String getBody(String language) {
    251         Body body = getMessageBody(language);
    252         return body == null ? null : body.message;
    253     }
    254 
    255     private Body getMessageBody(String language) {
    256         language = determineLanguage(language);
    257         for (Body body : bodies) {
    258             if (language.equals(body.language)) {
    259                 return body;
    260             }
    261         }
    262         return null;
    263     }
    264 
    265     /**
    266      * Returns a set of all bodies in this Message, including the default message body accessible
    267      * from {@link #getBody()}.
    268      *
    269      * @return a collection of all bodies in this Message.
    270      * @since 3.0.2
    271      */
    272     public Collection<Body> getBodies() {
    273         return Collections.unmodifiableCollection(bodies);
    274     }
    275 
    276     /**
    277      * Sets the body of the message. The body is the main message contents.
    278      *
    279      * @param body the body of the message.
    280      */
    281     public void setBody(String body) {
    282         if (body == null) {
    283             removeBody(""); // use empty string because #removeBody(null) is ambiguous
    284             return;
    285         }
    286         addBody(null, body);
    287     }
    288 
    289     /**
    290      * Adds a body with a corresponding language.
    291      *
    292      * @param language the language of the body being added.
    293      * @param body the body being added to the message.
    294      * @return the new {@link org.jivesoftware.smack.packet.Message.Body}
    295      * @throws NullPointerException if the body is null, a null pointer exception is thrown
    296      * @since 3.0.2
    297      */
    298     public Body addBody(String language, String body) {
    299         language = determineLanguage(language);
    300         Body messageBody = new Body(language, body);
    301         bodies.add(messageBody);
    302         return messageBody;
    303     }
    304 
    305     /**
    306      * Removes the body with the given language from the message.
    307      *
    308      * @param language the language of the body which is to be removed
    309      * @return true if a body was removed and false if it was not.
    310      */
    311     public boolean removeBody(String language) {
    312         language = determineLanguage(language);
    313         for (Body body : bodies) {
    314             if (language.equals(body.language)) {
    315                 return bodies.remove(body);
    316             }
    317         }
    318         return false;
    319     }
    320 
    321     /**
    322      * Removes the body from the message and returns true if the body was removed.
    323      *
    324      * @param body the body being removed from the message.
    325      * @return true if the body was successfully removed and false if it was not.
    326      * @since 3.0.2
    327      */
    328     public boolean removeBody(Body body) {
    329         return bodies.remove(body);
    330     }
    331 
    332     /**
    333      * Returns all the languages being used for the bodies, not including the default body.
    334      *
    335      * @return the languages being used for the bodies.
    336      * @since 3.0.2
    337      */
    338     public Collection<String> getBodyLanguages() {
    339         Body defaultBody = getMessageBody(null);
    340         List<String> languages = new ArrayList<String>();
    341         for (Body body : bodies) {
    342             if (!body.equals(defaultBody)) {
    343                 languages.add(body.language);
    344             }
    345         }
    346         return Collections.unmodifiableCollection(languages);
    347     }
    348 
    349     /**
    350      * Returns the thread id of the message, which is a unique identifier for a sequence
    351      * of "chat" messages. If no thread id is set, <tt>null</tt> will be returned.
    352      *
    353      * @return the thread id of the message, or <tt>null</tt> if it doesn't exist.
    354      */
    355     public String getThread() {
    356         return thread;
    357     }
    358 
    359     /**
    360      * Sets the thread id of the message, which is a unique identifier for a sequence
    361      * of "chat" messages.
    362      *
    363      * @param thread the thread id of the message.
    364      */
    365     public void setThread(String thread) {
    366         this.thread = thread;
    367     }
    368 
    369     /**
    370      * Returns the xml:lang of this Message.
    371      *
    372      * @return the xml:lang of this Message.
    373      * @since 3.0.2
    374      */
    375     public String getLanguage() {
    376         return language;
    377     }
    378 
    379     /**
    380      * Sets the xml:lang of this Message.
    381      *
    382      * @param language the xml:lang of this Message.
    383      * @since 3.0.2
    384      */
    385     public void setLanguage(String language) {
    386         this.language = language;
    387     }
    388 
    389     private String determineLanguage(String language) {
    390 
    391         // empty string is passed by #setSubject() and #setBody() and is the same as null
    392         language = "".equals(language) ? null : language;
    393 
    394         // if given language is null check if message language is set
    395         if (language == null && this.language != null) {
    396             return this.language;
    397         }
    398         else if (language == null) {
    399             return getDefaultLanguage();
    400         }
    401         else {
    402             return language;
    403         }
    404 
    405     }
    406 
    407     public String toXML() {
    408         StringBuilder buf = new StringBuilder();
    409         buf.append("<message");
    410         if (getXmlns() != null) {
    411             buf.append(" xmlns=\"").append(getXmlns()).append("\"");
    412         }
    413         if (language != null) {
    414             buf.append(" xml:lang=\"").append(getLanguage()).append("\"");
    415         }
    416         if (getPacketID() != null) {
    417             buf.append(" id=\"").append(getPacketID()).append("\"");
    418         }
    419         if (getTo() != null) {
    420             buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
    421         }
    422         if (getFrom() != null) {
    423             buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
    424         }
    425         if (type != Type.normal) {
    426             buf.append(" type=\"").append(type).append("\"");
    427         }
    428         buf.append(">");
    429         // Add the subject in the default language
    430         Subject defaultSubject = getMessageSubject(null);
    431         if (defaultSubject != null) {
    432             buf.append("<subject>").append(StringUtils.escapeForXML(defaultSubject.subject)).append("</subject>");
    433         }
    434         // Add the subject in other languages
    435         for (Subject subject : getSubjects()) {
    436             // Skip the default language
    437             if(subject.equals(defaultSubject))
    438                 continue;
    439             buf.append("<subject xml:lang=\"").append(subject.language).append("\">");
    440             buf.append(StringUtils.escapeForXML(subject.subject));
    441             buf.append("</subject>");
    442         }
    443         // Add the body in the default language
    444         Body defaultBody = getMessageBody(null);
    445         if (defaultBody != null) {
    446             buf.append("<body>").append(StringUtils.escapeForXML(defaultBody.message)).append("</body>");
    447         }
    448         // Add the bodies in other languages
    449         for (Body body : getBodies()) {
    450             // Skip the default language
    451             if(body.equals(defaultBody))
    452                 continue;
    453             buf.append("<body xml:lang=\"").append(body.getLanguage()).append("\">");
    454             buf.append(StringUtils.escapeForXML(body.getMessage()));
    455             buf.append("</body>");
    456         }
    457         if (thread != null) {
    458             buf.append("<thread>").append(thread).append("</thread>");
    459         }
    460         // Append the error subpacket if the message type is an error.
    461         if (type == Type.error) {
    462             XMPPError error = getError();
    463             if (error != null) {
    464                 buf.append(error.toXML());
    465             }
    466         }
    467         // Add packet extensions, if any are defined.
    468         buf.append(getExtensionsXML());
    469         buf.append("</message>");
    470         return buf.toString();
    471     }
    472 
    473 
    474     public boolean equals(Object o) {
    475         if (this == o) return true;
    476         if (o == null || getClass() != o.getClass()) return false;
    477 
    478         Message message = (Message) o;
    479 
    480         if(!super.equals(message)) { return false; }
    481         if (bodies.size() != message.bodies.size() || !bodies.containsAll(message.bodies)) {
    482             return false;
    483         }
    484         if (language != null ? !language.equals(message.language) : message.language != null) {
    485             return false;
    486         }
    487         if (subjects.size() != message.subjects.size() || !subjects.containsAll(message.subjects)) {
    488             return false;
    489         }
    490         if (thread != null ? !thread.equals(message.thread) : message.thread != null) {
    491             return false;
    492         }
    493         return type == message.type;
    494 
    495     }
    496 
    497     public int hashCode() {
    498         int result;
    499         result = (type != null ? type.hashCode() : 0);
    500         result = 31 * result + subjects.hashCode();
    501         result = 31 * result + (thread != null ? thread.hashCode() : 0);
    502         result = 31 * result + (language != null ? language.hashCode() : 0);
    503         result = 31 * result + bodies.hashCode();
    504         return result;
    505     }
    506 
    507     /**
    508      * Represents a message subject, its language and the content of the subject.
    509      */
    510     public static class Subject {
    511 
    512         private String subject;
    513         private String language;
    514 
    515         private Subject(String language, String subject) {
    516             if (language == null) {
    517                 throw new NullPointerException("Language cannot be null.");
    518             }
    519             if (subject == null) {
    520                 throw new NullPointerException("Subject cannot be null.");
    521             }
    522             this.language = language;
    523             this.subject = subject;
    524         }
    525 
    526         /**
    527          * Returns the language of this message subject.
    528          *
    529          * @return the language of this message subject.
    530          */
    531         public String getLanguage() {
    532             return language;
    533         }
    534 
    535         /**
    536          * Returns the subject content.
    537          *
    538          * @return the content of the subject.
    539          */
    540         public String getSubject() {
    541             return subject;
    542         }
    543 
    544 
    545         public int hashCode() {
    546             final int prime = 31;
    547             int result = 1;
    548             result = prime * result + this.language.hashCode();
    549             result = prime * result + this.subject.hashCode();
    550             return result;
    551         }
    552 
    553         public boolean equals(Object obj) {
    554             if (this == obj) {
    555                 return true;
    556             }
    557             if (obj == null) {
    558                 return false;
    559             }
    560             if (getClass() != obj.getClass()) {
    561                 return false;
    562             }
    563             Subject other = (Subject) obj;
    564             // simplified comparison because language and subject are always set
    565             return this.language.equals(other.language) && this.subject.equals(other.subject);
    566         }
    567 
    568     }
    569 
    570     /**
    571      * Represents a message body, its language and the content of the message.
    572      */
    573     public static class Body {
    574 
    575         private String message;
    576         private String language;
    577 
    578         private Body(String language, String message) {
    579             if (language == null) {
    580                 throw new NullPointerException("Language cannot be null.");
    581             }
    582             if (message == null) {
    583                 throw new NullPointerException("Message cannot be null.");
    584             }
    585             this.language = language;
    586             this.message = message;
    587         }
    588 
    589         /**
    590          * Returns the language of this message body.
    591          *
    592          * @return the language of this message body.
    593          */
    594         public String getLanguage() {
    595             return language;
    596         }
    597 
    598         /**
    599          * Returns the message content.
    600          *
    601          * @return the content of the message.
    602          */
    603         public String getMessage() {
    604             return message;
    605         }
    606 
    607         public int hashCode() {
    608             final int prime = 31;
    609             int result = 1;
    610             result = prime * result + this.language.hashCode();
    611             result = prime * result + this.message.hashCode();
    612             return result;
    613         }
    614 
    615         public boolean equals(Object obj) {
    616             if (this == obj) {
    617                 return true;
    618             }
    619             if (obj == null) {
    620                 return false;
    621             }
    622             if (getClass() != obj.getClass()) {
    623                 return false;
    624             }
    625             Body other = (Body) obj;
    626             // simplified comparison because language and message are always set
    627             return this.language.equals(other.language) && this.message.equals(other.message);
    628         }
    629 
    630     }
    631 
    632     /**
    633      * Represents the type of a message.
    634      */
    635     public enum Type {
    636 
    637         /**
    638          * (Default) a normal text message used in email like interface.
    639          */
    640         normal,
    641 
    642         /**
    643          * Typically short text message used in line-by-line chat interfaces.
    644          */
    645         chat,
    646 
    647         /**
    648          * Chat message sent to a groupchat server for group chats.
    649          */
    650         groupchat,
    651 
    652         /**
    653          * Text message to be displayed in scrolling marquee displays.
    654          */
    655         headline,
    656 
    657         /**
    658          * indicates a messaging error.
    659          */
    660         error;
    661 
    662         public static Type fromString(String name) {
    663             try {
    664                 return Type.valueOf(name);
    665             }
    666             catch (Exception e) {
    667                 return normal;
    668             }
    669         }
    670 
    671     }
    672 }
    673