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 org.jivesoftware.smack.util.XmlStringBuilder; 021 022import java.util.*; 023 024/** 025 * Represents XMPP message packets. A message can be one of several types: 026 * 027 * <ul> 028 * <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface. 029 * <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces. 030 * <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats. 031 * <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays. 032 * <li>Message.Type.ERROR -- indicates a messaging error. 033 * </ul> 034 * 035 * For each message type, different message fields are typically used as follows: 036 * <p> 037 * <table border="1"> 038 * <tr><td> </td><td colspan="5"><b>Message type</b></td></tr> 039 * <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> 040 * <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> 041 * <tr><td><i>thread</i></td> <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr> 042 * <tr><td><i>body</i></td> <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr> 043 * <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> 044 * </table> 045 * 046 * @author Matt Tucker 047 */ 048public class Message extends Packet { 049 050 private Type type = Type.normal; 051 private String thread = null; 052 private String language; 053 054 private final Set<Subject> subjects = new HashSet<Subject>(); 055 private final Set<Body> bodies = new HashSet<Body>(); 056 057 /** 058 * Creates a new, "normal" message. 059 */ 060 public Message() { 061 } 062 063 /** 064 * Creates a new "normal" message to the specified recipient. 065 * 066 * @param to the recipient of the message. 067 */ 068 public Message(String to) { 069 setTo(to); 070 } 071 072 /** 073 * Creates a new message of the specified type to a recipient. 074 * 075 * @param to the user to send the message to. 076 * @param type the message type. 077 */ 078 public Message(String to, Type type) { 079 setTo(to); 080 081 if (type != null) { 082 this.type = type; 083 } 084 } 085 086 /** 087 * Returns the type of the message. If no type has been set this method will return {@link 088 * org.jivesoftware.smack.packet.Message.Type#normal}. 089 * 090 * @return the type of the message. 091 */ 092 public Type getType() { 093 return type; 094 } 095 096 /** 097 * Sets the type of the message. 098 * 099 * @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 @Override 408 public XmlStringBuilder toXML() { 409 XmlStringBuilder buf = new XmlStringBuilder(); 410 buf.halfOpenElement("message"); 411 buf.xmlnsAttribute(getXmlns()); 412 buf.xmllangAttribute(getLanguage()); 413 addCommonAttributes(buf); 414 if (type != Type.normal) { 415 buf.attribute("type", type); 416 } 417 buf.rightAngelBracket(); 418 419 // Add the subject in the default language 420 Subject defaultSubject = getMessageSubject(null); 421 if (defaultSubject != null) { 422 buf.element("subject", defaultSubject.subject); 423 } 424 // Add the subject in other languages 425 for (Subject subject : getSubjects()) { 426 // Skip the default language 427 if(subject.equals(defaultSubject)) 428 continue; 429 buf.halfOpenElement("subject").xmllangAttribute(subject.language).rightAngelBracket(); 430 buf.escape(subject.subject); 431 buf.closeElement("subject"); 432 } 433 // Add the body in the default language 434 Body defaultBody = getMessageBody(null); 435 if (defaultBody != null) { 436 buf.element("body", defaultBody.message); 437 } 438 // Add the bodies in other languages 439 for (Body body : getBodies()) { 440 // Skip the default language 441 if(body.equals(defaultBody)) 442 continue; 443 buf.halfOpenElement("body").xmllangAttribute(body.getLanguage()).rightAngelBracket(); 444 buf.escape(body.getMessage()); 445 buf.closeElement("body"); 446 } 447 buf.optElement("thread", thread); 448 // Append the error subpacket if the message type is an error. 449 if (type == Type.error) { 450 XMPPError error = getError(); 451 if (error != null) { 452 buf.append(error.toXML()); 453 } 454 } 455 // Add packet extensions, if any are defined. 456 buf.append(getExtensionsXML()); 457 buf.closeElement("message"); 458 return buf; 459 } 460 461 462 public boolean equals(Object o) { 463 if (this == o) return true; 464 if (o == null || getClass() != o.getClass()) return false; 465 466 Message message = (Message) o; 467 468 if(!super.equals(message)) { return false; } 469 if (bodies.size() != message.bodies.size() || !bodies.containsAll(message.bodies)) { 470 return false; 471 } 472 if (language != null ? !language.equals(message.language) : message.language != null) { 473 return false; 474 } 475 if (subjects.size() != message.subjects.size() || !subjects.containsAll(message.subjects)) { 476 return false; 477 } 478 if (thread != null ? !thread.equals(message.thread) : message.thread != null) { 479 return false; 480 } 481 return type == message.type; 482 483 } 484 485 public int hashCode() { 486 int result; 487 result = (type != null ? type.hashCode() : 0); 488 result = 31 * result + subjects.hashCode(); 489 result = 31 * result + (thread != null ? thread.hashCode() : 0); 490 result = 31 * result + (language != null ? language.hashCode() : 0); 491 result = 31 * result + bodies.hashCode(); 492 return result; 493 } 494 495 /** 496 * Represents a message subject, its language and the content of the subject. 497 */ 498 public static class Subject { 499 500 private String subject; 501 private String language; 502 503 private Subject(String language, String subject) { 504 if (language == null) { 505 throw new NullPointerException("Language cannot be null."); 506 } 507 if (subject == null) { 508 throw new NullPointerException("Subject cannot be null."); 509 } 510 this.language = language; 511 this.subject = subject; 512 } 513 514 /** 515 * Returns the language of this message subject. 516 * 517 * @return the language of this message subject. 518 */ 519 public String getLanguage() { 520 return language; 521 } 522 523 /** 524 * Returns the subject content. 525 * 526 * @return the content of the subject. 527 */ 528 public String getSubject() { 529 return subject; 530 } 531 532 533 public int hashCode() { 534 final int prime = 31; 535 int result = 1; 536 result = prime * result + this.language.hashCode(); 537 result = prime * result + this.subject.hashCode(); 538 return result; 539 } 540 541 public boolean equals(Object obj) { 542 if (this == obj) { 543 return true; 544 } 545 if (obj == null) { 546 return false; 547 } 548 if (getClass() != obj.getClass()) { 549 return false; 550 } 551 Subject other = (Subject) obj; 552 // simplified comparison because language and subject are always set 553 return this.language.equals(other.language) && this.subject.equals(other.subject); 554 } 555 556 } 557 558 /** 559 * Represents a message body, its language and the content of the message. 560 */ 561 public static class Body { 562 563 private String message; 564 private String language; 565 566 private Body(String language, String message) { 567 if (language == null) { 568 throw new NullPointerException("Language cannot be null."); 569 } 570 if (message == null) { 571 throw new NullPointerException("Message cannot be null."); 572 } 573 this.language = language; 574 this.message = message; 575 } 576 577 /** 578 * Returns the language of this message body. 579 * 580 * @return the language of this message body. 581 */ 582 public String getLanguage() { 583 return language; 584 } 585 586 /** 587 * Returns the message content. 588 * 589 * @return the content of the message. 590 */ 591 public String getMessage() { 592 return message; 593 } 594 595 public int hashCode() { 596 final int prime = 31; 597 int result = 1; 598 result = prime * result + this.language.hashCode(); 599 result = prime * result + this.message.hashCode(); 600 return result; 601 } 602 603 public boolean equals(Object obj) { 604 if (this == obj) { 605 return true; 606 } 607 if (obj == null) { 608 return false; 609 } 610 if (getClass() != obj.getClass()) { 611 return false; 612 } 613 Body other = (Body) obj; 614 // simplified comparison because language and message are always set 615 return this.language.equals(other.language) && this.message.equals(other.message); 616 } 617 618 } 619 620 /** 621 * Represents the type of a message. 622 */ 623 public enum Type { 624 625 /** 626 * (Default) a normal text message used in email like interface. 627 */ 628 normal, 629 630 /** 631 * Typically short text message used in line-by-line chat interfaces. 632 */ 633 chat, 634 635 /** 636 * Chat message sent to a groupchat server for group chats. 637 */ 638 groupchat, 639 640 /** 641 * Text message to be displayed in scrolling marquee displays. 642 */ 643 headline, 644 645 /** 646 * indicates a messaging error. 647 */ 648 error; 649 650 public static Type fromString(String name) { 651 try { 652 return Type.valueOf(name); 653 } 654 catch (Exception e) { 655 return normal; 656 } 657 } 658 659 } 660}