/** * $RCSfile$ * $Revision$ * $Date$ * * Copyright 2003-2007 Jive Software. * * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smack.packet; import org.jivesoftware.smack.util.StringUtils; import java.util.*; /** * Represents XMPP message packets. A message can be one of several types: * * * * For each message type, different message fields are typically used as follows: *

* * * * * * * *
 Message type
FieldNormalChatGroup ChatHeadlineXMPPError
subject SHOULDSHOULD NOTSHOULD NOTSHOULD NOTSHOULD NOT
thread OPTIONALSHOULDOPTIONALOPTIONALSHOULD NOT
body SHOULDSHOULDSHOULDSHOULDSHOULD NOT
error MUST NOTMUST NOTMUST NOTMUST NOTMUST
* * @author Matt Tucker */ public class Message extends Packet { private Type type = Type.normal; private String thread = null; private String language; private final Set subjects = new HashSet(); private final Set bodies = new HashSet(); /** * Creates a new, "normal" message. */ public Message() { } /** * Creates a new "normal" message to the specified recipient. * * @param to the recipient of the message. */ public Message(String to) { setTo(to); } /** * Creates a new message of the specified type to a recipient. * * @param to the user to send the message to. * @param type the message type. */ public Message(String to, Type type) { setTo(to); this.type = type; } /** * Returns the type of the message. If no type has been set this method will return {@link * org.jivesoftware.smack.packet.Message.Type#normal}. * * @return the type of the message. */ public Type getType() { return type; } /** * Sets the type of the message. * * @param type the type of the message. * @throws IllegalArgumentException if null is passed in as the type */ public void setType(Type type) { if (type == null) { throw new IllegalArgumentException("Type cannot be null."); } this.type = type; } /** * Returns the default subject of the message, or null if the subject has not been set. * The subject is a short description of message contents. *

* The default subject of a message is the subject that corresponds to the message's language. * (see {@link #getLanguage()}) or if no language is set to the applications default * language (see {@link Packet#getDefaultLanguage()}). * * @return the subject of the message. */ public String getSubject() { return getSubject(null); } /** * Returns the subject corresponding to the language. If the language is null, the method result * will be the same as {@link #getSubject()}. Null will be returned if the language does not have * a corresponding subject. * * @param language the language of the subject to return. * @return the subject related to the passed in language. */ public String getSubject(String language) { Subject subject = getMessageSubject(language); return subject == null ? null : subject.subject; } private Subject getMessageSubject(String language) { language = determineLanguage(language); for (Subject subject : subjects) { if (language.equals(subject.language)) { return subject; } } return null; } /** * Returns a set of all subjects in this Message, including the default message subject accessible * from {@link #getSubject()}. * * @return a collection of all subjects in this message. */ public Collection getSubjects() { return Collections.unmodifiableCollection(subjects); } /** * Sets the subject of the message. The subject is a short description of * message contents. * * @param subject the subject of the message. */ public void setSubject(String subject) { if (subject == null) { removeSubject(""); // use empty string because #removeSubject(null) is ambiguous return; } addSubject(null, subject); } /** * Adds a subject with a corresponding language. * * @param language the language of the subject being added. * @param subject the subject being added to the message. * @return the new {@link org.jivesoftware.smack.packet.Message.Subject} * @throws NullPointerException if the subject is null, a null pointer exception is thrown */ public Subject addSubject(String language, String subject) { language = determineLanguage(language); Subject messageSubject = new Subject(language, subject); subjects.add(messageSubject); return messageSubject; } /** * Removes the subject with the given language from the message. * * @param language the language of the subject which is to be removed * @return true if a subject was removed and false if it was not. */ public boolean removeSubject(String language) { language = determineLanguage(language); for (Subject subject : subjects) { if (language.equals(subject.language)) { return subjects.remove(subject); } } return false; } /** * Removes the subject from the message and returns true if the subject was removed. * * @param subject the subject being removed from the message. * @return true if the subject was successfully removed and false if it was not. */ public boolean removeSubject(Subject subject) { return subjects.remove(subject); } /** * Returns all the languages being used for the subjects, not including the default subject. * * @return the languages being used for the subjects. */ public Collection getSubjectLanguages() { Subject defaultSubject = getMessageSubject(null); List languages = new ArrayList(); for (Subject subject : subjects) { if (!subject.equals(defaultSubject)) { languages.add(subject.language); } } return Collections.unmodifiableCollection(languages); } /** * Returns the default body of the message, or null if the body has not been set. The body * is the main message contents. *

* The default body of a message is the body that corresponds to the message's language. * (see {@link #getLanguage()}) or if no language is set to the applications default * language (see {@link Packet#getDefaultLanguage()}). * * @return the body of the message. */ public String getBody() { return getBody(null); } /** * Returns the body corresponding to the language. If the language is null, the method result * will be the same as {@link #getBody()}. Null will be returned if the language does not have * a corresponding body. * * @param language the language of the body to return. * @return the body related to the passed in language. * @since 3.0.2 */ public String getBody(String language) { Body body = getMessageBody(language); return body == null ? null : body.message; } private Body getMessageBody(String language) { language = determineLanguage(language); for (Body body : bodies) { if (language.equals(body.language)) { return body; } } return null; } /** * Returns a set of all bodies in this Message, including the default message body accessible * from {@link #getBody()}. * * @return a collection of all bodies in this Message. * @since 3.0.2 */ public Collection getBodies() { return Collections.unmodifiableCollection(bodies); } /** * Sets the body of the message. The body is the main message contents. * * @param body the body of the message. */ public void setBody(String body) { if (body == null) { removeBody(""); // use empty string because #removeBody(null) is ambiguous return; } addBody(null, body); } /** * Adds a body with a corresponding language. * * @param language the language of the body being added. * @param body the body being added to the message. * @return the new {@link org.jivesoftware.smack.packet.Message.Body} * @throws NullPointerException if the body is null, a null pointer exception is thrown * @since 3.0.2 */ public Body addBody(String language, String body) { language = determineLanguage(language); Body messageBody = new Body(language, body); bodies.add(messageBody); return messageBody; } /** * Removes the body with the given language from the message. * * @param language the language of the body which is to be removed * @return true if a body was removed and false if it was not. */ public boolean removeBody(String language) { language = determineLanguage(language); for (Body body : bodies) { if (language.equals(body.language)) { return bodies.remove(body); } } return false; } /** * Removes the body from the message and returns true if the body was removed. * * @param body the body being removed from the message. * @return true if the body was successfully removed and false if it was not. * @since 3.0.2 */ public boolean removeBody(Body body) { return bodies.remove(body); } /** * Returns all the languages being used for the bodies, not including the default body. * * @return the languages being used for the bodies. * @since 3.0.2 */ public Collection getBodyLanguages() { Body defaultBody = getMessageBody(null); List languages = new ArrayList(); for (Body body : bodies) { if (!body.equals(defaultBody)) { languages.add(body.language); } } return Collections.unmodifiableCollection(languages); } /** * Returns the thread id of the message, which is a unique identifier for a sequence * of "chat" messages. If no thread id is set, null will be returned. * * @return the thread id of the message, or null if it doesn't exist. */ public String getThread() { return thread; } /** * Sets the thread id of the message, which is a unique identifier for a sequence * of "chat" messages. * * @param thread the thread id of the message. */ public void setThread(String thread) { this.thread = thread; } /** * Returns the xml:lang of this Message. * * @return the xml:lang of this Message. * @since 3.0.2 */ public String getLanguage() { return language; } /** * Sets the xml:lang of this Message. * * @param language the xml:lang of this Message. * @since 3.0.2 */ public void setLanguage(String language) { this.language = language; } private String determineLanguage(String language) { // empty string is passed by #setSubject() and #setBody() and is the same as null language = "".equals(language) ? null : language; // if given language is null check if message language is set if (language == null && this.language != null) { return this.language; } else if (language == null) { return getDefaultLanguage(); } else { return language; } } public String toXML() { StringBuilder buf = new StringBuilder(); buf.append(""); // Add the subject in the default language Subject defaultSubject = getMessageSubject(null); if (defaultSubject != null) { buf.append("").append(StringUtils.escapeForXML(defaultSubject.subject)).append(""); } // Add the subject in other languages for (Subject subject : getSubjects()) { // Skip the default language if(subject.equals(defaultSubject)) continue; buf.append(""); buf.append(StringUtils.escapeForXML(subject.subject)); buf.append(""); } // Add the body in the default language Body defaultBody = getMessageBody(null); if (defaultBody != null) { buf.append("").append(StringUtils.escapeForXML(defaultBody.message)).append(""); } // Add the bodies in other languages for (Body body : getBodies()) { // Skip the default language if(body.equals(defaultBody)) continue; buf.append(""); buf.append(StringUtils.escapeForXML(body.getMessage())); buf.append(""); } if (thread != null) { buf.append("").append(thread).append(""); } // Append the error subpacket if the message type is an error. if (type == Type.error) { XMPPError error = getError(); if (error != null) { buf.append(error.toXML()); } } // Add packet extensions, if any are defined. buf.append(getExtensionsXML()); buf.append(""); return buf.toString(); } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Message message = (Message) o; if(!super.equals(message)) { return false; } if (bodies.size() != message.bodies.size() || !bodies.containsAll(message.bodies)) { return false; } if (language != null ? !language.equals(message.language) : message.language != null) { return false; } if (subjects.size() != message.subjects.size() || !subjects.containsAll(message.subjects)) { return false; } if (thread != null ? !thread.equals(message.thread) : message.thread != null) { return false; } return type == message.type; } public int hashCode() { int result; result = (type != null ? type.hashCode() : 0); result = 31 * result + subjects.hashCode(); result = 31 * result + (thread != null ? thread.hashCode() : 0); result = 31 * result + (language != null ? language.hashCode() : 0); result = 31 * result + bodies.hashCode(); return result; } /** * Represents a message subject, its language and the content of the subject. */ public static class Subject { private String subject; private String language; private Subject(String language, String subject) { if (language == null) { throw new NullPointerException("Language cannot be null."); } if (subject == null) { throw new NullPointerException("Subject cannot be null."); } this.language = language; this.subject = subject; } /** * Returns the language of this message subject. * * @return the language of this message subject. */ public String getLanguage() { return language; } /** * Returns the subject content. * * @return the content of the subject. */ public String getSubject() { return subject; } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.language.hashCode(); result = prime * result + this.subject.hashCode(); return result; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Subject other = (Subject) obj; // simplified comparison because language and subject are always set return this.language.equals(other.language) && this.subject.equals(other.subject); } } /** * Represents a message body, its language and the content of the message. */ public static class Body { private String message; private String language; private Body(String language, String message) { if (language == null) { throw new NullPointerException("Language cannot be null."); } if (message == null) { throw new NullPointerException("Message cannot be null."); } this.language = language; this.message = message; } /** * Returns the language of this message body. * * @return the language of this message body. */ public String getLanguage() { return language; } /** * Returns the message content. * * @return the content of the message. */ public String getMessage() { return message; } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.language.hashCode(); result = prime * result + this.message.hashCode(); return result; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Body other = (Body) obj; // simplified comparison because language and message are always set return this.language.equals(other.language) && this.message.equals(other.message); } } /** * Represents the type of a message. */ public enum Type { /** * (Default) a normal text message used in email like interface. */ normal, /** * Typically short text message used in line-by-line chat interfaces. */ chat, /** * Chat message sent to a groupchat server for group chats. */ groupchat, /** * Text message to be displayed in scrolling marquee displays. */ headline, /** * indicates a messaging error. */ error; public static Type fromString(String name) { try { return Type.valueOf(name); } catch (Exception e) { return normal; } } } }