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.Locale; 021 022import javax.xml.namespace.QName; 023 024import org.jivesoftware.smack.XMPPConnection; 025import org.jivesoftware.smack.util.EqualsUtil; 026import org.jivesoftware.smack.util.HashCode; 027import org.jivesoftware.smack.util.Objects; 028import org.jivesoftware.smack.util.StringUtils; 029import org.jivesoftware.smack.util.XmlStringBuilder; 030 031/** 032 * Represents XMPP message packets. A message can be one of several types: 033 * 034 * <ul> 035 * <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface. 036 * <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces. 037 * <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats. 038 * <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays. 039 * <li>Message.Type.ERROR -- indicates a messaging error. 040 * </ul> 041 * 042 * For each message type, different message fields are typically used as follows: 043 * <table border="1"> 044 * <caption>Message Types</caption> 045 * <tr><td> </td><td colspan="5"><b>Message type</b></td></tr> 046 * <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> 047 * <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> 048 * <tr><td><i>thread</i></td> <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr> 049 * <tr><td><i>body</i></td> <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr> 050 * <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> 051 * </table> 052 * 053 * @author Matt Tucker 054 */ 055public final class Message extends MessageOrPresence<MessageBuilder> 056 implements MessageView { 057 058 public static final String ELEMENT = "message"; 059 public static final String BODY = "body"; 060 061 private final Type type; 062 063 Message(MessageBuilder messageBuilder) { 064 super(messageBuilder); 065 type = messageBuilder.type; 066 } 067 068 /** 069 * Copy constructor. 070 * <p> 071 * This does not perform a deep clone, as extension elements are shared between the new and old 072 * instance. 073 * </p> 074 * 075 * @param other TODO javadoc me please 076 */ 077 public Message(Message other) { 078 super(other); 079 this.type = other.type; 080 } 081 082 @Override 083 public Type getType() { 084 if (type == null) { 085 return Type.normal; 086 } 087 return type; 088 } 089 090 @Override 091 public String getElementName() { 092 return ELEMENT; 093 } 094 095 @Override 096 public MessageBuilder asBuilder() { 097 return StanzaBuilder.buildMessageFrom(this, getStanzaId()); 098 } 099 100 @Override 101 public MessageBuilder asBuilder(String id) { 102 return StanzaBuilder.buildMessageFrom(this, id); 103 } 104 105 @Override 106 public MessageBuilder asBuilder(XMPPConnection connection) { 107 return connection.getStanzaFactory().buildMessageStanzaFrom(this); 108 } 109 110 @Override 111 public String toString() { 112 StringBuilder sb = new StringBuilder(); 113 sb.append("Message Stanza ["); 114 logCommonAttributes(sb); 115 if (type != null) { 116 sb.append("type=").append(type).append(','); 117 } 118 sb.append(']'); 119 return sb.toString(); 120 } 121 122 @Override 123 public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) { 124 XmlStringBuilder buf = new XmlStringBuilder(this, enclosingXmlEnvironment); 125 addCommonAttributes(buf); 126 buf.optAttribute("type", type); 127 buf.rightAngleBracket(); 128 129 // Append the error subpacket if the message type is an error. 130 if (type == Type.error) { 131 appendErrorIfExists(buf); 132 } 133 134 // Add extension elements, if any are defined. 135 buf.append(getExtensions()); 136 137 buf.closeElement(ELEMENT); 138 return buf; 139 } 140 141 /** 142 * Represents a message subject, its language and the content of the subject. 143 */ 144 public static final class Subject implements ExtensionElement { 145 146 public static final String ELEMENT = "subject"; 147 public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE; 148 149 public static final QName QNAME = new QName(NAMESPACE, ELEMENT); 150 151 private final String subject; 152 private final String language; 153 154 public Subject(String language, String subject) { 155 if (subject == null) { 156 throw new NullPointerException("Subject cannot be null."); 157 } 158 this.language = language; 159 this.subject = subject; 160 } 161 162 @Override 163 public String getLanguage() { 164 return language; 165 } 166 167 /** 168 * Returns the subject content. 169 * 170 * @return the content of the subject. 171 */ 172 public String getSubject() { 173 return subject; 174 } 175 176 private final HashCode.Cache hashCodeCache = new HashCode.Cache(); 177 178 @Override 179 public int hashCode() { 180 return hashCodeCache.getHashCode(c -> 181 c.append(language) 182 .append(subject) 183 ); 184 } 185 186 @Override 187 public boolean equals(Object obj) { 188 return EqualsUtil.equals(this, obj, (e, o) -> 189 e.append(language, o.language) 190 .append(subject, o.subject) 191 ); 192 } 193 194 @Override 195 public String getElementName() { 196 return ELEMENT; 197 } 198 199 @Override 200 public String getNamespace() { 201 return NAMESPACE; 202 } 203 204 @Override 205 public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 206 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); 207 xml.rightAngleBracket(); 208 xml.escape(subject); 209 xml.closeElement(getElementName()); 210 return xml; 211 } 212 213 } 214 215 /** 216 * Represents a message body, its language and the content of the message. 217 */ 218 public static final class Body implements ExtensionElement { 219 220 public static final String ELEMENT = "body"; 221 public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE; 222 public static final QName QNAME = new QName(NAMESPACE, ELEMENT); 223 224 enum BodyElementNamespace { 225 client(StreamOpen.CLIENT_NAMESPACE), 226 server(StreamOpen.SERVER_NAMESPACE), 227 ; 228 229 private final String xmlNamespace; 230 231 BodyElementNamespace(String xmlNamespace) { 232 this.xmlNamespace = xmlNamespace; 233 } 234 235 public String getNamespace() { 236 return xmlNamespace; 237 } 238 } 239 240 private final String message; 241 private final String language; 242 private final BodyElementNamespace namespace; 243 244 public Body(String language, String message) { 245 this(language, message, BodyElementNamespace.client); 246 } 247 248 public Body(String language, String message, BodyElementNamespace namespace) { 249 if (message == null) { 250 throw new NullPointerException("Message cannot be null."); 251 } 252 this.language = language; 253 this.message = message; 254 this.namespace = Objects.requireNonNull(namespace); 255 } 256 257 @Override 258 public String getLanguage() { 259 return language; 260 } 261 262 /** 263 * Returns the message content. 264 * 265 * @return the content of the message. 266 */ 267 public String getMessage() { 268 return message; 269 } 270 271 private final HashCode.Cache hashCodeCache = new HashCode.Cache(); 272 273 @Override 274 public int hashCode() { 275 return hashCodeCache.getHashCode(c -> 276 c.append(language) 277 .append(message) 278 ); 279 } 280 281 @Override 282 public boolean equals(Object obj) { 283 return EqualsUtil.equals(this, obj, (e, o) -> 284 e.append(language, o.language) 285 .append(message, o.message) 286 ); 287 } 288 289 @Override 290 public String getElementName() { 291 return ELEMENT; 292 } 293 294 @Override 295 public String getNamespace() { 296 return namespace.xmlNamespace; 297 } 298 299 @Override 300 public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) { 301 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingXmlEnvironment); 302 xml.rightAngleBracket(); 303 xml.text(message); 304 xml.closeElement(getElementName()); 305 return xml; 306 } 307 308 } 309 310 @SuppressWarnings("JavaLangClash") 311 public static class Thread implements ExtensionElement { 312 public static final String ELEMENT = "thread"; 313 public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE; 314 public static final QName QNAME = new QName(NAMESPACE, ELEMENT); 315 316 public static final String PARENT_ATTRIBUTE_NAME = "parent"; 317 318 private final String thread; 319 private final String parent; 320 321 public Thread(String thread) { 322 this(thread, null); 323 } 324 325 public Thread(String thread, String parent) { 326 this.thread = StringUtils.requireNotNullNorEmpty(thread, "thread must not be null nor empty"); 327 this.parent = StringUtils.requireNullOrNotEmpty(parent, "parent must be null or not empty"); 328 } 329 330 public String getThread() { 331 return thread; 332 } 333 334 public String getParent() { 335 return parent; 336 } 337 338 @Override 339 public String getElementName() { 340 return ELEMENT; 341 } 342 343 @Override 344 public String getNamespace() { 345 return NAMESPACE; 346 } 347 348 @Override 349 public QName getQName() { 350 return QNAME; 351 } 352 353 @Override 354 public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { 355 XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); 356 xml.optAttribute(PARENT_ATTRIBUTE_NAME, parent); 357 xml.rightAngleBracket(); 358 xml.escape(thread); 359 xml.closeElement(this); 360 return xml; 361 } 362 } 363 364 /** 365 * Represents the type of a message. 366 */ 367 public enum Type { 368 369 /** 370 * (Default) a normal text message used in email like interface. 371 */ 372 normal, 373 374 /** 375 * Typically short text message used in line-by-line chat interfaces. 376 */ 377 chat, 378 379 /** 380 * Chat message sent to a groupchat server for group chats. 381 */ 382 groupchat, 383 384 /** 385 * Text message to be displayed in scrolling marquee displays. 386 */ 387 headline, 388 389 /** 390 * indicates a messaging error. 391 */ 392 error; 393 394 /** 395 * Converts a String into the corresponding types. Valid String values that can be converted 396 * to types are: "normal", "chat", "groupchat", "headline" and "error". 397 * 398 * @param string the String value to covert. 399 * @return the corresponding Type. 400 * @throws IllegalArgumentException when not able to parse the string parameter 401 * @throws NullPointerException if the string is null 402 */ 403 public static Type fromString(String string) { 404 return Type.valueOf(string.toLowerCase(Locale.US)); 405 } 406 407 } 408}