001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smack.packet; 019 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Locale; 025import java.util.Set; 026 027import org.jivesoftware.smack.util.TypedCloneable; 028import org.jivesoftware.smack.util.XmlStringBuilder; 029 030/** 031 * Represents XMPP message packets. A message can be one of several types: 032 * 033 * <ul> 034 * <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface. 035 * <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces. 036 * <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats. 037 * <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays. 038 * <li>Message.Type.ERROR -- indicates a messaging error. 039 * </ul> 040 * 041 * For each message type, different message fields are typically used as follows: 042 * <p> 043 * <table border="1"> 044 * <tr><td> </td><td colspan="5"><b>Message type</b></td></tr> 045 * <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> 046 * <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> 047 * <tr><td><i>thread</i></td> <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr> 048 * <tr><td><i>body</i></td> <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr> 049 * <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> 050 * </table> 051 * 052 * @author Matt Tucker 053 */ 054public final class Message extends Stanza implements TypedCloneable<Message> { 055 056 public static final String ELEMENT = "message"; 057 public static final String BODY = "body"; 058 059 private Type type; 060 private String thread = null; 061 062 private final Set<Subject> subjects = new HashSet<Subject>(); 063 private final Set<Body> bodies = new HashSet<Body>(); 064 065 /** 066 * Creates a new, "normal" message. 067 */ 068 public Message() { 069 } 070 071 /** 072 * Creates a new "normal" message to the specified recipient. 073 * 074 * @param to the recipient of the message. 075 */ 076 public Message(String to) { 077 setTo(to); 078 } 079 080 /** 081 * Creates a new message of the specified type to a recipient. 082 * 083 * @param to the user to send the message to. 084 * @param type the message type. 085 */ 086 public Message(String to, Type type) { 087 this(to); 088 setType(type); 089 } 090 091 /** 092 * Creates a new message to the specified recipient and with the specified body. 093 * 094 * @param to the user to send the message to. 095 * @param body the body of the message. 096 */ 097 public Message(String to, String body) { 098 this(to); 099 setBody(body); 100 } 101 102 /** 103 * Copy constructor. 104 * <p> 105 * This does not perform a deep clone, as extension elements are shared between the new and old 106 * instance. 107 * </p> 108 * 109 * @param other 110 */ 111 public Message(Message other) { 112 super(other); 113 this.type = other.type; 114 this.thread = other.thread; 115 this.subjects.addAll(other.subjects); 116 this.bodies.addAll(other.bodies); 117 } 118 119 /** 120 * Returns the type of the message. If no type has been set this method will return {@link 121 * org.jivesoftware.smack.packet.Message.Type#normal}. 122 * 123 * @return the type of the message. 124 */ 125 public Type getType() { 126 if (type == null) { 127 return Type.normal; 128 } 129 return type; 130 } 131 132 /** 133 * Sets the type of the message. 134 * 135 * @param type the type of the message. 136 */ 137 public void setType(Type type) { 138 this.type = type; 139 } 140 141 /** 142 * Returns the default subject of the message, or null if the subject has not been set. 143 * The subject is a short description of message contents. 144 * <p> 145 * The default subject of a message is the subject that corresponds to the message's language. 146 * (see {@link #getLanguage()}) or if no language is set to the applications default 147 * language (see {@link Stanza#getDefaultLanguage()}). 148 * 149 * @return the subject of the message. 150 */ 151 public String getSubject() { 152 return getSubject(null); 153 } 154 155 /** 156 * Returns the subject corresponding to the language. If the language is null, the method result 157 * will be the same as {@link #getSubject()}. Null will be returned if the language does not have 158 * a corresponding subject. 159 * 160 * @param language the language of the subject to return. 161 * @return the subject related to the passed in language. 162 */ 163 public String getSubject(String language) { 164 Subject subject = getMessageSubject(language); 165 return subject == null ? null : subject.subject; 166 } 167 168 private Subject getMessageSubject(String language) { 169 language = determineLanguage(language); 170 for (Subject subject : subjects) { 171 if (language.equals(subject.language)) { 172 return subject; 173 } 174 } 175 return null; 176 } 177 178 /** 179 * Returns a set of all subjects in this Message, including the default message subject accessible 180 * from {@link #getSubject()}. 181 * 182 * @return a collection of all subjects in this message. 183 */ 184 public Set<Subject> getSubjects() { 185 return Collections.unmodifiableSet(subjects); 186 } 187 188 /** 189 * Sets the subject of the message. The subject is a short description of 190 * message contents. 191 * 192 * @param subject the subject of the message. 193 */ 194 public void setSubject(String subject) { 195 if (subject == null) { 196 removeSubject(""); // use empty string because #removeSubject(null) is ambiguous 197 return; 198 } 199 addSubject(null, subject); 200 } 201 202 /** 203 * Adds a subject with a corresponding language. 204 * 205 * @param language the language of the subject being added. 206 * @param subject the subject being added to the message. 207 * @return the new {@link org.jivesoftware.smack.packet.Message.Subject} 208 * @throws NullPointerException if the subject is null, a null pointer exception is thrown 209 */ 210 public Subject addSubject(String language, String subject) { 211 language = determineLanguage(language); 212 Subject messageSubject = new Subject(language, subject); 213 subjects.add(messageSubject); 214 return messageSubject; 215 } 216 217 /** 218 * Removes the subject with the given language from the message. 219 * 220 * @param language the language of the subject which is to be removed 221 * @return true if a subject was removed and false if it was not. 222 */ 223 public boolean removeSubject(String language) { 224 language = determineLanguage(language); 225 for (Subject subject : subjects) { 226 if (language.equals(subject.language)) { 227 return subjects.remove(subject); 228 } 229 } 230 return false; 231 } 232 233 /** 234 * Removes the subject from the message and returns true if the subject was removed. 235 * 236 * @param subject the subject being removed from the message. 237 * @return true if the subject was successfully removed and false if it was not. 238 */ 239 public boolean removeSubject(Subject subject) { 240 return subjects.remove(subject); 241 } 242 243 /** 244 * Returns all the languages being used for the subjects, not including the default subject. 245 * 246 * @return the languages being used for the subjects. 247 */ 248 public List<String> getSubjectLanguages() { 249 Subject defaultSubject = getMessageSubject(null); 250 List<String> languages = new ArrayList<String>(); 251 for (Subject subject : subjects) { 252 if (!subject.equals(defaultSubject)) { 253 languages.add(subject.language); 254 } 255 } 256 return Collections.unmodifiableList(languages); 257 } 258 259 /** 260 * Returns the default body of the message, or null if the body has not been set. The body 261 * is the main message contents. 262 * <p> 263 * The default body of a message is the body that corresponds to the message's language. 264 * (see {@link #getLanguage()}) or if no language is set to the applications default 265 * language (see {@link Stanza#getDefaultLanguage()}). 266 * 267 * @return the body of the message. 268 */ 269 public String getBody() { 270 return getBody(null); 271 } 272 273 /** 274 * Returns the body corresponding to the language. If the language is null, the method result 275 * will be the same as {@link #getBody()}. Null will be returned if the language does not have 276 * a corresponding body. 277 * 278 * @param language the language of the body to return. 279 * @return the body related to the passed in language. 280 * @since 3.0.2 281 */ 282 public String getBody(String language) { 283 Body body = getMessageBody(language); 284 return body == null ? null : body.message; 285 } 286 287 private Body getMessageBody(String language) { 288 language = determineLanguage(language); 289 for (Body body : bodies) { 290 if (language.equals(body.language)) { 291 return body; 292 } 293 } 294 return null; 295 } 296 297 /** 298 * Returns a set of all bodies in this Message, including the default message body accessible 299 * from {@link #getBody()}. 300 * 301 * @return a collection of all bodies in this Message. 302 * @since 3.0.2 303 */ 304 public Set<Body> getBodies() { 305 return Collections.unmodifiableSet(bodies); 306 } 307 308 /** 309 * Sets the body of the message. The body is the main message contents. 310 * 311 * @param body the body of the message. 312 */ 313 public void setBody(String body) { 314 if (body == null) { 315 removeBody(""); // use empty string because #removeBody(null) is ambiguous 316 return; 317 } 318 addBody(null, body); 319 } 320 321 /** 322 * Adds a body with a corresponding language. 323 * 324 * @param language the language of the body being added. 325 * @param body the body being added to the message. 326 * @return the new {@link org.jivesoftware.smack.packet.Message.Body} 327 * @throws NullPointerException if the body is null, a null pointer exception is thrown 328 * @since 3.0.2 329 */ 330 public Body addBody(String language, String body) { 331 language = determineLanguage(language); 332 Body messageBody = new Body(language, body); 333 bodies.add(messageBody); 334 return messageBody; 335 } 336 337 /** 338 * Removes the body with the given language from the message. 339 * 340 * @param language the language of the body which is to be removed 341 * @return true if a body was removed and false if it was not. 342 */ 343 public boolean removeBody(String language) { 344 language = determineLanguage(language); 345 for (Body body : bodies) { 346 if (language.equals(body.language)) { 347 return bodies.remove(body); 348 } 349 } 350 return false; 351 } 352 353 /** 354 * Removes the body from the message and returns true if the body was removed. 355 * 356 * @param body the body being removed from the message. 357 * @return true if the body was successfully removed and false if it was not. 358 * @since 3.0.2 359 */ 360 public boolean removeBody(Body body) { 361 return bodies.remove(body); 362 } 363 364 /** 365 * Returns all the languages being used for the bodies, not including the default body. 366 * 367 * @return the languages being used for the bodies. 368 * @since 3.0.2 369 */ 370 public List<String> getBodyLanguages() { 371 Body defaultBody = getMessageBody(null); 372 List<String> languages = new ArrayList<String>(); 373 for (Body body : bodies) { 374 if (!body.equals(defaultBody)) { 375 languages.add(body.language); 376 } 377 } 378 return Collections.unmodifiableList(languages); 379 } 380 381 /** 382 * Returns the thread id of the message, which is a unique identifier for a sequence 383 * of "chat" messages. If no thread id is set, <tt>null</tt> will be returned. 384 * 385 * @return the thread id of the message, or <tt>null</tt> if it doesn't exist. 386 */ 387 public String getThread() { 388 return thread; 389 } 390 391 /** 392 * Sets the thread id of the message, which is a unique identifier for a sequence 393 * of "chat" messages. 394 * 395 * @param thread the thread id of the message. 396 */ 397 public void setThread(String thread) { 398 this.thread = thread; 399 } 400 401 private String determineLanguage(String language) { 402 403 // empty string is passed by #setSubject() and #setBody() and is the same as null 404 language = "".equals(language) ? null : language; 405 406 // if given language is null check if message language is set 407 if (language == null && this.language != null) { 408 return this.language; 409 } 410 else if (language == null) { 411 return getDefaultLanguage(); 412 } 413 else { 414 return language; 415 } 416 417 } 418 419 @Override 420 public XmlStringBuilder toXML() { 421 XmlStringBuilder buf = new XmlStringBuilder(); 422 buf.halfOpenElement(ELEMENT); 423 addCommonAttributes(buf); 424 buf.optAttribute("type", type); 425 buf.rightAngleBracket(); 426 427 // Add the subject in the default language 428 Subject defaultSubject = getMessageSubject(null); 429 if (defaultSubject != null) { 430 buf.element("subject", defaultSubject.subject); 431 } 432 // Add the subject in other languages 433 for (Subject subject : getSubjects()) { 434 // Skip the default language 435 if(subject.equals(defaultSubject)) 436 continue; 437 buf.halfOpenElement("subject").xmllangAttribute(subject.language).rightAngleBracket(); 438 buf.escape(subject.subject); 439 buf.closeElement("subject"); 440 } 441 // Add the body in the default language 442 Body defaultBody = getMessageBody(null); 443 if (defaultBody != null) { 444 buf.element("body", defaultBody.message); 445 } 446 // Add the bodies in other languages 447 for (Body body : getBodies()) { 448 // Skip the default language 449 if(body.equals(defaultBody)) 450 continue; 451 buf.halfOpenElement(BODY).xmllangAttribute(body.getLanguage()).rightAngleBracket(); 452 buf.escape(body.getMessage()); 453 buf.closeElement(BODY); 454 } 455 buf.optElement("thread", thread); 456 // Append the error subpacket if the message type is an error. 457 if (type == Type.error) { 458 appendErrorIfExists(buf); 459 } 460 // Add packet extensions, if any are defined. 461 buf.append(getExtensionsXML()); 462 buf.closeElement(ELEMENT); 463 return buf; 464 } 465 466 /** 467 * Creates and returns a copy of this message stanza. 468 * <p> 469 * This does not perform a deep clone, as extension elements are shared between the new and old 470 * instance. 471 * </p> 472 * @return a clone of this message. 473 */ 474 @Override 475 public Message clone() { 476 return new Message(this); 477 } 478 479 /** 480 * Represents a message subject, its language and the content of the subject. 481 */ 482 public static class Subject { 483 484 private final String subject; 485 private final String language; 486 487 private Subject(String language, String subject) { 488 if (language == null) { 489 throw new NullPointerException("Language cannot be null."); 490 } 491 if (subject == null) { 492 throw new NullPointerException("Subject cannot be null."); 493 } 494 this.language = language; 495 this.subject = subject; 496 } 497 498 /** 499 * Returns the language of this message subject. 500 * 501 * @return the language of this message subject. 502 */ 503 public String getLanguage() { 504 return language; 505 } 506 507 /** 508 * Returns the subject content. 509 * 510 * @return the content of the subject. 511 */ 512 public String getSubject() { 513 return subject; 514 } 515 516 517 public int hashCode() { 518 final int prime = 31; 519 int result = 1; 520 result = prime * result + this.language.hashCode(); 521 result = prime * result + this.subject.hashCode(); 522 return result; 523 } 524 525 public boolean equals(Object obj) { 526 if (this == obj) { 527 return true; 528 } 529 if (obj == null) { 530 return false; 531 } 532 if (getClass() != obj.getClass()) { 533 return false; 534 } 535 Subject other = (Subject) obj; 536 // simplified comparison because language and subject are always set 537 return this.language.equals(other.language) && this.subject.equals(other.subject); 538 } 539 540 } 541 542 /** 543 * Represents a message body, its language and the content of the message. 544 */ 545 public static class Body { 546 547 private final String message; 548 private final String language; 549 550 private Body(String language, String message) { 551 if (language == null) { 552 throw new NullPointerException("Language cannot be null."); 553 } 554 if (message == null) { 555 throw new NullPointerException("Message cannot be null."); 556 } 557 this.language = language; 558 this.message = message; 559 } 560 561 /** 562 * Returns the language of this message body. 563 * 564 * @return the language of this message body. 565 */ 566 public String getLanguage() { 567 return language; 568 } 569 570 /** 571 * Returns the message content. 572 * 573 * @return the content of the message. 574 */ 575 public String getMessage() { 576 return message; 577 } 578 579 public int hashCode() { 580 final int prime = 31; 581 int result = 1; 582 result = prime * result + this.language.hashCode(); 583 result = prime * result + this.message.hashCode(); 584 return result; 585 } 586 587 public boolean equals(Object obj) { 588 if (this == obj) { 589 return true; 590 } 591 if (obj == null) { 592 return false; 593 } 594 if (getClass() != obj.getClass()) { 595 return false; 596 } 597 Body other = (Body) obj; 598 // simplified comparison because language and message are always set 599 return this.language.equals(other.language) && this.message.equals(other.message); 600 } 601 602 } 603 604 /** 605 * Represents the type of a message. 606 */ 607 public enum Type { 608 609 /** 610 * (Default) a normal text message used in email like interface. 611 */ 612 normal, 613 614 /** 615 * Typically short text message used in line-by-line chat interfaces. 616 */ 617 chat, 618 619 /** 620 * Chat message sent to a groupchat server for group chats. 621 */ 622 groupchat, 623 624 /** 625 * Text message to be displayed in scrolling marquee displays. 626 */ 627 headline, 628 629 /** 630 * indicates a messaging error. 631 */ 632 error; 633 634 /** 635 * Converts a String into the corresponding types. Valid String values that can be converted 636 * to types are: "normal", "chat", "groupchat", "headline" and "error". 637 * 638 * @param string the String value to covert. 639 * @return the corresponding Type. 640 * @throws IllegalArgumentException when not able to parse the string parameter 641 * @throws NullPointerException if the string is null 642 */ 643 public static Type fromString(String string) { 644 return Type.valueOf(string.toLowerCase(Locale.US)); 645 } 646 647 } 648}