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>&nbsp;</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}