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> </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