• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
Message()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      */
Message(String to)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      */
Message(String to, Type type)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      */
getType()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      */
setType(Type type)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      */
getSubject()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      */
getSubject(String language)131     public String getSubject(String language) {
132         Subject subject = getMessageSubject(language);
133         return subject == null ? null : subject.subject;
134     }
135 
getMessageSubject(String language)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      */
getSubjects()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      */
setSubject(String subject)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      */
addSubject(String language, String subject)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      */
removeSubject(String language)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      */
removeSubject(Subject subject)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      */
getSubjectLanguages()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      */
getBody()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      */
getBody(String language)250     public String getBody(String language) {
251         Body body = getMessageBody(language);
252         return body == null ? null : body.message;
253     }
254 
getMessageBody(String language)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      */
getBodies()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      */
setBody(String body)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      */
addBody(String language, String body)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      */
removeBody(String language)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      */
removeBody(Body body)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      */
getBodyLanguages()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      */
getThread()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      */
setThread(String thread)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      */
getLanguage()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      */
setLanguage(String language)385     public void setLanguage(String language) {
386         this.language = language;
387     }
388 
determineLanguage(String language)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 
toXML()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 
equals(Object o)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 
hashCode()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 
Subject(String language, String subject)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          */
getLanguage()531         public String getLanguage() {
532             return language;
533         }
534 
535         /**
536          * Returns the subject content.
537          *
538          * @return the content of the subject.
539          */
getSubject()540         public String getSubject() {
541             return subject;
542         }
543 
544 
hashCode()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 
equals(Object obj)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 
Body(String language, String message)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          */
getLanguage()594         public String getLanguage() {
595             return language;
596         }
597 
598         /**
599          * Returns the message content.
600          *
601          * @return the content of the message.
602          */
getMessage()603         public String getMessage() {
604             return message;
605         }
606 
hashCode()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 
equals(Object obj)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 
fromString(String name)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