1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-09 09:09:38 +02:00

Introduce StanzaBuilder

As first step to immutable Stanza types.
This commit is contained in:
Florian Schmaus 2019-10-24 15:45:08 +02:00
parent 926c5892ad
commit 5db6191110
134 changed files with 2576 additions and 764 deletions

View file

@ -105,11 +105,13 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Session;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StanzaFactory;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
@ -402,6 +404,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private final Map<QName, IQRequestHandler> setIqRequestHandler = new HashMap<>();
private final Map<QName, IQRequestHandler> getIqRequestHandler = new HashMap<>();
private final StanzaFactory stanzaFactory;
/**
* Create a new XMPPConnection to an XMPP server.
*
@ -440,6 +444,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
listener.connectionCreated(this);
}
StanzaIdSource stanzaIdSource = configuration.constructStanzaIdSource();
stanzaFactory = new StanzaFactory(stanzaIdSource);
}
/**
@ -733,7 +740,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// eventually load the roster. And we should load the roster before we
// send the initial presence.
if (config.isSendPresence() && !resumed) {
sendStanza(new Presence(Presence.Type.available));
Presence availablePresence = getStanzaFactory()
.buildPresenceStanza()
.ofType(Presence.Type.available)
.build();
sendStanza(availablePresence);
}
}
@ -814,6 +825,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
@Override
public final StanzaFactory getStanzaFactory() {
return stanzaFactory;
}
@Override
public final void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
Objects.requireNonNull(stanza, "Stanza must not be null");
@ -893,7 +909,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
public void disconnect() {
Presence unavailablePresence = null;
if (isAuthenticated()) {
unavailablePresence = new Presence(Presence.Type.unavailable);
unavailablePresence = getStanzaFactory().buildPresenceStanza()
.ofType(Presence.Type.unavailable)
.build();
}
try {
disconnect(unavailablePresence);
@ -1416,7 +1434,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// If the IQ stanza is of type "get" or "set" with no registered IQ request handler, then answer an
// IQ of type 'error' with condition 'service-unavailable'.
final ErrorIQ errorIQ = IQ.createErrorResponse(iq, StanzaError.getBuilder(
replyCondition));
replyCondition).build());
// Use async sendStanza() here, since if sendStanza() would block, then some connections, e.g.
// XmppNioTcpConnection, would deadlock, as this operation is performed in the same thread that is
asyncGo(() -> {

View file

@ -36,6 +36,9 @@ import javax.net.ssl.X509TrustManager;
import javax.security.auth.callback.CallbackHandler;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.packet.id.StanzaIdSourceFactory;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
@ -159,6 +162,8 @@ public abstract class ConnectionConfiguration {
private final boolean compressionEnabled;
private final StanzaIdSourceFactory stanzaIdSourceFactory;
protected ConnectionConfiguration(Builder<?, ?> builder) {
authzid = builder.authzid;
username = builder.username;
@ -213,6 +218,8 @@ public abstract class ConnectionConfiguration {
compressionEnabled = builder.compressionEnabled;
stanzaIdSourceFactory = builder.stanzaIdSourceFactory;
// If the enabledSaslmechanisms are set, then they must not be empty
assert enabledSaslMechanisms == null || !enabledSaslMechanisms.isEmpty();
@ -568,6 +575,10 @@ public abstract class ConnectionConfiguration {
return Collections.unmodifiableSet(enabledSaslMechanisms);
}
StanzaIdSource constructStanzaIdSource() {
return stanzaIdSourceFactory.constructStanzaIdSource();
}
/**
* A builder for XMPP connection configurations.
* <p>
@ -612,6 +623,7 @@ public abstract class ConnectionConfiguration {
private Set<String> enabledSaslMechanisms;
private X509TrustManager customX509TrustManager;
private boolean compressionEnabled = false;
private StanzaIdSourceFactory stanzaIdSourceFactory = new StandardStanzaIdSource.Factory();
protected Builder() {
if (SmackConfiguration.DEBUG) {
@ -1134,6 +1146,17 @@ public abstract class ConnectionConfiguration {
return getThis();
}
/**
* Set the factory for stanza ID sources to use.
*
* @param stanzaIdSourceFactory the factory for stanza ID sources to use.
* @return a reference to this builder.
* @since 4.4
*/
public B setStanzaIdSourceFactory(StanzaIdSourceFactory stanzaIdSourceFactory) {
this.stanzaIdSourceFactory = Objects.requireNonNull(stanzaIdSourceFactory);
return getThis();
}
public abstract C build();

View file

@ -49,7 +49,6 @@ import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism;
import org.jivesoftware.smack.util.CloseableUtil;
import org.jivesoftware.smack.util.FileUtils;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;

View file

@ -29,6 +29,7 @@ import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaFactory;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityFullJid;
@ -178,6 +179,8 @@ public interface XMPPConnection {
*/
boolean isUsingCompression();
StanzaFactory getStanzaFactory();
/**
* Sends the specified stanza to the server.
*

View file

@ -26,7 +26,6 @@ import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@ -62,8 +61,7 @@ public final class FailureProvider extends NonzaProvider<Failure> {
case StreamOpen.SERVER_NAMESPACE:
switch (name) {
case StanzaError.ERROR:
StanzaError.Builder stanzaErrorBuilder = PacketParserUtils.parseError(parser, failureXmlEnvironment);
stanzaError = stanzaErrorBuilder.build();
stanzaError = PacketParserUtils.parseError(parser, failureXmlEnvironment);
break;
default:
LOGGER.warning("Unknown element in " + namespace + ": " + name);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014 Florian Schmaus
* Copyright © 2014-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,14 +18,18 @@ package org.jivesoftware.smack.packet;
public class EmptyResultIQ extends IQ {
EmptyResultIQ(IqBuilder iqBuilder) {
super(iqBuilder, null, null);
}
// TODO: Deprecate when stanza builder and parsing logic is ready.
public EmptyResultIQ() {
super(null, null);
setType(IQ.Type.result);
}
public EmptyResultIQ(IQ request) {
this();
initializeAsResultFor(request);
this(StanzaBuilder.buildIqResultFor(request));
}
@Override

View file

@ -27,13 +27,13 @@ public class ErrorIQ extends SimpleIQ {
* <p>
* According to RFC 6120 § 8.3.1 "4. An error stanza MUST contain an &lt;error/&gt; child element.", so the xmppError argument is mandatory.
* </p>
* @param xmppErrorBuilder the XMPPError builder (required).
* @param stanzaError the stanzaError (required).
*/
public ErrorIQ(StanzaError.Builder xmppErrorBuilder) {
public ErrorIQ(StanzaError stanzaError) {
super(ELEMENT, null);
Objects.requireNonNull(xmppErrorBuilder, "xmppErrorBuilder must not be null");
Objects.requireNonNull(stanzaError, "stanzaError must not be null");
setType(IQ.Type.error);
setError(xmppErrorBuilder);
setError(stanzaError);
}
}

View file

@ -18,7 +18,7 @@ package org.jivesoftware.smack.packet;
import javax.xml.namespace.QName;
public interface FullyQualifiedElement extends NamedElement {
public interface FullyQualifiedElement extends NamedElement, XmlLangElement {
/**
* Returns the root element XML namespace.
@ -33,11 +33,7 @@ public interface FullyQualifiedElement extends NamedElement {
return new QName(namespaceURI, localPart);
}
/**
* Returns the xml:lang of this XML element, or null if one has not been set.
*
* @return the xml:lang of this XML element, or null.
*/
@Override
default String getLanguage() {
return null;
}

View file

@ -42,7 +42,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
*
* @author Matt Tucker
*/
public abstract class IQ extends Stanza {
public abstract class IQ extends Stanza implements IqView {
// Don't name this field 'ELEMENT'. When it comes to IQ, ELEMENT is the child element!
public static final String IQ_ELEMENT = "iq";
@ -54,6 +54,7 @@ public abstract class IQ extends Stanza {
private Type type = Type.get;
// TODO: This method should be protected!
public IQ(IQ iq) {
super(iq);
type = iq.getType();
@ -62,7 +63,16 @@ public abstract class IQ extends Stanza {
this.childElementQName = iq.childElementQName;
}
// TODO: Deprecate when stanza builder is ready.
protected IQ(String childElementName, String childElementNamespace) {
this(IqBuilder.EMPTY, childElementName, childElementNamespace);
}
protected IQ(IqBuilder iqBuilder, String childElementName, String childElementNamespace) {
super(iqBuilder);
type = iqBuilder.type;
this.childElementName = childElementName;
this.childElementNamespace = childElementNamespace;
if (childElementName == null) {
@ -72,11 +82,7 @@ public abstract class IQ extends Stanza {
}
}
/**
* Returns the type of the IQ packet.
*
* @return the type of the IQ packet.
*/
@Override
public Type getType() {
return type;
}
@ -90,6 +96,7 @@ public abstract class IQ extends Stanza {
*
* @param type the type of the IQ packet.
*/
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public void setType(Type type) {
this.type = Objects.requireNonNull(type, "type must not be null");
}
@ -260,19 +267,6 @@ public abstract class IQ extends Stanza {
*/
protected abstract IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml);
protected final void initializeAsResultFor(IQ request) {
assert this != request;
if (!(request.getType() == Type.get || request.getType() == Type.set)) {
throw new IllegalArgumentException(
"IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
}
setStanzaId(request.getStanzaId());
setFrom(request.getTo());
setTo(request.getFrom());
setType(Type.result);
}
/**
* Convenience method to create a new empty {@link Type#result IQ.Type.result}
* IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
@ -311,7 +305,7 @@ public abstract class IQ extends Stanza {
* {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
* @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
*/
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Builder error) {
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError error) {
if (!request.isRequestIQ()) {
throw new IllegalArgumentException(
"IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
@ -321,35 +315,25 @@ public abstract class IQ extends Stanza {
result.setFrom(request.getTo());
result.setTo(request.getFrom());
error.setStanza(result);
return result;
}
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Condition condition) {
return createErrorResponse(request, StanzaError.getBuilder(condition));
/**
* Deprecated.
*
* @param request the request.
* @param error the error.
* @return an error IQ.
* @deprecated use {@link #createErrorResponse(IQ, StanzaError)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Builder error) {
return createErrorResponse(request, error.build());
}
/**
* Convenience method to create a new {@link Type#error IQ.Type.error} IQ
* based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
* IQ. The new stanza will be initialized with:<ul>
* <li>The sender set to the recipient of the originating IQ.
* <li>The recipient set to the sender of the originating IQ.
* <li>The type set to {@link Type#error IQ.Type.error}.
* <li>The id set to the id of the originating IQ.
* <li>The child element contained in the associated originating IQ.
* <li>The provided {@link StanzaError XMPPError}.
* </ul>
*
* @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
* @param error the error to associate with the created IQ packet.
* @throws IllegalArgumentException if the IQ stanza does not have a type of
* {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
* @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
*/
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError error) {
return createErrorResponse(request, StanzaError.getBuilder(error));
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Condition condition) {
return createErrorResponse(request, StanzaError.getBuilder(condition).build());
}
/**

View file

@ -0,0 +1,57 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.packet.IQ.Type;
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.ToStringUtil;
public final class IqBuilder extends StanzaBuilder<IqBuilder> implements IqView {
static final IqBuilder EMPTY = new IqBuilder(StandardStanzaIdSource.DEFAULT);
IQ.Type type = Type.get;
IqBuilder(StanzaIdSource stanzaIdSource) {
super(stanzaIdSource);
}
IqBuilder(String stanzaId) {
super(stanzaId);
}
@Override
protected void addStanzaSpecificAttributes(ToStringUtil.Builder builder) {
builder.addValue("type", type);
}
public IqBuilder ofType(IQ.Type type) {
this.type = Objects.requireNonNull(type);
return getThis();
}
@Override
public IqBuilder getThis() {
return this;
}
@Override
public IQ.Type getType() {
return type;
}
}

View file

@ -0,0 +1,28 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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;
public interface IqView extends StanzaView {
/**
* Returns the type of the IQ packet.
*
* @return the type of the IQ packet.
*/
IQ.Type getType();
}

View file

@ -24,6 +24,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
import org.jivesoftware.smack.util.Objects;
@ -58,7 +60,7 @@ import org.jxmpp.stringprep.XmppStringprepException;
*
* @author Matt Tucker
*/
public final class Message extends Stanza implements TypedCloneable<Message> {
public final class Message extends Stanza implements MessageView, TypedCloneable<Message> {
public static final String ELEMENT = "message";
public static final String BODY = "body";
@ -66,11 +68,12 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
private Type type;
private String thread = null;
private final Set<Subject> subjects = new HashSet<Subject>();
/**
* Creates a new, "normal" message.
* @deprecated use {@link StanzaBuilder}, preferable via {@link StanzaFactory}, instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Message() {
}
@ -78,7 +81,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* Creates a new "normal" message to the specified recipient.
*
* @param to the recipient of the message.
* @deprecated use {@link StanzaBuilder}, preferable via {@link StanzaFactory}, instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Message(Jid to) {
setTo(to);
}
@ -88,7 +94,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
*
* @param to the user to send the message to.
* @param type the message type.
* @deprecated use {@link StanzaBuilder}, preferable via {@link StanzaFactory}, instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Message(Jid to, Type type) {
this(to);
setType(type);
@ -99,7 +108,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
*
* @param to the user to send the message to.
* @param body the body of the message.
* @deprecated use {@link StanzaBuilder}, preferable via {@link StanzaFactory}, instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Message(Jid to, String body) {
this(to);
setBody(body);
@ -111,7 +123,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* @param to the user to send the message to.
* @param body the body of the message.
* @throws XmppStringprepException if 'to' is not a valid XMPP address.
* @deprecated use {@link StanzaBuilder}, preferable via {@link StanzaFactory}, instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Message(String to, String body) throws XmppStringprepException {
this(JidCreate.from(to), body);
}
@ -122,12 +137,21 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* @param to TODO javadoc me please
* @param extensionElement TODO javadoc me please
* @since 4.2
* @deprecated use {@link StanzaBuilder}, preferable via {@link StanzaFactory}, instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Message(Jid to, ExtensionElement extensionElement) {
this(to);
addExtension(extensionElement);
}
Message(MessageBuilder messageBuilder) {
super(messageBuilder);
type = messageBuilder.type;
thread = messageBuilder.thread;
}
/**
* Copy constructor.
* <p>
@ -141,15 +165,9 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
super(other);
this.type = other.type;
this.thread = other.thread;
this.subjects.addAll(other.subjects);
}
/**
* 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.
*/
@Override
public Type getType() {
if (type == null) {
return Type.normal;
@ -161,7 +179,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* Sets the type of the message.
*
* @param type the type of the message.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void setType(Type type) {
this.type = type;
}
@ -195,8 +216,9 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
private Subject getMessageSubject(String language) {
language = determineLanguage(language);
for (Subject subject : subjects) {
if (Objects.equals(language, subject.language)) {
for (Subject subject : getSubjects()) {
if (Objects.equals(language, subject.language)
|| (subject.language == null && Objects.equals(this.language, language))) {
return subject;
}
}
@ -210,7 +232,12 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* @return a collection of all subjects in this message.
*/
public Set<Subject> getSubjects() {
return Collections.unmodifiableSet(subjects);
List<Subject> subjectList = getExtensions(Subject.class);
Set<Subject> subjects = new HashSet<>(subjectList.size());
subjects.addAll(subjectList);
return subjects;
}
/**
@ -218,7 +245,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* message contents.
*
* @param subject the subject of the message.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public void setSubject(String subject) {
if (subject == null) {
removeSubject(""); // use empty string because #removeSubject(null) is ambiguous
@ -235,10 +265,20 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* @return the new {@link org.jivesoftware.smack.packet.Message.Subject}
* @throws NullPointerException if the subject is null, a null pointer exception is thrown
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public Subject addSubject(String language, String subject) {
language = determineLanguage(language);
List<Subject> currentSubjects = getExtensions(Subject.class);
for (Subject currentSubject : currentSubjects) {
if (language.equals(currentSubject.getLanguage())) {
throw new IllegalArgumentException("Subject with the language " + language + " already exists");
}
}
Subject messageSubject = new Subject(language, subject);
subjects.add(messageSubject);
addExtension(messageSubject);
return messageSubject;
}
@ -248,11 +288,13 @@ public final class Message extends Stanza implements TypedCloneable<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.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public boolean removeSubject(String language) {
language = determineLanguage(language);
for (Subject subject : subjects) {
for (Subject subject : getExtensions(Subject.class)) {
if (language.equals(subject.language)) {
return subjects.remove(subject);
return removeSubject(subject);
}
}
return false;
@ -264,8 +306,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* @param subject the subject being removed from the message.
* @return true if the subject was successfully removed and false if it was not.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public boolean removeSubject(Subject subject) {
return subjects.remove(subject);
return removeExtension(subject) != null;
}
/**
@ -276,7 +320,7 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
public List<String> getSubjectLanguages() {
Subject defaultSubject = getMessageSubject(null);
List<String> languages = new ArrayList<String>();
for (Subject subject : subjects) {
for (Subject subject : getExtensions(Subject.class)) {
if (!subject.equals(defaultSubject)) {
languages.add(subject.language);
}
@ -345,7 +389,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* @param body the body of the message.
* @see #setBody(String)
* @since 4.2
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public void setBody(CharSequence body) {
String bodyString;
if (body != null) {
@ -360,7 +407,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* Sets the body of the message. The body is the main message contents.
*
* @param body the body of the message.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public void setBody(String body) {
if (body == null) {
removeBody(""); // use empty string because #removeBody(null) is ambiguous
@ -377,7 +427,10 @@ public final class Message extends Stanza implements TypedCloneable<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
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public Body addBody(String language, String body) {
language = determineLanguage(language);
@ -393,7 +446,10 @@ public final class Message extends Stanza implements TypedCloneable<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.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public boolean removeBody(String language) {
language = determineLanguage(language);
for (Body body : getBodies()) {
@ -412,7 +468,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* @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
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public boolean removeBody(Body body) {
ExtensionElement removedElement = removeExtension(body);
return removedElement != null;
@ -450,7 +509,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
* of "chat" messages.
*
* @param thread the thread id of the message.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public void setThread(String thread) {
this.thread = thread;
}
@ -472,6 +534,10 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
return ELEMENT;
}
public MessageBuilder asBuilder() {
return StanzaBuilder.buildMessageFrom(this, getStanzaId());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -491,18 +557,6 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
buf.optAttribute("type", type);
buf.rightAngleBracket();
// Add the subject in the default language
Subject defaultSubject = getMessageSubject(null);
if (defaultSubject != null) {
buf.element("subject", defaultSubject.subject);
}
// Add the subject in other languages
for (Subject subject : getSubjects()) {
// Skip the default language
if (subject.equals(defaultSubject))
continue;
buf.append(subject);
}
buf.optElement("thread", thread);
// Append the error subpacket if the message type is an error.
if (type == Type.error) {
@ -537,10 +591,12 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
public static final String ELEMENT = "subject";
public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE;
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private final String subject;
private final String language;
private Subject(String language, String subject) {
public Subject(String language, String subject) {
if (subject == null) {
throw new NullPointerException("Subject cannot be null.");
}

View file

@ -0,0 +1,164 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.packet.Message.Body;
import org.jivesoftware.smack.packet.Message.Subject;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.ToStringUtil;
public final class MessageBuilder extends StanzaBuilder<MessageBuilder> implements MessageView {
static final MessageBuilder EMPTY = new MessageBuilder(() -> {
return null;
});
Message.Type type;
String thread;
MessageBuilder(Message message, String stanzaId) {
super(message, stanzaId);
copyFromMessage(message);
}
MessageBuilder(Message message, StanzaIdSource stanzaIdSource) {
super(message, stanzaIdSource);
copyFromMessage(message);
}
MessageBuilder(StanzaIdSource stanzaIdSource) {
super(stanzaIdSource);
}
MessageBuilder(String stanzaId) {
super(stanzaId);
}
private void copyFromMessage(Message message) {
type = message.getType();
thread = message.getThread();
}
@Override
protected void addStanzaSpecificAttributes(ToStringUtil.Builder builder) {
builder.addValue("type", type)
.addValue("thread", thread)
;
}
public MessageBuilder ofType(Message.Type type) {
this.type = type;
return getThis();
}
public MessageBuilder setThread(String thread) {
this.thread = thread;
return getThis();
}
/**
* Sets the subject of the message. The subject is a short description of
* message contents.
*
* @param subject the subject of the message.
* @return a reference to this builder.
*/
public MessageBuilder setSubject(String subject) {
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 a reference to this builder.
* @throws NullPointerException if the subject is null.
*/
public MessageBuilder addSubject(String language, String subject) {
language = StringUtils.requireNullOrNotEmpty(language, "language must be null or not empty");
for (Subject currentSubject : getExtensions(Subject.class)) {
if (StringUtils.nullSafeCharSequenceEquals(language, currentSubject.getLanguage())) {
throw new IllegalArgumentException("Subject with the language " + language + " already exists");
}
}
Subject messageSubject = new Subject(language, subject);
addExtension(messageSubject);
return this;
}
/**
* Sets the body of the message.
*
* @param body the body of the message.
* @return a reference to this builder.
* @see #setBody(String)
*/
public MessageBuilder setBody(CharSequence body) {
return setBody(body.toString());
}
/**
* Sets the body of the message. The body is the main message contents.
*
* @param body the body of the message.
* @return a reference to this builder.
*/
public MessageBuilder setBody(String body) {
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 a reference to this builder.
*/
public MessageBuilder addBody(String language, String body) {
language = StringUtils.requireNullOrNotEmpty(language, "language must be null or not empty");
for (Body currentBody : getExtensions(Body.class)) {
if (StringUtils.nullSafeCharSequenceEquals(language, currentBody.getLanguage())) {
throw new IllegalArgumentException("Bodyt with the language " + language + " already exists");
}
}
Body messageBody = new Body(language, body);
addExtension(messageBody);
return this;
}
@Override
public MessageBuilder getThis() {
return this;
}
public Message build() {
return new Message(this);
}
@Override
public Message.Type getType() {
return type;
}
}

View file

@ -0,0 +1,29 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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;
public interface MessageView extends StanzaView {
/**
* 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.
*/
Message.Type getType();
}

View file

@ -19,7 +19,8 @@ package org.jivesoftware.smack.packet;
import java.util.Locale;
import org.jivesoftware.smack.packet.id.StanzaIdUtil;
import javax.net.SocketFactory;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TypedCloneable;
@ -60,7 +61,7 @@ import org.jxmpp.jid.Jid;
*
* @author Matt Tucker
*/
public final class Presence extends Stanza implements TypedCloneable<Presence> {
public final class Presence extends Stanza implements PresenceView, TypedCloneable<Presence> {
public static final String ELEMENT = "presence";
@ -81,7 +82,10 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
* Creates a new presence update. Status, priority, and mode are left un-set.
*
* @param type the type.
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Presence(Type type) {
// Ensure that the stanza ID is set by calling super().
super();
@ -94,7 +98,10 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
* @param to the recipient.
* @param type the type.
* @since 4.2
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Presence(Jid to, Type type) {
this(type);
setTo(to);
@ -107,7 +114,10 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
* @param status a text message describing the presence update.
* @param priority the priority of this presence update.
* @param mode the mode type for this presence update.
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public Presence(Type type, String status, int priority, Mode mode) {
// Ensure that the stanza ID is set by calling super().
super();
@ -117,6 +127,14 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
setMode(mode);
}
Presence(PresenceBuilder presenceBuilder) {
super(presenceBuilder);
type = presenceBuilder.type;
status = presenceBuilder.status;
priority = presenceBuilder.priority;
mode = presenceBuilder.mode;
}
/**
* Copy constructor.
* <p>
@ -163,11 +181,7 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
return type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd);
}
/**
* Returns the type of this presence packet.
*
* @return the type of the presence packet.
*/
@Override
public Type getType() {
return type;
}
@ -176,18 +190,15 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
* Sets the type of the presence packet.
*
* @param type the type of the presence packet.
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void setType(Type type) {
this.type = Objects.requireNonNull(type, "Type cannot be null");
}
/**
* Returns the status message of the presence update, or <code>null</code> if there
* is not a status. The status is free-form text describing a user's presence
* (i.e., "gone to lunch").
*
* @return the status message.
*/
@Override
public String getStatus() {
return status;
}
@ -197,18 +208,21 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
* describing a user's presence (i.e., "gone to lunch").
*
* @param status the status message.
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void setStatus(String status) {
this.status = status;
}
/**
* Returns the priority of the presence.
*
* @return the priority.
* @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a>
*/
@Override
public int getPriority() {
return getPriorityByte();
}
@Override
public byte getPriorityByte() {
if (priority == null) {
return 0;
}
@ -221,7 +235,10 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
* @param priority the priority of the presence.
* @throws IllegalArgumentException if the priority is outside the valid range.
* @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a>
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void setPriority(int priority) {
if (priority < -128 || priority > 127) {
throw new IllegalArgumentException("Priority value " + priority +
@ -234,11 +251,7 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
this.priority = priority;
}
/**
* Returns the mode of the presence update.
*
* @return the mode.
*/
@Override
public Mode getMode() {
if (mode == null) {
return Mode.available;
@ -251,7 +264,10 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
* to be the same thing as {@link Presence.Mode#available}.
*
* @param mode the mode.
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void setMode(Mode mode) {
this.mode = mode;
}
@ -261,6 +277,10 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
return ELEMENT;
}
public PresenceBuilder asBuilder() {
return StanzaBuilder.buildPresenceFrom(this, getStanzaId());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -326,7 +346,7 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
*/
public Presence cloneWithNewId() {
Presence clone = clone();
clone.setStanzaId(StanzaIdUtil.newStanzaId());
clone.setNewStanzaId();
return clone;
}

View file

@ -0,0 +1,140 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.packet.Presence.Mode;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.ToStringUtil;
public final class PresenceBuilder extends StanzaBuilder<PresenceBuilder> implements PresenceView {
static final PresenceBuilder EMPTY = new PresenceBuilder(() -> {
return null;
});
Presence.Type type = Presence.Type.available;
String status;
Byte priority;
Presence.Mode mode;
PresenceBuilder(Presence presence, String stanzaId) {
super(presence, stanzaId);
copyFromPresence(presence);
}
PresenceBuilder(Presence presence, StanzaIdSource stanzaIdSource) {
super(presence, stanzaIdSource);
copyFromPresence(presence);
}
PresenceBuilder(StanzaIdSource stanzaIdSource) {
super(stanzaIdSource);
}
PresenceBuilder(String stanzaId) {
super(stanzaId);
}
private void copyFromPresence(Presence presence) {
type = presence.getType();
status = presence.getStatus();
priority = presence.getPriorityByte();
mode = presence.getMode();
}
@Override
protected void addStanzaSpecificAttributes(ToStringUtil.Builder builder) {
builder.addValue("type", type)
.addValue("mode", mode)
.addValue("priority", priority)
.addValue("status", status)
;
}
public PresenceBuilder ofType(Presence.Type type) {
this.type = Objects.requireNonNull(type, "Type cannot be null");
return getThis();
}
public PresenceBuilder setStatus(String status) {
this.status = status;
return getThis();
}
public PresenceBuilder setPriority(int priority) {
if (priority < -128 || priority > 127) {
throw new IllegalArgumentException("Priority value " + priority +
" is not valid. Valid range is -128 through 127.");
}
Byte priorityByte = (byte) priority;
return setPriority(priorityByte);
}
public PresenceBuilder setPriority(Byte priority) {
this.priority = priority;
return getThis();
}
public PresenceBuilder setMode(Presence.Mode mode) {
this.mode = mode;
return getThis();
}
@Override
public PresenceBuilder getThis() {
return this;
}
public Presence build() {
return new Presence(this);
}
@Override
public Presence.Type getType() {
return type;
}
@Override
public String getStatus() {
return status;
}
@Override
public int getPriority() {
return getPriorityByte();
}
@Override
public byte getPriorityByte() {
if (priority == null) {
return 0;
}
return priority;
}
@Override
public Presence.Mode getMode() {
if (mode == null) {
return Mode.available;
}
return mode;
}
}

View file

@ -0,0 +1,59 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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;
public interface PresenceView extends StanzaView {
/**
* Returns the type of this presence stanza.
*
* @return the type of the presence stanza.
*/
Presence.Type getType();
/**
* Returns the status message of the presence update, or <code>null</code> if there
* is not a status. The status is free-form text describing a user's presence
* (i.e., "gone to lunch").
*
* @return the status message.
*/
String getStatus();
/**
* Returns the priority of the presence.
*
* @return the priority.
* @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a>
*/
int getPriority();
/**
* Returns the priority of the presence.
*
* @return the priority.
* @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a>
*/
byte getPriorityByte();
/**
* Returns the mode of the presence update.
*
* @return the mode.
*/
Presence.Mode getMode();
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014 Florian Schmaus
* Copyright © 2014-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,6 +29,10 @@ public abstract class SimpleIQ extends IQ {
super(childElementName, childElementNamespace);
}
protected SimpleIQ(IqBuilder iqBuilder, String childElementName, String childElementNamespace) {
super(iqBuilder, childElementName, childElementNamespace);
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.setEmptyElement();

View file

@ -20,15 +20,19 @@ package org.jivesoftware.smack.packet;
import static org.jivesoftware.smack.util.StringUtils.requireNotNullNorEmpty;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.id.StanzaIdUtil;
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.PacketUtil;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smack.util.XmppElementUtil;
import org.jxmpp.jid.Jid;
@ -43,12 +47,16 @@ import org.jxmpp.jid.Jid;
* XMPP Stanzas are {@link Message}, {@link IQ} and {@link Presence}. Which therefore subclass this
* class. <b>If you think you need to subclass this class, then you are doing something wrong.</b>
* </p>
* <p>
* Use {@link StanzaBuilder} to construct a stanza instance. All instance mutating methods of this
* class are deprecated, although not all of them are currently marked as such, and must not be used.
* </p>
*
* @author Matt Tucker
* @author Florian Schmaus
* @see <a href="http://xmpp.org/rfcs/rfc6120.html#stanzas">RFC 6120 § 8. XML Stanzas</a>
*/
public abstract class Stanza implements TopLevelStreamElement {
public abstract class Stanza implements StanzaView, TopLevelStreamElement {
public static final String TEXT = "text";
public static final String ITEM = "item";
@ -56,12 +64,14 @@ public abstract class Stanza implements TopLevelStreamElement {
protected static final String DEFAULT_LANGUAGE =
java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US);
private final MultiMap<QName, ExtensionElement> extensionElements = new MultiMap<>();
private final MultiMap<QName, ExtensionElement> extensionElements;
// Assume that all stanzas Smack handles are in the client namespace, since Smack is an XMPP client library. We can
// change this behavior later if it is required.
private final String namespace = StreamOpen.CLIENT_NAMESPACE;
private final StanzaIdSource usedStanzaIdSource;
private String id = null;
private Jid to;
private Jid from;
@ -80,30 +90,46 @@ public abstract class Stanza implements TopLevelStreamElement {
protected String language;
protected Stanza() {
this(StanzaIdUtil.newStanzaId());
extensionElements = new MultiMap<>();
usedStanzaIdSource = null;
id = StandardStanzaIdSource.DEFAULT.getNewStanzaId();
}
protected Stanza(String stanzaId) {
setStanzaId(stanzaId);
protected Stanza(StanzaBuilder<?> stanzaBuilder) {
if (stanzaBuilder.stanzaIdSource != null) {
id = stanzaBuilder.stanzaIdSource.getNewStanzaId();
// Note that some stanza ID sources, e.g. StanzaBuilder.PresenceBuilder.EMPTY return null here. Hence we
// only check that the returned string is not empty.
assert StringUtils.isNullOrNotEmpty(id);
usedStanzaIdSource = stanzaBuilder.stanzaIdSource;
} else {
// N.B. It is ok if stanzaId here is null.
id = stanzaBuilder.stanzaId;
usedStanzaIdSource = null;
}
to = stanzaBuilder.to;
from = stanzaBuilder.from;
error = stanzaBuilder.stanzaError;
language = stanzaBuilder.language;
extensionElements = stanzaBuilder.extensionElements.clone();
}
protected Stanza(Stanza p) {
usedStanzaIdSource = p.usedStanzaIdSource;
id = p.getStanzaId();
to = p.getTo();
from = p.getFrom();
error = p.error;
// Copy extensions
for (ExtensionElement pe : p.getExtensions()) {
addExtension(pe);
}
extensionElements = p.extensionElements.clone();
}
/**
* Returns the unique ID of the stanza. The returned value could be <code>null</code>.
*
* @return the packet's unique ID or <code>null</code> if the id is not available.
*/
@Override
public String getStanzaId() {
return id;
}
@ -138,56 +164,50 @@ public abstract class Stanza implements TopLevelStreamElement {
*
* @return the stanza id.
* @since 4.2
* @deprecated use {@link #setNewStanzaId()} instead.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public String setStanzaId() {
return ensureStanzaIdSet();
}
/**
* Set a new stanza ID even if there is already one set.
*
* @return the stanza id.
* @since 4.4
*/
public String setNewStanzaId() {
return ensureStanzaIdSet(true);
}
/**
* Ensure a stanza id is set.
*
* @return the stanza id.
* @since 4.4
*/
public String ensureStanzaIdSet() {
return ensureStanzaIdSet(false);
}
/**
* Ensure that a stanza ID is set.
*
* @param forceNew force a new ID even if there is already one set.
* @return the stanza ID.
* @since 4.4
*/
private String ensureStanzaIdSet(boolean forceNew) {
if (forceNew || !hasStanzaIdSet()) {
setStanzaId(StanzaIdUtil.newStanzaId());
if (!hasStanzaIdSet()) {
setNewStanzaId();
}
return getStanzaId();
}
/**
* Returns who the stanza is being sent "to", or <code>null</code> if
* the value is not set. The XMPP protocol often makes the "to"
* attribute optional, so it does not always need to be set.<p>
* Throws an {@link IllegalArgumentException} if this stanza has no stanza ID set.
*
* @return who the stanza is being sent to, or <code>null</code> if the
* value has not been set.
* @throws IllegalArgumentException if this stanza has no stanza ID set.
* @since 4.4.
*/
public final void throwIfNoStanzaId() {
if (hasStanzaIdSet()) {
return;
}
throw new IllegalArgumentException("The stanza has no RFC stanza ID set, although one is required");
}
/**
* Ensure that a stanza ID is set.
*
* @return the stanza ID.
* @since 4.4
*/
// TODO: Remove this method once StanzaBuilder is ready.
protected String setNewStanzaId() {
if (usedStanzaIdSource != null) {
id = usedStanzaIdSource.getNewStanzaId();
}
else {
id = StandardStanzaIdSource.DEFAULT.getNewStanzaId();
}
return getStanzaId();
}
@Override
public Jid getTo() {
return to;
}
@ -198,18 +218,12 @@ public abstract class Stanza implements TopLevelStreamElement {
*
* @param to who the packet is being sent to.
*/
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public void setTo(Jid to) {
this.to = to;
}
/**
* Returns who the stanza is being sent "from" or <code>null</code> if
* the value is not set. The XMPP protocol often makes the "from"
* attribute optional, so it does not always need to be set.<p>
*
* @return who the stanza is being sent from, or <code>null</code> if the
* value has not been set.
*/
@Override
public Jid getFrom() {
return from;
}
@ -221,16 +235,12 @@ public abstract class Stanza implements TopLevelStreamElement {
*
* @param from who the packet is being sent to.
*/
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public void setFrom(Jid from) {
this.from = from;
}
/**
* Returns the error associated with this packet, or <code>null</code> if there are
* no errors.
*
* @return the error sub-packet or <code>null</code> if there isn't an error.
*/
@Override
public StanzaError getError() {
return error;
}
@ -238,14 +248,22 @@ public abstract class Stanza implements TopLevelStreamElement {
/**
* Sets the error for this stanza.
*
* @param xmppErrorBuilder the error to associate with this stanza.
* @param stanzaError the error that this stanza carries and hence signals.
*/
public void setError(StanzaError.Builder xmppErrorBuilder) {
if (xmppErrorBuilder == null) {
return;
}
xmppErrorBuilder.setStanza(this);
error = xmppErrorBuilder.build();
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public void setError(StanzaError stanzaError) {
error = stanzaError;
}
/**
* Deprecated.
* @param stanzaError the stanza error.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void setError(StanzaError.Builder stanzaError) {
setError(stanzaError.build());
}
@Override
@ -257,16 +275,15 @@ public abstract class Stanza implements TopLevelStreamElement {
* Sets the xml:lang of this Stanza.
*
* @param language the xml:lang of this Stanza.
* @deprecated use {@link StanzaBuilder#setLanguage(String)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void setLanguage(String language) {
this.language = language;
}
/**
* Returns a list of all extension elements of this stanza.
*
* @return a list of all extension elements of this stanza.
*/
@Override
public List<ExtensionElement> getExtensions() {
synchronized (extensionElements) {
// No need to create a new list, values() will already create a new one for us
@ -274,6 +291,16 @@ public abstract class Stanza implements TopLevelStreamElement {
}
}
public final MultiMap<QName, ExtensionElement> getExtensionsMap() {
return cloneExtensionsMap();
}
final MultiMap<QName, ExtensionElement> cloneExtensionsMap() {
synchronized (extensionElements) {
return extensionElements.clone();
}
}
/**
* Return a list of all extensions with the given element name <em>and</em> namespace.
* <p>
@ -289,7 +316,23 @@ public abstract class Stanza implements TopLevelStreamElement {
requireNotNullNorEmpty(elementName, "elementName must not be null nor empty");
requireNotNullNorEmpty(namespace, "namespace must not be null nor empty");
QName key = new QName(namespace, elementName);
return extensionElements.getAll(key);
return getExtensions(key);
}
@Override
public List<ExtensionElement> getExtensions(QName qname) {
List<ExtensionElement> res;
synchronized (extensionElements) {
res = extensionElements.getAll(qname);
}
return Collections.unmodifiableList(res);
}
@Override
public <E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass) {
synchronized (extensionElements) {
return XmppElementUtil.getElementsFrom(extensionElements, extensionElementClass);
}
}
/**
@ -322,21 +365,31 @@ public abstract class Stanza implements TopLevelStreamElement {
return null;
}
QName key = new QName(namespace, elementName);
ExtensionElement packetExtension;
synchronized (extensionElements) {
packetExtension = extensionElements.getFirst(key);
}
ExtensionElement packetExtension = getExtension(key);
if (packetExtension == null) {
return null;
}
return (PE) packetExtension;
}
@SuppressWarnings("unchecked")
@Override
public final <E extends ExtensionElement> E getExtension(QName qname) {
synchronized (extensionElements) {
return (E) extensionElements.getFirst(qname);
}
}
/**
* Adds a stanza extension to the packet. Does nothing if extension is null.
* <p>
* Please note that although this method is not yet marked as deprecated, it is recommended to use
* {@link StanzaBuilder#addExtension(ExtensionElement)} instead.
* </p>
*
* @param extension a stanza extension.
*/
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public void addExtension(ExtensionElement extension) {
if (extension == null) return;
QName key = extension.getQName();
@ -348,11 +401,16 @@ public abstract class Stanza implements TopLevelStreamElement {
/**
* Add the given extension and override eventually existing extensions with the same name and
* namespace.
* <p>
* Please note that although this method is not yet marked as deprecated, it is recommended to use
* {@link StanzaBuilder#overrideExtension(ExtensionElement)} instead.
* </p>
*
* @param extension the extension element to add.
* @return one of the removed extensions or <code>null</code> if there are none.
* @since 4.1.2
*/
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public ExtensionElement overrideExtension(ExtensionElement extension) {
if (extension == null) return null;
synchronized (extensionElements) {
@ -370,6 +428,7 @@ public abstract class Stanza implements TopLevelStreamElement {
*
* @param extensions a collection of stanza extensions
*/
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public void addExtensions(Collection<ExtensionElement> extensions) {
if (extensions == null) return;
for (ExtensionElement packetExtension : extensions) {
@ -421,6 +480,7 @@ public abstract class Stanza implements TopLevelStreamElement {
* @param namespace TODO javadoc me please
* @return the removed stanza extension or null.
*/
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public ExtensionElement removeExtension(String elementName, String namespace) {
QName key = new QName(namespace, elementName);
synchronized (extensionElements) {
@ -433,7 +493,10 @@ public abstract class Stanza implements TopLevelStreamElement {
*
* @param extension the stanza extension to remove.
* @return the removed stanza extension or null.
* @deprecated use {@link StanzaBuilder} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public ExtensionElement removeExtension(ExtensionElement extension) {
QName key = extension.getQName();
synchronized (extensionElements) {

View file

@ -0,0 +1,301 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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 java.util.Collection;
import java.util.List;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.ToStringUtil;
import org.jivesoftware.smack.util.XmppElementUtil;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public abstract class StanzaBuilder<B extends StanzaBuilder<B>> implements StanzaView {
final StanzaIdSource stanzaIdSource;
final String stanzaId;
Jid to;
Jid from;
StanzaError stanzaError;
String language;
MultiMap<QName, ExtensionElement> extensionElements = new MultiMap<>();
protected StanzaBuilder(StanzaIdSource stanzaIdSource) {
this.stanzaIdSource = stanzaIdSource;
this.stanzaId = null;
}
protected StanzaBuilder(String stanzaId) {
this.stanzaIdSource = null;
this.stanzaId = StringUtils.requireNullOrNotEmpty(stanzaId, "Stanza ID must not be the empty String");
}
protected StanzaBuilder(Stanza message, String stanzaId) {
this(stanzaId);
copyFromStanza(message);
}
protected StanzaBuilder(Stanza message, StanzaIdSource stanzaIdSource) {
this(stanzaIdSource);
copyFromStanza(message);
}
private void copyFromStanza(Stanza stanza) {
to = stanza.getTo();
from = stanza.getFrom();
stanzaError = stanza.getError();
language = stanza.getLanguage();
extensionElements = stanza.cloneExtensionsMap();
}
/**
* Set the recipent address of the stanza.
*
* @param to whoe the stanza is being sent to.
* @return a reference to this builder.
* @throws XmppStringprepException if the provided character sequence is not a valid XMPP address.
* @see #to(Jid)
*/
public final B to(CharSequence to) throws XmppStringprepException {
return to(JidCreate.from(to));
}
/**
* Sets who the stanza is being sent "to". The XMPP protocol often makes the "to" attribute optional, so it does not
* always need to be set.
*
* @param to who the stanza is being sent to.
* @return a reference to this builder.
*/
public final B to(Jid to) {
this.to = to;
return getThis();
}
/**
* Sets who the the stanza is being sent "from".
*
* @param from who the stanza is being sent from.
* @return a reference to this builder.
* @throws XmppStringprepException if the provided character sequence is not a valid XMPP address.
* @see #from(Jid)
*/
public final B from(CharSequence from) throws XmppStringprepException {
return from(JidCreate.from(from));
}
/**
* Sets who the stanza is being sent "from". The XMPP protocol often makes the "from" attribute optional, so it does
* not always need to be set.
*
* @param from who the stanza is being sent from.
* @return a reference to this builder.
*/
public final B from(Jid from) {
this.from = from;
return getThis();
}
/**
* Sets the error for this stanza.
*
* @param stanzaError the error to associate with this stanza.
* @return a reference to this builder.
*/
public final B setError(StanzaError stanzaError) {
this.stanzaError = stanzaError;
return getThis();
}
/**
* Sets the xml:lang for this stanza.
*
* @param language the xml:lang of this stanza.
* @return a reference to this builder.
*/
public final B setLanguage(String language) {
this.language = language;
return getThis();
}
public final B addExtension(ExtensionElement extensionElement) {
QName key = extensionElement.getQName();
extensionElements.put(key, extensionElement);
return getThis();
}
public final B addExtensions(Collection<? extends ExtensionElement> extensionElements) {
for (ExtensionElement extensionElement : extensionElements) {
addExtension(extensionElement);
}
return getThis();
}
public final B overrideExtension(ExtensionElement extensionElement) {
QName key = extensionElement.getQName();
extensionElements.remove(key);
extensionElements.put(key, extensionElement);
return getThis();
}
public abstract B getThis();
@Override
public final String getStanzaId() {
return stanzaId;
}
@Override
public final Jid getTo() {
return to;
}
@Override
public final Jid getFrom() {
return from;
}
@Override
public final String getLanguage() {
return language;
}
@Override
public final StanzaError getError() {
return stanzaError;
}
@SuppressWarnings("unchecked")
@Override
public final <E extends ExtensionElement> E getExtension(QName qname) {
return (E) extensionElements.getFirst(qname);
}
@Override
public final List<ExtensionElement> getExtensions() {
return extensionElements.values();
}
@Override
public final List<ExtensionElement> getExtensions(QName qname) {
return extensionElements.getAll(qname);
}
@Override
public final <E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass) {
return XmppElementUtil.getElementsFrom(extensionElements, extensionElementClass);
}
public final boolean willBuildStanzaWithId() {
return stanzaIdSource != null || StringUtils.isNotEmpty(stanzaId);
}
public final void throwIfNoStanzaId() {
if (willBuildStanzaWithId()) {
return;
}
throw new IllegalArgumentException(
"The builder will not build a stanza with an ID set, although it is required");
}
protected abstract void addStanzaSpecificAttributes(ToStringUtil.Builder builder);
@Override
public final String toString() {
ToStringUtil.Builder builder = ToStringUtil.builderFor(getClass())
.addValue("id", stanzaId)
.addValue("from", from)
.addValue("to", to)
.addValue("language", language)
.addValue("error", stanzaError)
;
addStanzaSpecificAttributes(builder);
builder.add("Extension Elements", extensionElements.values(), e -> {
return e.getQName();
});
return builder.build();
}
public static MessageBuilder buildMessage() {
return buildMessage(null);
}
public static MessageBuilder buildMessage(String stanzaId) {
return new MessageBuilder(stanzaId);
}
public static MessageBuilder buildMessageFrom(Message message, String stanzaId) {
return new MessageBuilder(message, stanzaId);
}
public static MessageBuilder buildMessageFrom(Message message, StanzaIdSource stanzaIdSource) {
return new MessageBuilder(message, stanzaIdSource);
}
public static PresenceBuilder buildPresence() {
return buildPresence(null);
}
public static PresenceBuilder buildPresence(String stanzaId) {
return new PresenceBuilder(stanzaId);
}
public static PresenceBuilder buildPresenceFrom(Presence presence, String stanzaId) {
return new PresenceBuilder(presence, stanzaId);
}
public static PresenceBuilder buildPresenceFrom(Presence presence, StanzaIdSource stanzaIdSource) {
return new PresenceBuilder(presence, stanzaIdSource);
}
public static IqBuilder buildIq(String stanzaId) {
return new IqBuilder(stanzaId);
}
public static IqBuilder buildIqResultFor(IQ request) {
if (!(request.getType() == Type.get || request.getType() == Type.set)) {
throw new IllegalArgumentException(
"IQ request must be of type 'set' or 'get'. Original IQ: " + request.toXML());
}
return buildIq(request.getStanzaId())
.to(request.getFrom())
.from(request.getTo())
.ofType(IQ.Type.result);
}
public static EmptyResultIQ buildEmptyIqResultFor(IQ request) {
IqBuilder iqBuilder = buildIqResultFor(request);
return new EmptyResultIQ(iqBuilder);
}
}

View file

@ -106,7 +106,6 @@ public class StanzaError extends AbstractError implements ExtensionElement {
private final String conditionText;
private final String errorGenerator;
private final Type type;
private final Stanza stanza;
/**
* Creates a new error with the specified type, condition and message.
@ -120,13 +119,11 @@ public class StanzaError extends AbstractError implements ExtensionElement {
* @param errorGenerator TODO javadoc me please
* @param descriptiveTexts TODO javadoc me please
* @param extensions list of stanza extensions
* @param stanza the stanza carrying this XMPP error.
*/
public StanzaError(Condition condition, String conditionText, String errorGenerator, Type type, Map<String, String> descriptiveTexts,
List<ExtensionElement> extensions, Stanza stanza) {
List<ExtensionElement> extensions) {
super(descriptiveTexts, ERROR_CONDITION_AND_TEXT_NAMESPACE, extensions);
this.condition = Objects.requireNonNull(condition, "condition must not be null");
this.stanza = stanza;
// Some implementations may send the condition as non-empty element containing the empty string, that is
// <condition xmlns='foo'></condition>, in this case the parser may calls this constructor with the empty string
// as conditionText, therefore reset it to null if it's the empty string
@ -184,16 +181,6 @@ public class StanzaError extends AbstractError implements ExtensionElement {
return conditionText;
}
/**
* Get the stanza carrying the XMPP error.
*
* @return the stanza carrying the XMPP error.
* @since 4.2
*/
public Stanza getStanza() {
return stanza;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("XMPPError: ");
@ -271,7 +258,6 @@ public class StanzaError extends AbstractError implements ExtensionElement {
private String conditionText;
private String errorGenerator;
private Type type;
private Stanza stanza;
private Builder() {
}
@ -296,17 +282,11 @@ public class StanzaError extends AbstractError implements ExtensionElement {
return this;
}
public Builder setStanza(Stanza stanza) {
this.stanza = stanza;
return this;
}
public Builder copyFrom(StanzaError xmppError) {
setCondition(xmppError.getCondition());
setType(xmppError.getType());
setConditionText(xmppError.getConditionText());
setErrorGenerator(xmppError.getErrorGenerator());
setStanza(xmppError.getStanza());
setDescriptiveTexts(xmppError.descriptiveTexts);
setTextNamespace(xmppError.textNamespace);
setExtensions(xmppError.extensions);
@ -315,7 +295,7 @@ public class StanzaError extends AbstractError implements ExtensionElement {
public StanzaError build() {
return new StanzaError(condition, conditionText, errorGenerator, type, descriptiveTexts,
extensions, stanza);
extensions);
}
@Override

View file

@ -0,0 +1,49 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.packet.id.StanzaIdSource;
public final class StanzaFactory {
private final StanzaIdSource stanzaIdSource;
public StanzaFactory(StanzaIdSource stanzaIdSource) {
this.stanzaIdSource = stanzaIdSource;
}
public MessageBuilder buildMessageStanza() {
return new MessageBuilder(stanzaIdSource);
}
public MessageBuilder buildMessageStanzaFrom(Message message) {
return new MessageBuilder(message, stanzaIdSource);
}
public PresenceBuilder buildPresenceStanza() {
return new PresenceBuilder(stanzaIdSource);
}
public PresenceBuilder buildPresenceStanzaFrom(Presence presence) {
return new PresenceBuilder(presence, stanzaIdSource);
}
public IqBuilder buildIqStanza() {
return new IqBuilder(stanzaIdSource);
}
}

View file

@ -0,0 +1,82 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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 java.util.List;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.util.XmppElementUtil;
import org.jxmpp.jid.Jid;
public interface StanzaView extends XmlLangElement {
/**
* Returns the unique ID of the stanza. The returned value could be <code>null</code>.
*
* @return the packet's unique ID or <code>null</code> if the id is not available.
*/
String getStanzaId();
/**
* Returns who the stanza is being sent "to", or <code>null</code> if
* the value is not set. The XMPP protocol often makes the "to"
* attribute optional, so it does not always need to be set.<p>
*
* @return who the stanza is being sent to, or <code>null</code> if the
* value has not been set.
*/
Jid getTo();
/**
* Returns who the stanza is being sent "from" or <code>null</code> if
* the value is not set. The XMPP protocol often makes the "from"
* attribute optional, so it does not always need to be set.<p>
*
* @return who the stanza is being sent from, or <code>null</code> if the
* value has not been set.
*/
Jid getFrom();
/**
* Returns the error associated with this packet, or <code>null</code> if there are
* no errors.
*
* @return the error sub-packet or <code>null</code> if there isn't an error.
*/
StanzaError getError();
<E extends ExtensionElement> E getExtension(QName qname);
@SuppressWarnings("unchecked")
default <E extends ExtensionElement> E getExtension(Class<E> extensionElementClass) {
QName qname = XmppElementUtil.getQNameFor(extensionElementClass);
return (E) getExtension(qname);
}
/**
* Returns a list of all extension elements of this stanza.
*
* @return a list of all extension elements of this stanza.
*/
List<ExtensionElement> getExtensions();
List<ExtensionElement> getExtensions(QName qname);
<E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass);
}

View file

@ -18,7 +18,6 @@ package org.jivesoftware.smack.packet;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
public class XmlEnvironment {

View file

@ -0,0 +1,28 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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;
public interface XmlLangElement {
/**
* Returns the xml:lang of this XML element, or null if one has not been set.
*
* @return the xml:lang of this XML element, or null.
*/
String getLanguage();
}

View file

@ -0,0 +1,54 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.id;
import org.jivesoftware.smack.util.StringUtils;
public final class RandomStringStanzaIdSource {
public static class Factory implements StanzaIdSourceFactory {
private static final int REQUIRED_MIN_LENGTH = 10;
private final int length;
private final boolean verySecure;
public static final Factory VERY_SECURE = new Factory(10, true);
public static final Factory MEDIUM_SECURE = new Factory(10, false);
public Factory(int length, boolean verySecure) {
if (length < REQUIRED_MIN_LENGTH) {
throw new IllegalArgumentException(
"Insufficient length " + length + ", must be at least " + REQUIRED_MIN_LENGTH);
}
this.length = length;
this.verySecure = verySecure;
}
@Override
public StanzaIdSource constructStanzaIdSource() {
StanzaIdSource stanzaIdSource;
if (verySecure) {
stanzaIdSource = () -> StringUtils.randomString(length);
} else {
stanzaIdSource = () -> StringUtils.insecureRandomString(length);
}
return stanzaIdSource;
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2015 Florian Schmaus
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,20 +20,32 @@ import java.util.concurrent.atomic.AtomicLong;
import org.jivesoftware.smack.util.StringUtils;
public class StanzaIdUtil {
public class StandardStanzaIdSource implements StanzaIdSource {
public static final StandardStanzaIdSource DEFAULT = new StandardStanzaIdSource();
/**
* A prefix helps to make sure that ID's are unique across multiple instances.
*/
private static final String PREFIX = StringUtils.randomString(5) + "-";
private final String prefix = StringUtils.randomString(5) + "-";
/**
* Keeps track of the current increment, which is appended to the prefix to
* forum a unique ID.
*/
private static final AtomicLong ID = new AtomicLong();
private final AtomicLong id = new AtomicLong();
@Override
public String getNewStanzaId() {
return prefix + Long.toString(id.incrementAndGet());
}
public static class Factory implements StanzaIdSourceFactory {
@Override
public StandardStanzaIdSource constructStanzaIdSource() {
return new StandardStanzaIdSource();
}
public static String newStanzaId() {
return PREFIX + Long.toString(ID.incrementAndGet());
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.id;
public interface StanzaIdSource {
String getNewStanzaId();
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.id;
public interface StanzaIdSourceFactory {
StanzaIdSource constructStanzaIdSource();
}

View file

@ -0,0 +1,41 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.id;
import java.util.UUID;
public final class UuidStanzaIdSource implements StanzaIdSource {
public static final UuidStanzaIdSource INSTANCE = new UuidStanzaIdSource();
private UuidStanzaIdSource() {
}
@Override
public String getNewStanzaId() {
return UUID.randomUUID().toString();
}
public static class Factory implements StanzaIdSourceFactory {
@Override
public UuidStanzaIdSource constructStanzaIdSource() {
return INSTANCE;
}
}
}

View file

@ -0,0 +1,23 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.util;
public interface Function<R, T> {
R apply(T t);
}

View file

@ -34,7 +34,7 @@ import java.util.Set;
* @param <K> the type of the keys the map uses.
* @param <V> the type of the values the map uses.
*/
public class MultiMap<K, V> {
public class MultiMap<K, V> implements TypedCloneable<MultiMap<K, V>> {
/**
* The constant value {@value}.
@ -252,6 +252,19 @@ public class MultiMap<K, V> {
return new MultiMap<K, V>(Collections.unmodifiableMap(mapCopy));
}
@Override
public MultiMap<K, V> clone() {
Map<K, List<V>> clonedMap = new LinkedHashMap<>(map.size());
// TODO: Use Map.forEach() once Smack's minimum Android API is 24 or higher.
for (Entry<K, List<V>> entry : map.entrySet()) {
List<V> clonedList = CollectionUtil.newListWith(entry.getValue());
clonedMap.put(entry.getKey(), clonedList);
}
return new MultiMap<>(clonedMap);
}
private static final class SimpleMapEntry<K, V> implements Map.Entry<K, V> {
private final K key;

View file

@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.compress.packet.Compress;
@ -36,9 +37,12 @@ import org.jivesoftware.smack.packet.ErrorIQ;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.PresenceBuilder;
import org.jivesoftware.smack.packet.Session;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError;
@ -120,18 +124,25 @@ public class PacketParserUtils {
}
}
private static void parseCommonStanzaAttributes(Stanza stanza, XmlPullParser parser, XmlEnvironment xmlEnvironment) throws XmppStringprepException {
private interface StanzaBuilderSupplier<SB extends StanzaBuilder<?>> {
SB get(String stanzaId);
}
private static <SB extends StanzaBuilder<?>> SB parseCommonStanzaAttributes(StanzaBuilderSupplier<SB> stanzaBuilderSupplier, XmlPullParser parser, XmlEnvironment xmlEnvironment) throws XmppStringprepException {
String id = parser.getAttributeValue("id");
stanza.setStanzaId(id);
SB stanzaBuilder = stanzaBuilderSupplier.get(id);
Jid to = ParserUtils.getJidAttribute(parser, "to");
stanza.setTo(to);
stanzaBuilder.to(to);
Jid from = ParserUtils.getJidAttribute(parser, "from");
stanza.setFrom(from);
stanzaBuilder.from(from);
String language = ParserUtils.getXmlLang(parser, xmlEnvironment);
stanza.setLanguage(language);
stanzaBuilder.setLanguage(language);
return stanzaBuilder;
}
public static Message parseMessage(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
@ -154,11 +165,14 @@ public class PacketParserUtils {
XmlEnvironment messageXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
final int initialDepth = parser.getDepth();
Message message = new Message();
parseCommonStanzaAttributes(message, parser, outerXmlEnvironment);
MessageBuilder message = parseCommonStanzaAttributes(id -> {
return StanzaBuilder.buildMessage(id);
}, parser, outerXmlEnvironment);
String typeString = parser.getAttributeValue("", "type");
if (typeString != null) {
message.setType(Message.Type.fromString(typeString));
message.ofType(Message.Type.fromString(typeString));
}
// Parse sub-elements. We include extra logic to make sure the values
@ -176,9 +190,8 @@ public class PacketParserUtils {
String xmlLangSubject = ParserUtils.getXmlLang(parser);
String subject = parseElementText(parser);
if (message.getSubject(xmlLangSubject) == null) {
message.addSubject(xmlLangSubject, subject);
}
Message.Subject subjectExtensionElement = new Message.Subject(xmlLangSubject, subject);
message.addExtension(subjectExtensionElement);
break;
case "thread":
if (thread == null) {
@ -189,7 +202,8 @@ public class PacketParserUtils {
message.setError(parseError(parser, messageXmlEnvironment));
break;
default:
PacketParserUtils.addExtensionElement(message, parser, elementName, namespace, messageXmlEnvironment);
ExtensionElement extensionElement = parseExtensionElement(elementName, namespace, parser, messageXmlEnvironment);
message.addExtension(extensionElement);
break;
}
break;
@ -208,7 +222,7 @@ public class PacketParserUtils {
// situations where we have a body element with an explicit xml lang set and once where the value is inherited
// and both values are equal.
return message;
return message.build();
}
/**
@ -436,13 +450,16 @@ public class PacketParserUtils {
final int initialDepth = parser.getDepth();
XmlEnvironment presenceXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
PresenceBuilder presence = parseCommonStanzaAttributes(
stanzaId -> StanzaBuilder.buildPresence(stanzaId), parser, outerXmlEnvironment);
Presence.Type type = Presence.Type.available;
String typeString = parser.getAttributeValue("", "type");
if (typeString != null && !typeString.equals("")) {
type = Presence.Type.fromString(typeString);
}
Presence presence = new Presence(type);
parseCommonStanzaAttributes(presence, parser, outerXmlEnvironment);
presence.ofType(type);
// Parse sub-elements
outerloop: while (true) {
@ -468,9 +485,7 @@ public class PacketParserUtils {
// '<show />' element, which is a invalid XMPP presence
// stanza according to RFC 6121 4.7.2.1
LOGGER.warning("Empty or null mode text in presence show element form "
+ presence.getFrom()
+ " with id '"
+ presence.getStanzaId()
+ presence
+ "' which is invalid according to RFC6121 4.7.2.1");
}
break;
@ -482,10 +497,10 @@ public class PacketParserUtils {
// Be extra robust: Skip PacketExtensions that cause Exceptions, instead of
// failing completely here. See SMACK-390 for more information.
try {
PacketParserUtils.addExtensionElement(presence, parser, elementName, namespace, presenceXmlEnvironment);
ExtensionElement extensionElement = parseExtensionElement(elementName, namespace, parser, presenceXmlEnvironment);
presence.addExtension(extensionElement);
} catch (Exception e) {
LOGGER.warning("Failed to parse extension element in Presence stanza: \"" + e + "\" from: '"
+ presence.getFrom() + " id: '" + presence.getStanzaId() + "'");
LOGGER.log(Level.WARNING, "Failed to parse extension element in Presence stanza: " + presence, e);
}
break;
}
@ -500,7 +515,8 @@ public class PacketParserUtils {
break;
}
}
return presence;
return presence.build();
}
public static IQ parseIQ(XmlPullParser parser) throws Exception {
@ -523,7 +539,7 @@ public class PacketParserUtils {
final int initialDepth = parser.getDepth();
XmlEnvironment iqXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
IQ iqPacket = null;
StanzaError.Builder error = null;
StanzaError error = null;
final String id = parser.getAttributeValue("", "id");
final Jid to = ParserUtils.getJidAttribute(parser, "to");
@ -747,7 +763,7 @@ public class PacketParserUtils {
return new StreamError(condition, conditionText, descriptiveTexts, extensions);
}
public static StanzaError.Builder parseError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
public static StanzaError parseError(XmlPullParser parser) throws XmlPullParserException, IOException, SmackParsingException {
return parseError(parser, null);
}
@ -761,7 +777,7 @@ public class PacketParserUtils {
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
*/
public static StanzaError.Builder parseError(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
public static StanzaError parseError(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
final int initialDepth = parser.getDepth();
Map<String, String> descriptiveTexts = null;
XmlEnvironment stanzaErrorXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
@ -808,7 +824,8 @@ public class PacketParserUtils {
}
}
builder.setExtensions(extensions).setDescriptiveTexts(descriptiveTexts);
return builder;
return builder.build();
}
/**

View file

@ -427,6 +427,13 @@ public class StringUtils {
return true;
}
public static boolean isNullOrNotEmpty(CharSequence cs) {
if (cs == null) {
return true;
}
return !cs.toString().isEmpty();
}
/**
* Returns true if the given CharSequence is empty.
*
@ -456,6 +463,11 @@ public class StringUtils {
*/
public static StringBuilder toStringBuilder(Collection<? extends Object> collection, String delimiter) {
StringBuilder sb = new StringBuilder(collection.size() * 20);
appendTo(collection, delimiter, sb);
return sb;
}
public static void appendTo(Collection<? extends Object> collection, String delimiter, StringBuilder sb) {
for (Iterator<? extends Object> it = collection.iterator(); it.hasNext();) {
Object cs = it.next();
sb.append(cs);
@ -463,7 +475,6 @@ public class StringUtils {
sb.append(delimiter);
}
}
return sb;
}
public static String returnIfNotEmptyTrimmed(String string) {

View file

@ -0,0 +1,75 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* 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.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ToStringUtil {
public static Builder builderFor(Class<?> clazz) {
StringBuilder sb = new StringBuilder();
sb.append(clazz.getSimpleName()).append('(');
return new Builder(sb);
}
public static final class Builder {
private final StringBuilder sb;
private Builder(StringBuilder sb) {
this.sb = sb;
}
public Builder addValue(String name, Object value) {
if (value == null) {
return this;
}
if (sb.charAt(sb.length() - 1) != '(') {
sb.append(' ');
}
sb.append(name).append("='").append(value).append('\'');
return this;
}
public <V> Builder add(String name, Collection<? extends V> values, Function<?, V> toStringFunction) {
if (values.isEmpty()) {
return this;
}
sb.append(' ').append(name).append('[');
List<String> stringValues = new ArrayList<>(values.size());
for (V value : values) {
String valueString = toStringFunction.apply(value).toString();
stringValues.add(valueString);
}
StringUtils.appendTo(stringValues, ", ", sb);
sb.append(']');
return this;
}
public String build() {
sb.append(')');
return sb.toString();
}
}
}

View file

@ -16,6 +16,9 @@
*/
package org.jivesoftware.smack.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
@ -52,4 +55,21 @@ public class XmppElementUtil {
return new QName(namespace, element);
}
public static <E extends FullyQualifiedElement, R extends FullyQualifiedElement> List<R> getElementsFrom(
MultiMap<QName, E> elementMap, Class<R> extensionElementClass) {
QName qname = XmppElementUtil.getQNameFor(extensionElementClass);
List<E> extensionElements = elementMap.getAll(qname);
if (extensionElements.isEmpty()) {
return Collections.emptyList();
}
List<R> res = new ArrayList<>(extensionElements.size());
for (E extensionElement : extensionElements) {
R e = extensionElementClass.cast(extensionElement);
res.add(e);
}
return res;
}
}

View file

@ -19,8 +19,8 @@ package org.jivesoftware.smack.filter;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.junit.Test;
import org.jxmpp.jid.EntityFullJid;
@ -47,7 +47,7 @@ public class FromMatchesFilterTest {
@Test
public void autoCompareMatchingEntityFullJid() {
FromMatchesFilter filter = FromMatchesFilter.create(FULL_JID1_R1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(FULL_JID1_R1);
assertTrue(filter.accept(packet));
@ -71,7 +71,7 @@ public class FromMatchesFilterTest {
@Test
public void autoCompareMatchingBaseJid() {
FromMatchesFilter filter = FromMatchesFilter.create(BASE_JID1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(BASE_JID1);
assertTrue(filter.accept(packet));
@ -95,7 +95,7 @@ public class FromMatchesFilterTest {
@Test
public void autoCompareMatchingServiceJid() {
FromMatchesFilter filter = FromMatchesFilter.create(SERVICE_JID1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(SERVICE_JID1);
assertTrue(filter.accept(packet));
@ -116,7 +116,7 @@ public class FromMatchesFilterTest {
@Test
public void bareCompareMatchingEntityFullJid() {
FromMatchesFilter filter = FromMatchesFilter.createBare(FULL_JID1_R1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(BASE_JID1);
assertTrue(filter.accept(packet));
@ -140,7 +140,7 @@ public class FromMatchesFilterTest {
@Test
public void bareCompareMatchingBaseJid() {
FromMatchesFilter filter = FromMatchesFilter.createBare(BASE_JID1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(BASE_JID1);
assertTrue(filter.accept(packet));
@ -164,7 +164,7 @@ public class FromMatchesFilterTest {
@Test
public void bareCompareMatchingServiceJid() {
FromMatchesFilter filter = FromMatchesFilter.createBare(SERVICE_JID1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(SERVICE_JID1);
assertTrue(filter.accept(packet));
@ -185,7 +185,7 @@ public class FromMatchesFilterTest {
@Test
public void fullCompareMatchingEntityFullJid() {
FromMatchesFilter filter = FromMatchesFilter.createFull(FULL_JID1_R1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(FULL_JID1_R1);
assertTrue(filter.accept(packet));
@ -209,7 +209,7 @@ public class FromMatchesFilterTest {
@Test
public void fullCompareMatchingBaseJid() {
FromMatchesFilter filter = FromMatchesFilter.createFull(BASE_JID1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(BASE_JID1);
assertTrue(filter.accept(packet));
@ -233,7 +233,7 @@ public class FromMatchesFilterTest {
@Test
public void fullCompareMatchingServiceJid() {
FromMatchesFilter filter = FromMatchesFilter.createFull(SERVICE_JID1);
Stanza packet = new Message();
Stanza packet = StanzaBuilder.buildMessage().build();
packet.setFrom(SERVICE_JID1);
assertTrue(filter.accept(packet));

View file

@ -62,7 +62,7 @@ public class IQResponseTest {
*/
@Test
public void testGeneratingValidErrorResponse() throws XmppStringprepException {
final StanzaError.Builder error = StanzaError.getBuilder(StanzaError.Condition.bad_request);
final StanzaError error = StanzaError.getBuilder(StanzaError.Condition.bad_request).build();
final IQ request = new TestIQ(ELEMENT, NAMESPACE);
request.setType(IQ.Type.set);
@ -75,7 +75,7 @@ public class IQResponseTest {
assertNotNull(result.getStanzaId());
assertEquals(request.getStanzaId(), result.getStanzaId());
assertEquals(request.getFrom(), result.getTo());
assertEquals(error.build().toXML().toString(), result.getError().toXML().toString());
assertEquals(error.toXML().toString(), result.getError().toXML().toString());
// TODO this test was never valid
// assertEquals(CHILD_ELEMENT, result.getChildElementXML());
}
@ -110,7 +110,7 @@ public class IQResponseTest {
*/
@Test
public void testGeneratingErrorBasedOnError() throws XmppStringprepException {
final StanzaError.Builder error = StanzaError.getBuilder(StanzaError.Condition.bad_request);
final StanzaError error = StanzaError.getBuilder(StanzaError.Condition.bad_request).build();
final IQ request = new TestIQ(ELEMENT, NAMESPACE);
request.setType(IQ.Type.error);

View file

@ -18,7 +18,6 @@ package org.jivesoftware.smack.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
@ -44,10 +43,12 @@ public class MessageTest {
.append("</message>");
String control = controlBuilder.toString();
Message messageTypeInConstructor = new Message(null, Message.Type.chat);
messageTypeInConstructor.setStanzaId(null);
assertEquals(type, messageTypeInConstructor.getType());
assertXmlSimilar(control, messageTypeInConstructor.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
Message messageBuildWithBuilder = StanzaBuilder.buildMessage()
.ofType(Message.Type.chat)
.build();
assertEquals(type, messageBuildWithBuilder.getType());
assertXmlSimilar(control, messageBuildWithBuilder.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
controlBuilder = new StringBuilder();
controlBuilder.append("<message")
@ -57,16 +58,18 @@ public class MessageTest {
.append("</message>");
control = controlBuilder.toString();
Message messageTypeSet = getNewMessage();
messageTypeSet.setType(type2);
Message messageTypeSet = StanzaBuilder.buildMessage()
.ofType(type2)
.build();
assertEquals(type2, messageTypeSet.getType());
assertXmlSimilar(control, messageTypeSet.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@Test(expected = NullPointerException.class)
public void setNullMessageBodyTest() {
Message message = getNewMessage();
message.addBody(null, null);
StanzaBuilder.buildMessage()
.addBody(null, null)
.build();
}
@Test
@ -81,9 +84,9 @@ public class MessageTest {
.append("</message>");
String control = controlBuilder.toString();
Message message = getNewMessage();
message.setSubject(messageSubject);
Message message = StanzaBuilder.buildMessage()
.setSubject(messageSubject)
.build();
assertEquals(messageSubject, message.getSubject());
assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@ -100,9 +103,9 @@ public class MessageTest {
.append("</message>");
String control = controlBuilder.toString();
Message message = getNewMessage();
message.setBody(messageBody);
Message message = StanzaBuilder.buildMessage()
.setBody(messageBody)
.build();
assertEquals(messageBody, message.getBody());
assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@ -133,10 +136,11 @@ public class MessageTest {
.append("</message>");
String control = controlBuilder.toString();
Message message = getNewMessage();
message.addBody(null, messageBody1);
message.addBody(lang2, messageBody2);
message.addBody(lang3, messageBody3);
Message message = StanzaBuilder.buildMessage()
.addBody(null, messageBody1)
.addBody(lang2, messageBody2)
.addBody(lang3, messageBody3)
.build();
assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE));
Collection<String> languages = message.getBodyLanguages();
@ -148,21 +152,20 @@ public class MessageTest {
}
@Test
public void removeMessageBodyTest() {
Message message = getNewMessage();
message.setBody("test");
public void simpleMessageBodyTest() {
Message message = StanzaBuilder.buildMessage()
.setBody("test")
.build();
assertTrue(message.getBodies().size() == 1);
message.setBody(null);
message = StanzaBuilder.buildMessage().build();
assertTrue(message.getBodies().size() == 0);
assertFalse(message.removeBody("sp"));
Message.Body body = message.addBody("es", "test");
message = StanzaBuilder.buildMessage()
.addBody("es", "test")
.build();
assertTrue(message.getBodies().size() == 1);
message.removeBody(body);
assertTrue(message.getBodies().size() == 0);
}
@Test
@ -177,8 +180,9 @@ public class MessageTest {
.append("</message>");
String control = controlBuilder.toString();
Message message = getNewMessage();
message.setThread(messageThread);
Message message = StanzaBuilder.buildMessage()
.setThread(messageThread)
.build();
assertEquals(messageThread, message.getThread());
assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
@ -196,15 +200,10 @@ public class MessageTest {
.append("</message>");
String control = controlBuilder.toString();
Message message = getNewMessage();
message.setLanguage(lang);
Message message = StanzaBuilder.buildMessage()
.setLanguage(lang)
.build();
assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
private static Message getNewMessage() {
Message message = new Message();
message.setStanzaId(null);
return message;
}
}

View file

@ -41,8 +41,9 @@ public class PresenceTest {
.append("</presence>");
String control = controlBuilder.toString();
Presence presenceTypeInConstructor = new Presence(type);
presenceTypeInConstructor.setStanzaId(null);
Presence presenceTypeInConstructor = StanzaBuilder.buildPresence()
.ofType(type)
.build();
assertEquals(type, presenceTypeInConstructor.getType());
assertXmlSimilar(control, presenceTypeInConstructor.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
@ -54,27 +55,27 @@ public class PresenceTest {
.append("</presence>");
control = controlBuilder.toString();
Presence presenceTypeSet = getNewPresence();
presenceTypeSet.setType(type2);
PresenceBuilder presenceTypeSet = getNewPresence();
presenceTypeSet.ofType(type2);
assertEquals(type2, presenceTypeSet.getType());
assertXmlSimilar(control, presenceTypeSet.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
assertXmlSimilar(control, presenceTypeSet.build().toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@Test
public void setNullPresenceTypeTest() {
assertThrows(IllegalArgumentException.class, () ->
getNewPresence().setType(null)
getNewPresence().ofType(null)
);
}
@Test
public void isPresenceAvailableTest() {
Presence presence = getNewPresence();
presence.setType(Presence.Type.available);
assertTrue(presence.isAvailable());
PresenceBuilder presence = getNewPresence();
presence.ofType(Presence.Type.available);
assertTrue(presence.build().isAvailable());
presence.setType(Presence.Type.unavailable);
assertFalse(presence.isAvailable());
presence.ofType(Presence.Type.unavailable);
assertFalse(presence.build().isAvailable());
}
@Test
@ -89,11 +90,11 @@ public class PresenceTest {
.append("</presence>");
String control = controlBuilder.toString();
Presence presence = getNewPresence();
PresenceBuilder presence = getNewPresence();
presence.setStatus(status);
assertEquals(status, presence.getStatus());
assertXmlSimilar(control, presence.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
assertXmlSimilar(control, presence.build().toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@Test
@ -108,11 +109,11 @@ public class PresenceTest {
.append("</presence>");
String control = controlBuilder.toString();
Presence presence = getNewPresence();
PresenceBuilder presence = getNewPresence();
presence.setPriority(priority);
assertEquals(priority, presence.getPriority());
assertXmlSimilar(control, presence.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
assertXmlSimilar(control, presence.build().toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@Test
@ -143,11 +144,14 @@ public class PresenceTest {
.append("</presence>");
String control = controlBuilder.toString();
Presence presenceModeInConstructor = new Presence(Presence.Type.available, status, priority,
mode1);
presenceModeInConstructor.setStanzaId(null);
assertEquals(mode1, presenceModeInConstructor.getMode());
assertXmlSimilar(control, presenceModeInConstructor.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
Presence presenceBuildWithBuilder = StanzaBuilder.buildPresence()
.ofType(Presence.Type.available)
.setStatus(status)
.setPriority(priority)
.setMode(mode1)
.build();
assertEquals(mode1, presenceBuildWithBuilder.getMode());
assertXmlSimilar(control, presenceBuildWithBuilder.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
controlBuilder = new StringBuilder();
controlBuilder.append("<presence>")
@ -157,20 +161,20 @@ public class PresenceTest {
.append("</presence>");
control = controlBuilder.toString();
Presence presenceModeSet = getNewPresence();
PresenceBuilder presenceModeSet = getNewPresence();
presenceModeSet.setMode(mode2);
assertEquals(mode2, presenceModeSet.getMode());
assertXmlSimilar(control, presenceModeSet.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
assertXmlSimilar(control, presenceModeSet.build().toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
@Test
public void isModeAwayTest() {
Presence presence = getNewPresence();
PresenceBuilder presence = getNewPresence();
presence.setMode(Presence.Mode.away);
assertTrue(presence.isAway());
assertTrue(presence.build().isAway());
presence.setMode(Presence.Mode.chat);
assertFalse(presence.isAway());
assertFalse(presence.build().isAway());
}
@Test
@ -185,15 +189,14 @@ public class PresenceTest {
.append("</presence>");
String control = controlBuilder.toString();
Presence presence = getNewPresence();
PresenceBuilder presence = getNewPresence();
presence.setLanguage(lang);
assertXmlSimilar(control, presence.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
assertXmlSimilar(control, presence.build().toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}
private static Presence getNewPresence() {
Presence presence = new Presence(Presence.Type.available);
presence.setStanzaId(null);
private static PresenceBuilder getNewPresence() {
PresenceBuilder presence = StanzaBuilder.buildPresence().ofType(Presence.Type.available);
return presence;
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2016-2017 Florian Schmaus
* Copyright © 2016-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,8 +18,6 @@ package org.jivesoftware.smack.packet;
import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.packet.Presence.Mode;
import org.junit.Test;
import org.jxmpp.jid.JidTestUtil;
@ -27,15 +25,21 @@ public class ToStringTest {
@Test
public void messageTest() {
Message message = new Message(JidTestUtil.BARE_JID_1, Message.Type.headline);
message.setStanzaId("message-id");
Message message = StanzaBuilder.buildMessage("message-id")
.ofType(Message.Type.headline)
.to(JidTestUtil.BARE_JID_1)
.build();
String string = message.toString();
assertEquals("Message Stanza [to=one@exampleone.org,id=message-id,type=headline,]", string);
}
@Test
public void presenceTest() {
Presence presence = new Presence(Presence.Type.subscribe, null, 0, Mode.away);
Presence presence = StanzaBuilder.buildPresence()
.ofType(Presence.Type.subscribe)
.setPriority(0)
.setMode(Presence.Mode.away)
.build();
presence.setStanzaId("presence-id");
String string = presence.toString();
assertEquals("Presence Stanza [id=presence-id,type=subscribe,mode=away,prio=0,]", string);

View file

@ -16,12 +16,13 @@
*/
package org.jivesoftware.smack.packet;
import static org.jivesoftware.smack.packet.StanzaError.Condition;
import static org.jivesoftware.smack.packet.StanzaError.Type;
import static org.junit.Assert.assertEquals;
import java.util.Map;
import org.jivesoftware.smack.packet.StanzaError.Condition;
import org.jivesoftware.smack.packet.StanzaError.Type;
import org.junit.Test;
public class XMPPErrorTest {

View file

@ -45,7 +45,6 @@ import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import com.jamesmurty.utils.XMLBuilder;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@ -547,30 +546,6 @@ public class PacketParserUtilsTest {
assertTrue(message.getSubjectLanguages().contains(otherLanguage));
assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
// message has default language, first subject no language, second subject default language
control = XMLBuilder.create("message")
.a("from", "romeo@montague.lit/orchard")
.a("to", "juliet@capulet.lit/balcony")
.a("id", "zid615d9")
.a("type", "chat")
.a("xml:lang", defaultLanguage)
.e("subject")
.t(defaultLanguage)
.up()
.e("subject")
.a("xml:lang", defaultLanguage)
.t(defaultLanguage + "2")
.asString(outputProperties);
message = PacketParserUtils
.parseMessage(PacketParserUtils.getParserFor(control));
assertEquals(defaultLanguage, message.getSubject());
assertEquals(defaultLanguage, message.getSubject(defaultLanguage));
assertEquals(1, message.getSubjects().size());
assertEquals(0, message.getSubjectLanguages().size());
assertXmlNotSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
// message has non-default language, first subject no language, second subject default language
control = XMLBuilder.create("message")
.a("from", "romeo@montague.lit/orchard")
@ -867,7 +842,7 @@ public class PacketParserUtilsTest {
.element("text", StanzaError.ERROR_CONDITION_AND_TEXT_NAMESPACE).t(text).up()
.asString();
XmlPullParser parser = TestUtils.getParser(errorXml);
StanzaError error = PacketParserUtils.parseError(parser).build();
StanzaError error = PacketParserUtils.parseError(parser);
assertEquals(text, error.getDescriptiveText());
}
}