Compare commits

...

16 Commits

Author SHA1 Message Date
Florian Schmaus 9d626bf787 core: improve AsyncButOrdered
Instead of marking the handle as not running by setting the handler's
value in the map to false, we now remove simply the key if there is no
handler running. This also means we no longer need to use a weak hash
map for this.

Also reduce the size of the synchronized blocks, mainly by scheduling
the handler outside of the synchronized(threadActiveMap) block.

Make some code better readable and add some more comments. Also do
start a new handler thread if the task threw.
2019-11-08 10:14:21 +01:00
Florian Schmaus a7a298c5d8 Use standard stanza listeners in MultiUserChat
Those, relatively new, listeners guarantee that the individual
listeners are not invoked in concurrently while preserving the
order. Exactly what MultiUserChat previously did with AsyncButOrdered,
which is now no longer needed and hence can be removed.
2019-11-07 14:36:50 +01:00
Florian Schmaus eb4c2c5572 s/occured/occurred/ 2019-10-30 12:02:36 +01:00
Florian Schmaus b0277d7e74 Emit <starttls/> as empty element when possible 2019-10-30 12:00:20 +01:00
Florian Schmaus 2915101843 Use isEmpty() in StringUtils.requireNullOrNotEmpty() 2019-10-30 11:56:55 +01:00
Florian Schmaus 6d1252755b Mark more methods in Stanza as final 2019-10-30 09:23:01 +01:00
Florian Schmaus ee699f24dd Remove DiscoverInfo.Identity(Identity) copy constructor
as there is no need to copy an immutable type.
2019-10-30 08:24:13 +01:00
Florian Schmaus 63a4212f7e Remove clone() from DiscoverInfo.Identity
there is no need to clone immutable types.
2019-10-30 08:22:47 +01:00
Florian Schmaus 63ba524758 Make IQ(IQ) constructor protected 2019-10-30 08:20:25 +01:00
Florian Schmaus e2223254cf Organize imports everywhere 2019-10-29 21:12:22 +01:00
Florian Schmaus 69767e9538 Rename AbstractIqBuilder subclasses to their designated names 2019-10-29 19:34:18 +01:00
Florian Schmaus 6e32305987 Apply builder pattern to DiscoverInfo
This is the first transformation of an IQ type to the builder type.
2019-10-29 11:14:55 +01:00
Florian Schmaus 36072fb25a test: created mocked connectionwith stanza factory 2019-10-29 10:40:41 +01:00
Florian Schmaus 4b01892129 Remove unused DiscoverInfo instances in PingTest 2019-10-27 07:51:25 +01:00
Florian Schmaus e2d206e741 Introduce XMPPConnection.add(Message|Presence)Interceptor
add deprecate addStanzaInterceptor().
2019-10-25 21:41:55 +02:00
Florian Schmaus 5db6191110 Introduce StanzaBuilder
As first step to immutable Stanza types.
2019-10-25 21:41:55 +02:00
234 changed files with 4056 additions and 1411 deletions

View File

@ -15,7 +15,11 @@ Smack Key Advantages
AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker", "password", "jabber.org");
connection.connect().login();
Message message = new Message("jsmith@jivesoftware.com", "Howdy! How are you?");
Message message = connection.getStanzaFactory()
.buildMessageStanza()
.to("jsmith@jivesoftware.com")
.setBody("Howdy! How are you?")
.build();
connection.sendStanza(message);
```

View File

@ -28,17 +28,17 @@ SMACK_EXCEPTIONS[InterruptedException]="if the calling thread was interrupted."
SMACK_EXCEPTIONS[XMPPErrorException]="if there was an XMPP error returned."
SMACK_EXCEPTIONS[NoResponseException]="if there was no response from the remote entity."
SMACK_EXCEPTIONS[NotLoggedInException]="if the XMPP connection is not authenticated."
SMACK_EXCEPTIONS[BOSHException]="if an BOSH related error occured."
SMACK_EXCEPTIONS[IOException]="if an I/O error occured."
SMACK_EXCEPTIONS[BOSHException]="if an BOSH related error occurred."
SMACK_EXCEPTIONS[IOException]="if an I/O error occurred."
SMACK_EXCEPTIONS[SmackException]="if Smack detected an exceptional situation."
SMACK_EXCEPTIONS[XMPPException]="if an XMPP protocol error was received."
SMACK_EXCEPTIONS[SmackSaslException]="if a SASL specific error occured."
SMACK_EXCEPTIONS[SmackSaslException]="if a SASL specific error occurred."
SMACK_EXCEPTIONS[SASLErrorException]="if a SASL protocol error was returned."
SMACK_EXCEPTIONS[NotAMucServiceException]="if the entity is not a MUC serivce."
SMACK_EXCEPTIONS[NoSuchAlgorithmException]="if no such algorithm is available."
SMACK_EXCEPTIONS[KeyManagementException]="if there was a key mangement error."
SMACK_EXCEPTIONS[XmppStringprepException]="if the provided string is invalid."
SMACK_EXCEPTIONS[XmlPullParserException]="if an error in the XML parser occured."
SMACK_EXCEPTIONS[XmlPullParserException]="if an error in the XML parser occurred."
SMACK_EXCEPTIONS[SmackParsingException]="if the Smack parser (provider) encountered invalid input."
SMACK_EXCEPTIONS[MucNotJoinedException]="if not joined to the Multi-User Chat."
SMACK_EXCEPTIONS[MucAlreadyJoinedException]="if already joined the Multi-User Chat."7y
@ -54,7 +54,7 @@ SMACK_EXCEPTIONS[IllegalArgumentException]="if an illegal argument was given."
SMACK_EXCEPTIONS[NotAPubSubNodeException]="if a involved node is not a PubSub node."
SMACK_EXCEPTIONS[NoAcceptableTransferMechanisms]="if no acceptable transfer mechanisms are available"
SMACK_EXCEPTIONS[NoSuchMethodException]="if no such method is declared"
SMACK_EXCEPTIONS[Exception]="if an exception occured."
SMACK_EXCEPTIONS[Exception]="if an exception occurred."
SMACK_EXCEPTIONS[TestNotPossibleException]="if the test is not possible."
SMACK_EXCEPTIONS[TimeoutException]="if there was a timeout."
SMACK_EXCEPTIONS[IllegalStateException]="if an illegal state was encountered"

View File

@ -517,8 +517,8 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
if ("urn:ietf:params:xml:ns:xmpp-streams".equals(parser.getNamespace(null))) {
throw new StreamErrorException(PacketParserUtils.parseStreamError(parser));
} else {
StanzaError.Builder builder = PacketParserUtils.parseError(parser);
throw new XMPPException.XMPPErrorException(null, builder.build());
StanzaError stanzaError = PacketParserUtils.parseError(parser);
throw new XMPPException.XMPPErrorException(null, stanzaError);
}
default:
parseAndProcessNonza(parser);

View File

@ -100,16 +100,22 @@ import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Mechanisms;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.MessageOrPresence;
import org.jivesoftware.smack.packet.MessageOrPresenceBuilder;
import org.jivesoftware.smack.packet.Nonza;
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.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;
@ -121,11 +127,13 @@ import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.Predicate;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
@ -241,6 +249,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private final Map<StanzaListener, InterceptorWrapper> interceptors =
new HashMap<>();
private final Map<Consumer<MessageBuilder>, GenericInterceptorWrapper<MessageBuilder, Message>> messageInterceptors = new HashMap<>();
private final Map<Consumer<PresenceBuilder>, GenericInterceptorWrapper<PresenceBuilder, Presence>> presenceInterceptors = new HashMap<>();
private XmlEnvironment incomingStreamXmlEnvironment;
protected XmlEnvironment outgoingStreamXmlEnvironment;
@ -402,6 +414,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 +454,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
listener.connectionCreated(this);
}
StanzaIdSource stanzaIdSource = configuration.constructStanzaIdSource();
stanzaFactory = new StanzaFactory(stanzaIdSource);
}
/**
@ -518,7 +535,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
*
* @throws XMPPException if an error occurs on the XMPP protocol level.
* @throws SmackException if an error occurs somewhere else besides XMPP protocol level.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @return a reference to this object, to chain <code>connect()</code> with <code>login()</code>.
* @throws InterruptedException if the calling thread was interrupted.
*/
@ -558,7 +575,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* login if the previous connection state was logged (authenticated).
*
* @throws SmackException if Smack detected an exceptional situation.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @throws XMPPException if an XMPP protocol error was received.
* @throws InterruptedException if the calling thread was interrupted.
*/
@ -611,7 +628,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @param password TODO javadoc me please
* @throws XMPPException if an XMPP protocol error was received.
* @throws SmackException if Smack detected an exceptional situation.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @throws InterruptedException if the calling thread was interrupted.
* @see #login
*/
@ -629,7 +646,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @param resource TODO javadoc me please
* @throws XMPPException if an XMPP protocol error was received.
* @throws SmackException if Smack detected an exceptional situation.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @throws InterruptedException if the calling thread was interrupted.
* @see #login
*/
@ -733,7 +750,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 +835,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");
@ -833,8 +859,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
// Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify
// the content of the stanza.
firePacketInterceptors(stanza);
sendStanzaInternal(stanza);
Stanza stanzaAfterInterceptors = firePacketInterceptors(stanza);
sendStanzaInternal(stanzaAfterInterceptors);
}
/**
@ -847,9 +873,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @return the used SASLMechanism.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws SASLErrorException if a SASL protocol error was returned.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackSaslException if a SASL specific error occured.
* @throws SmackSaslException if a SASL specific error occurred.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws NoResponseException if there was no response from the remote entity.
* @throws SmackWrappedException in case of an exception.
@ -893,7 +919,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);
@ -1186,6 +1214,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
});
}
@Deprecated
@Override
public void addStanzaInterceptor(StanzaListener packetInterceptor,
StanzaFilter packetFilter) {
@ -1198,6 +1227,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
@Deprecated
@Override
public void removeStanzaInterceptor(StanzaListener packetInterceptor) {
synchronized (interceptors) {
@ -1205,15 +1235,83 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
private static <MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> void addInterceptor(
Map<Consumer<MPB>, GenericInterceptorWrapper<MPB, MP>> interceptors, Consumer<MPB> interceptor,
Predicate<MP> filter) {
Objects.requireNonNull(interceptor, "Interceptor must not be null");
GenericInterceptorWrapper<MPB, MP> interceptorWrapper = new GenericInterceptorWrapper<>(interceptor, filter);
synchronized (interceptors) {
interceptors.put(interceptor, interceptorWrapper);
}
}
private static <MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> void removeInterceptor(
Map<Consumer<MPB>, GenericInterceptorWrapper<MPB, MP>> interceptors, Consumer<MPB> interceptor) {
synchronized (interceptors) {
interceptors.remove(interceptor);
}
}
@Override
public void addMessageInterceptor(Consumer<MessageBuilder> messageInterceptor, Predicate<Message> messageFilter) {
addInterceptor(messageInterceptors, messageInterceptor, messageFilter);
}
@Override
public void removeMessageInterceptor(Consumer<MessageBuilder> messageInterceptor) {
removeInterceptor(messageInterceptors, messageInterceptor);
}
@Override
public void addPresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor,
Predicate<Presence> presenceFilter) {
addInterceptor(presenceInterceptors, presenceInterceptor, presenceFilter);
}
@Override
public void removePresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor) {
removeInterceptor(presenceInterceptors, presenceInterceptor);
}
private static <MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> MP fireMessageOrPresenceInterceptors(
MP messageOrPresence, Map<Consumer<MPB>, GenericInterceptorWrapper<MPB, MP>> interceptors) {
List<Consumer<MPB>> interceptorsToInvoke = new LinkedList<>();
synchronized (interceptors) {
for (GenericInterceptorWrapper<MPB, MP> interceptorWrapper : interceptors.values()) {
if (interceptorWrapper.filterMatches(messageOrPresence)) {
Consumer<MPB> interceptor = interceptorWrapper.getInterceptor();
interceptorsToInvoke.add(interceptor);
}
}
}
// Avoid transforming the stanza to a builder if there is no interceptor.
if (interceptorsToInvoke.isEmpty()) {
return messageOrPresence;
}
MPB builder = messageOrPresence.asBuilder();
for (Consumer<MPB> interceptor : interceptorsToInvoke) {
interceptor.accept(builder);
}
// Now that the interceptors have (probably) modified the stanza in its builder form, we need to re-assemble it.
messageOrPresence = builder.build();
return messageOrPresence;
}
/**
* Process interceptors. Interceptors may modify the stanza that is about to be sent.
* Since the thread that requested to send the stanza will invoke all interceptors, it
* is important that interceptors perform their work as soon as possible so that the
* thread does not remain blocked for a long period.
*
* @param packet the stanza that is going to be sent to the server
* @param packet the stanza that is going to be sent to the server.
* @return the, potentially modified stanza, after the interceptors are run.
*/
private void firePacketInterceptors(Stanza packet) {
private Stanza firePacketInterceptors(Stanza packet) {
List<StanzaListener> interceptorsToInvoke = new LinkedList<>();
synchronized (interceptors) {
for (InterceptorWrapper interceptorWrapper : interceptors.values()) {
@ -1229,6 +1327,22 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
LOGGER.log(Level.SEVERE, "Packet interceptor threw exception", e);
}
}
final Stanza stanzaAfterInterceptors;
if (packet instanceof Message) {
Message message = (Message) packet;
stanzaAfterInterceptors = fireMessageOrPresenceInterceptors(message, messageInterceptors);
}
else if (packet instanceof Presence) {
Presence presence = (Presence) packet;
stanzaAfterInterceptors = fireMessageOrPresenceInterceptors(presence, presenceInterceptors);
} else {
// We do not (yet) support interceptors for IQ stanzas.
assert packet instanceof IQ;
stanzaAfterInterceptors = packet;
}
return stanzaAfterInterceptors;
}
/**
@ -1416,7 +1530,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(() -> {
@ -1656,6 +1770,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
/**
* A wrapper class to associate a stanza filter with an interceptor.
*/
@Deprecated
// TODO: Remove once addStanzaInterceptor is gone.
protected static class InterceptorWrapper {
private final StanzaListener packetInterceptor;
@ -1681,6 +1797,24 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
private static final class GenericInterceptorWrapper<MPB extends MessageOrPresenceBuilder<MP, MPB>, MP extends MessageOrPresence<MPB>> {
private final Consumer<MPB> stanzaInterceptor;
private final Predicate<MP> stanzaFilter;
private GenericInterceptorWrapper(Consumer<MPB> stanzaInterceptor, Predicate<MP> stanzaFilter) {
this.stanzaInterceptor = stanzaInterceptor;
this.stanzaFilter = stanzaFilter;
}
private boolean filterMatches(MP stanza) {
return stanzaFilter == null || stanzaFilter.test(stanza);
}
public Consumer<MPB> getInterceptor() {
return stanzaInterceptor;
}
}
@Override
public int getConnectionCounter() {
return connectionCounterValue;

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-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.
@ -16,6 +16,7 @@
*/
package org.jivesoftware.smack;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.WeakHashMap;
@ -51,9 +52,17 @@ import java.util.concurrent.Executor;
*/
public class AsyncButOrdered<K> {
/**
* A map with the currently pending runnables for a given key. Note that this is a weak hash map so we do not have
* to take care of removing the keys ourselfs from the map.
*/
private final Map<K, Queue<Runnable>> pendingRunnables = new WeakHashMap<>();
private final Map<K, Boolean> threadActiveMap = new WeakHashMap<>();
/**
* A marker map if there is an active thread for the given key. Holds the responsible handler thread if one is
* active, otherwise the key is non-existend in the map.
*/
private final Map<K, Handler> threadActiveMap = new HashMap<>();
private final Executor executor;
@ -65,6 +74,14 @@ public class AsyncButOrdered<K> {
this.executor = executor;
}
private void scheduleHandler(Handler handler) {
if (executor == null) {
AbstractXMPPConnection.asyncGo(handler);
} else {
executor.execute(handler);
}
}
/**
* Invoke the given {@link Runnable} asynchronous but ordered in respect to the given key.
*
@ -73,6 +90,7 @@ public class AsyncButOrdered<K> {
* @return true if a new thread was created
*/
public boolean performAsyncButOrdered(K key, Runnable runnable) {
// First check if a key queue already exists, create one if not.
Queue<Runnable> keyQueue;
synchronized (pendingRunnables) {
keyQueue = pendingRunnables.get(key);
@ -82,29 +100,27 @@ public class AsyncButOrdered<K> {
}
}
// Then add the task to the queue.
keyQueue.add(runnable);
boolean newHandler;
// Finally check if there is already a handler working on that queue, create one if not.
Handler newlyCreatedHandler = null;
synchronized (threadActiveMap) {
Boolean threadActive = threadActiveMap.get(key);
if (threadActive == null) {
threadActive = false;
threadActiveMap.put(key, threadActive);
}
if (!threadActiveMap.containsKey(key)) {
newlyCreatedHandler = new Handler(keyQueue, key);
newHandler = !threadActive;
if (newHandler) {
Handler handler = new Handler(keyQueue, key);
threadActiveMap.put(key, true);
if (executor == null) {
AbstractXMPPConnection.asyncGo(handler);
} else {
executor.execute(handler);
}
// Mark that there is thread active for the given key. Note that this has to be done before scheduling
// the handler thread.
threadActiveMap.put(key, newlyCreatedHandler);
}
}
return newHandler;
if (newlyCreatedHandler != null) {
scheduleHandler(newlyCreatedHandler);
return true;
}
return false;
}
public Executor asExecutorFor(final K key) {
@ -134,11 +150,14 @@ public class AsyncButOrdered<K> {
try {
runnable.run();
} catch (Throwable t) {
// The run() method threw, this handler thread is going to terminate because of that. Ensure we note
// that in the map.
// The run() method threw, this handler thread is going to terminate because of that. We create
// a new handler to continue working on the queue while throwing the throwable so that the
// executor can handle it.
Handler newlyCreatedHandler = new Handler(keyQueue, key);
synchronized (threadActiveMap) {
threadActiveMap.put(key, false);
threadActiveMap.put(key, newlyCreatedHandler);
}
scheduleHandler(newlyCreatedHandler);
throw t;
}
}
@ -146,7 +165,7 @@ public class AsyncButOrdered<K> {
synchronized (threadActiveMap) {
// If the queue is empty, stop this handler, otherwise continue looping.
if (keyQueue.isEmpty()) {
threadActiveMap.put(key, false);
threadActiveMap.remove(key);
break mainloop;
}
}

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

@ -175,9 +175,9 @@ public final class SASLAuthentication {
* @return the used SASLMechanism.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws SASLErrorException if a SASL protocol error was returned.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackSaslException if a SASL specific error occured.
* @throws SmackSaslException if a SASL specific error occurred.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws NoResponseException if there was no response from the remote entity.
*/
@ -233,7 +233,7 @@ public final class SASLAuthentication {
*
* @param challenge a base64 encoded string representing the challenge.
* @param finalChallenge true if this is the last challenge send by the server within the success stanza
* @throws SmackSaslException if a SASL specific error occured.
* @throws SmackSaslException if a SASL specific error occurred.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
@ -252,7 +252,7 @@ public final class SASLAuthentication {
* @throws SmackException if Smack detected an exceptional situation.
* @throws InterruptedException if the calling thread was interrupted.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws SmackSaslException if a SASL specific error occured.
* @throws SmackSaslException if a SASL specific error occurred.
*/
void authenticated(Success success) throws InterruptedException, SmackSaslException, NotConnectedException {
// RFC6120 6.3.10 "At the end of the authentication exchange, the SASL server (the XMPP

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

@ -27,8 +27,15 @@ import org.jivesoftware.smack.iqrequest.IQRequestHandler;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.FullyQualifiedElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.PresenceBuilder;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StanzaFactory;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.Predicate;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityFullJid;
@ -178,6 +185,8 @@ public interface XMPPConnection {
*/
boolean isUsingCompression();
StanzaFactory getStanzaFactory();
/**
* Sends the specified stanza to the server.
*
@ -358,7 +367,7 @@ public interface XMPPConnection {
boolean removeStanzaListener(StanzaListener stanzaListener);
/**
* Registers a <b>synchronous</b> stanza listener with this connection. A stanza listener will be invoked only when
* Registers a <b>synchronous</b> stanza listener with this connection. A stanza listener will be invoked only when
* an incoming stanza is received. A stanza filter determines which stanzas will be delivered to the listener. If
* the same stanza listener is added again with a different filter, only the new filter will be used.
* <p>
@ -443,16 +452,65 @@ public interface XMPPConnection {
*
* @param stanzaInterceptor the stanza interceptor to notify of stanzas about to be sent.
* @param stanzaFilter the stanza filter to use.
* @deprecated use {@link #addMessageInterceptor(Consumer, Predicate)} or {@link #addPresenceInterceptor(Consumer, Predicate)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
void addStanzaInterceptor(StanzaListener stanzaInterceptor, StanzaFilter stanzaFilter);
/**
* Removes a stanza interceptor.
*
* @param stanzaInterceptor the stanza interceptor to remove.
* @deprecated use {@link #removeMessageInterceptor(Consumer)} or {@link #removePresenceInterceptor(Consumer)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
void removeStanzaInterceptor(StanzaListener stanzaInterceptor);
/**
* Registers a stanza interceptor with this connection. The interceptor will be
* invoked every time a stanza is about to be sent by this connection. Interceptors
* may modify the stanza to be sent. A stanza filter determines which stanzas
* will be delivered to the interceptor.
*
* <p>
* NOTE: For a similar functionality on incoming stanzas, see {@link #addAsyncStanzaListener(StanzaListener, StanzaFilter)}.
* </p>
*
* @param messageInterceptor the stanza interceptor to notify of stanzas about to be sent.
* @param messageFilter the stanza filter to use.
*/
void addMessageInterceptor(Consumer<MessageBuilder> messageInterceptor, Predicate<Message> messageFilter);
/**
* Removes a message interceptor.
*
* @param messageInterceptor the message interceptor to remove.
*/
void removeMessageInterceptor(Consumer<MessageBuilder> messageInterceptor);
/**
* Registers a stanza interceptor with this connection. The interceptor will be
* invoked every time a stanza is about to be sent by this connection. Interceptors
* may modify the stanza to be sent. A stanza filter determines which stanzas
* will be delivered to the interceptor.
*
* <p>
* NOTE: For a similar functionality on incoming stanzas, see {@link #addAsyncStanzaListener(StanzaListener, StanzaFilter)}.
* </p>
*
* @param presenceInterceptor the stanza interceptor to notify of stanzas about to be sent.
* @param presenceFilter the stanza filter to use.
*/
void addPresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor, Predicate<Presence> presenceFilter);
/**
* Removes a presence interceptor.
*
* @param presenceInterceptor the stanza interceptor to remove.
*/
void removePresenceInterceptor(Consumer<PresenceBuilder> presenceInterceptor);
/**
* Returns the current value of the reply timeout in milliseconds for request for this
* XMPPConnection instance.

View File

@ -19,7 +19,6 @@ package org.jivesoftware.smack.compress.provider;
import org.jivesoftware.smack.compress.packet.Compressed;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
public final class CompressedProvider extends NonzaProvider<Compressed> {

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

@ -76,7 +76,7 @@ public class Java7ZlibInputOutputStream extends XMPPInputOutputStream {
* byte without blocking, 0 means that the system is known to block for more input.
*
* @return 0 if no data is available, 1 otherwise
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
*/
@Override
public int available() throws IOException {

View File

@ -18,6 +18,7 @@
package org.jivesoftware.smack.filter;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Predicate;
/**
* Defines a way to filter stanzas for particular attributes. Stanza filters are used when
@ -51,7 +52,7 @@ import org.jivesoftware.smack.packet.Stanza;
* @see org.jivesoftware.smack.StanzaListener
* @author Matt Tucker
*/
public interface StanzaFilter {
public interface StanzaFilter extends Predicate<Stanza> {
/**
* Tests whether or not the specified stanza should pass the filter.
@ -60,4 +61,9 @@ public interface StanzaFilter {
* @return true if and only if <code>stanza</code> passes the filter.
*/
boolean accept(Stanza stanza);
@Override
default boolean test(Stanza stanza) {
return accept(stanza);
}
}

View File

@ -0,0 +1,69 @@
/**
*
* 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;
import org.jivesoftware.smack.util.ToStringUtil;
public abstract class AbstractIqBuilder<IB extends AbstractIqBuilder<IB>> extends StanzaBuilder<IB> implements IqView {
protected IQ.Type type = IQ.Type.get;
AbstractIqBuilder(AbstractIqBuilder<?> other) {
super(other);
type = other.type;
}
AbstractIqBuilder(StanzaIdSource stanzaIdSource) {
super(stanzaIdSource);
}
AbstractIqBuilder(String stanzaId) {
super(stanzaId);
}
public static IqData createResponse(IqView request) {
return createResponse(request, IQ.ResponseType.result);
}
public static IqData createErrorResponse(IqView request) {
return createResponse(request, IQ.ResponseType.error);
}
protected static IqData createResponse(IqView request, IQ.ResponseType responseType) {
if (!(request.getType() == IQ.Type.get || request.getType() == IQ.Type.set)) {
throw new IllegalArgumentException("IQ request must be of type 'set' or 'get'. Original IQ: " + request);
}
IqData commonResponseIqData = buildResponse(request, s -> {
return StanzaBuilder.buildIqData(s);
});
commonResponseIqData.ofType(responseType.getType());
return commonResponseIqData;
}
@Override
protected final void addStanzaSpecificAttributes(ToStringUtil.Builder builder) {
builder.addValue("type", getType());
}
@Override
public final IQ.Type getType() {
return type;
}
}

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(IqData 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(AbstractIqBuilder.createResponse(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,7 +54,7 @@ public abstract class IQ extends Stanza {
private Type type = Type.get;
public IQ(IQ iq) {
protected IQ(IQ iq) {
super(iq);
type = iq.getType();
this.childElementName = iq.childElementName;
@ -62,7 +62,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(IqData.EMPTY, childElementName, childElementNamespace);
}
protected IQ(AbstractIqBuilder<?> iqBuilder, String childElementName, String childElementNamespace) {
super(iqBuilder);
type = iqBuilder.type;
this.childElementName = childElementName;
this.childElementNamespace = childElementNamespace;
if (childElementName == null) {
@ -72,11 +81,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 +95,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 +266,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 +304,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 +314,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());
}
/**
@ -392,6 +375,25 @@ public abstract class IQ extends Stanza {
}
}
public enum ResponseType {
result(Type.result),
error(Type.error),
;
final Type type;
ResponseType(Type type) {
this.type = type;
}
Type getType() {
return type;
}
}
public static class IQChildElementXmlStringBuilder extends XmlStringBuilder {
private final String element;

View File

@ -0,0 +1,44 @@
/**
*
* 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.XMPPConnection;
import org.jivesoftware.smack.util.Objects;
public abstract class IqBuilder<IB extends IqBuilder<IB, I>, I extends IQ>
extends AbstractIqBuilder<IB> {
protected IqBuilder(AbstractIqBuilder<?> other) {
super(other);
}
protected IqBuilder(XMPPConnection connection) {
super(connection.getStanzaFactory().getStanzaIdSource());
}
protected IqBuilder(String stanzaId) {
super(stanzaId);
}
public IB ofType(IQ.Type type) {
this.type = Objects.requireNonNull(type);
return getThis();
}
public abstract I build();
}

View File

@ -0,0 +1,44 @@
/**
*
* 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.StandardStanzaIdSource;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.util.Objects;
public final class IqData extends AbstractIqBuilder<IqData> {
static final IqData EMPTY = new IqData(StandardStanzaIdSource.DEFAULT);
IqData(StanzaIdSource stanzaIdSource) {
super(stanzaIdSource);
}
IqData(String stanzaId) {
super(stanzaId);
}
public IqData ofType(IQ.Type type) {
this.type = Objects.requireNonNull(type);
return getThis();
}
@Override
public IqData getThis() {
return this;
}
}

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,8 @@ import org.jxmpp.stringprep.XmppStringprepException;
*
* @author Matt Tucker
*/
public final class Message extends Stanza implements TypedCloneable<Message> {
public final class Message extends MessageOrPresence<MessageBuilder>
implements MessageView, TypedCloneable<Message> {
public static final String ELEMENT = "message";
public static final String BODY = "body";
@ -66,11 +69,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 +82,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 +95,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 +109,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 +124,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 +138,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 +166,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 +180,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 +217,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 +233,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 +246,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 +266,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 +289,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 +307,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 +321,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 +390,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 +408,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 +428,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 +447,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 +469,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 +510,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 +535,11 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
return ELEMENT;
}
@Override
public MessageBuilder asBuilder() {
return StanzaBuilder.buildMessageFrom(this, getStanzaId());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -491,18 +559,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 +593,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.");
}
@ -608,6 +666,7 @@ public final class Message extends Stanza implements TypedCloneable<Message> {
public static final String ELEMENT = "body";
public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE;
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
enum BodyElementNamespace {
client(StreamOpen.CLIENT_NAMESPACE),

View File

@ -0,0 +1,165 @@
/**
*
* 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 MessageOrPresenceBuilder<Message, 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;
}
@Override
public Message build() {
return new Message(this);
}
@Override
public Message.Type getType() {
return type;
}
}

View File

@ -0,0 +1,36 @@
/**
*
* 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 abstract class MessageOrPresence<MPB extends MessageOrPresenceBuilder<?, ?>> extends Stanza {
@Deprecated
// TODO: Remove in Smack 4.5.
protected MessageOrPresence() {
}
protected MessageOrPresence(StanzaBuilder<?> stanzaBuilder) {
super(stanzaBuilder);
}
protected MessageOrPresence(Stanza other) {
super(other);
}
public abstract MPB asBuilder();
}

View File

@ -0,0 +1,42 @@
/**
*
* 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 abstract class MessageOrPresenceBuilder<MP extends MessageOrPresence<? extends MessageOrPresenceBuilder<MP, SB>>, SB extends StanzaBuilder<SB>>
extends StanzaBuilder<SB> {
protected MessageOrPresenceBuilder(Stanza stanza, StanzaIdSource stanzaIdSource) {
super(stanza, stanzaIdSource);
}
protected MessageOrPresenceBuilder(Stanza stanza, String stanzaId) {
super(stanza, stanzaId);
}
protected MessageOrPresenceBuilder(StanzaIdSource stanzaIdSource) {
super(stanzaIdSource);
}
protected MessageOrPresenceBuilder(String stanzaId) {
super(stanzaId);
}
public abstract MP build();
}

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,8 @@ import org.jxmpp.jid.Jid;
*
* @author Matt Tucker
*/
public final class Presence extends Stanza implements TypedCloneable<Presence> {
public final class Presence extends MessageOrPresence<PresenceBuilder>
implements PresenceView, TypedCloneable<Presence> {
public static final String ELEMENT = "presence";
@ -81,7 +83,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 +99,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 +115,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 +128,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 +182,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 +191,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 +209,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 +236,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 +252,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 +265,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 +278,11 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
return ELEMENT;
}
@Override
public PresenceBuilder asBuilder() {
return StanzaBuilder.buildPresenceFrom(this, getStanzaId());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -326,7 +348,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,141 @@
/**
*
* 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 MessageOrPresenceBuilder<Presence, 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;
}
@Override
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(IqData 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,31 +90,47 @@ 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.
*/
public String getStanzaId() {
@Override
public final String getStanzaId() {
return id;
}
@ -127,7 +153,7 @@ public abstract class Stanza implements TopLevelStreamElement {
* @return true if the stanza ID is set, false otherwise.
* @since 4.1
*/
public boolean hasStanzaIdSet() {
public final boolean hasStanzaIdSet() {
// setStanzaId ensures that the id is either null or not empty,
// so we can assume that it is set if it's not null.
return id != null;
@ -138,57 +164,51 @@ 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 Jid getTo() {
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 final Jid getTo() {
return to;
}
@ -198,19 +218,13 @@ 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.
*/
public Jid getFrom() {
@Override
public final Jid getFrom() {
return from;
}
@ -221,35 +235,39 @@ 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.
*/
public StanzaError getError() {
@Override
public final StanzaError getError() {
return error;
}
/**
* 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
public String getLanguage() {
public final String getLanguage() {
return language;
}
@ -257,23 +275,32 @@ 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.
*/
public List<ExtensionElement> getExtensions() {
@Override
public final List<ExtensionElement> getExtensions() {
synchronized (extensionElements) {
// No need to create a new list, values() will already create a new one for us
return extensionElements.values();
}
}
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>
@ -285,11 +312,27 @@ public abstract class Stanza implements TopLevelStreamElement {
* @return a set of all matching extensions.
* @since 4.1
*/
public List<ExtensionElement> getExtensions(String elementName, String namespace) {
public final List<ExtensionElement> getExtensions(String elementName, String namespace) {
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 final List<ExtensionElement> getExtensions(QName qname) {
List<ExtensionElement> res;
synchronized (extensionElements) {
res = extensionElements.getAll(qname);
}
return Collections.unmodifiableList(res);
}
@Override
public final <E extends ExtensionElement> List<E> getExtensions(Class<E> extensionElementClass) {
synchronized (extensionElements) {
return XmppElementUtil.getElementsFrom(extensionElements, extensionElementClass);
}
}
/**
@ -301,7 +344,7 @@ public abstract class Stanza implements TopLevelStreamElement {
* @param namespace the namespace of the extension that is desired.
* @return the stanza extension with the given namespace.
*/
public ExtensionElement getExtension(String namespace) {
public final ExtensionElement getExtension(String namespace) {
return PacketUtil.extensionElementFrom(getExtensions(), null, namespace);
}
@ -317,27 +360,37 @@ public abstract class Stanza implements TopLevelStreamElement {
* @return the extension, or <code>null</code> if it doesn't exist.
*/
@SuppressWarnings("unchecked")
public <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) {
public final <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) {
if (namespace == null) {
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.
*/
public void addExtension(ExtensionElement extension) {
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public final void addExtension(ExtensionElement extension) {
if (extension == null) return;
QName key = extension.getQName();
synchronized (extensionElements) {
@ -348,12 +401,17 @@ 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
*/
public ExtensionElement overrideExtension(ExtensionElement extension) {
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public final ExtensionElement overrideExtension(ExtensionElement extension) {
if (extension == null) return null;
synchronized (extensionElements) {
// Note that we need to use removeExtension(String, String) here. If would use
@ -370,7 +428,8 @@ public abstract class Stanza implements TopLevelStreamElement {
*
* @param extensions a collection of stanza extensions
*/
public void addExtensions(Collection<ExtensionElement> extensions) {
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public final void addExtensions(Collection<ExtensionElement> extensions) {
if (extensions == null) return;
for (ExtensionElement packetExtension : extensions) {
addExtension(packetExtension);
@ -387,7 +446,7 @@ public abstract class Stanza implements TopLevelStreamElement {
* @param namespace TODO javadoc me please
* @return true if a stanza extension exists, false otherwise.
*/
public boolean hasExtension(String elementName, String namespace) {
public final boolean hasExtension(String elementName, String namespace) {
if (elementName == null) {
return hasExtension(namespace);
}
@ -397,13 +456,9 @@ public abstract class Stanza implements TopLevelStreamElement {
}
}
/**
* Check if a stanza extension with the given namespace exists.
*
* @param namespace TODO javadoc me please
* @return true if a stanza extension exists, false otherwise.
*/
public boolean hasExtension(String namespace) {
// Overridden in order to avoid an extra copy.
@Override
public final boolean hasExtension(String namespace) {
synchronized (extensionElements) {
for (ExtensionElement packetExtension : extensionElements.values()) {
if (packetExtension.getNamespace().equals(namespace)) {
@ -421,7 +476,8 @@ public abstract class Stanza implements TopLevelStreamElement {
* @param namespace TODO javadoc me please
* @return the removed stanza extension or null.
*/
public ExtensionElement removeExtension(String elementName, String namespace) {
// TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
public final ExtensionElement removeExtension(String elementName, String namespace) {
QName key = new QName(namespace, elementName);
synchronized (extensionElements) {
return extensionElements.remove(key);
@ -433,8 +489,11 @@ 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.
*/
public ExtensionElement removeExtension(ExtensionElement extension) {
@Deprecated
// TODO: Remove in Smack 4.5.
public final ExtensionElement removeExtension(ExtensionElement extension) {
QName key = extension.getQName();
synchronized (extensionElements) {
List<ExtensionElement> list = extensionElements.getAll(key);
@ -494,7 +553,7 @@ public abstract class Stanza implements TopLevelStreamElement {
*
* @param xml the XmlStringBuilder to append the error to.
*/
protected void appendErrorIfExists(XmlStringBuilder xml) {
protected final void appendErrorIfExists(XmlStringBuilder xml) {
StanzaError error = getError();
if (error != null) {
xml.append(error);

View File

@ -0,0 +1,306 @@
/**
*
* 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.id.StanzaIdSource;
import org.jivesoftware.smack.util.Function;
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(StanzaBuilder<?> other) {
stanzaIdSource = other.stanzaIdSource;
stanzaId = other.stanzaId;
to = other.to;
from = other.from;
stanzaError = other.stanzaError;
language = other.language;
extensionElements = other.extensionElements.clone();
}
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 IqData buildIqData(String stanzaId) {
return new IqData(stanzaId);
}
public static <SB extends StanzaBuilder<?>> SB buildResponse(StanzaView request, Function<SB, String> builderFromStanzaId) {
SB responseBuilder = builderFromStanzaId.apply(request.getStanzaId());
responseBuilder.to(request.getFrom())
.from(request.getTo())
;
return responseBuilder;
}
}

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,53 @@
/**
*
* 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;
StanzaIdSource getStanzaIdSource() {
return 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 IqData buildIqData() {
return new IqData(stanzaIdSource);
}
}

View File

@ -0,0 +1,102 @@
/**
*
* 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);
default boolean hasExtension(QName qname) {
return getExtension(qname) != null;
}
/**
* Check if a extension element with the given namespace exists.
*
* @param namespace the namespace of the extension element to check for.
* @return true if a stanza extension exists, false otherwise.
*/
default boolean hasExtension(String namespace) {
for (ExtensionElement packetExtension : getExtensions()) {
if (packetExtension.getNamespace().equals(namespace)) {
return true;
}
}
return false;
}
@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

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014-2018 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.
@ -52,9 +52,15 @@ public class StartTls implements Nonza {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.rightAngleBracket();
xml.condEmptyElement(required, "required");
xml.closeElement(this);
if (required) {
xml.rightAngleBracket();
xml.emptyElement("required");
xml.closeElement(this);
} else {
xml.closeEmptyElement();
}
return xml;
}

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

@ -39,7 +39,7 @@ public interface ParsingExceptionCallback {
* Called when parsing a stanza caused an exception.
*
* @param stanzaData the raw stanza data that caused the exception
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
*/
void handleUnparsableStanza(UnparseableStanza stanzaData) throws IOException;

View File

@ -25,7 +25,6 @@ import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;

View File

@ -0,0 +1,45 @@
/**
*
* 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.provider;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.jivesoftware.smack.packet.Element;
public class AbstractProvider<E extends Element> {
private final Class<E> elementClass;
@SuppressWarnings("unchecked")
protected AbstractProvider() {
Type currentType = getClass().getGenericSuperclass();
while (!(currentType instanceof ParameterizedType)) {
Class<?> currentClass = (Class<?>) currentType;
currentType = currentClass.getGenericSuperclass();
}
ParameterizedType parameterizedGenericSuperclass = (ParameterizedType) currentType;
Type[] actualTypeArguments = parameterizedGenericSuperclass.getActualTypeArguments();
Type elementType = actualTypeArguments[0];
elementClass = (Class<E>) elementType;
}
public final Class<E> getElementClass() {
return elementClass;
}
}

View File

@ -23,7 +23,6 @@ import java.io.IOException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;

View File

@ -26,7 +26,6 @@ import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;

View File

@ -17,15 +17,53 @@
package org.jivesoftware.smack.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
/**
* <p>
* <b>Deprecation Notice:</b> This class is deprecated, use {@link IQProvider} instead.
* </p>
* An abstract class for parsing custom IQ packets. Each IQProvider must be registered with
* the ProviderManager class for it to be used. Every implementation of this
* abstract class <b>must</b> have a public, no-argument constructor.
*
* @author Matt Tucker
*/
public abstract class IQProvider<I extends IQ> extends Provider<I> {
public abstract class IQProvider<I extends IQ> extends IqProvider<I> {
public final I parse(XmlPullParser parser) throws IOException, XmlPullParserException, SmackParsingException {
return parse(parser, (XmlEnvironment) null);
}
public final I parse(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws IOException, XmlPullParserException, SmackParsingException {
// XPP3 calling convention assert: Parser should be at start tag
ParserUtils.assertAtStartTag(parser);
final int initialDepth = parser.getDepth();
final XmlEnvironment xmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
I e = parse(parser, initialDepth, xmlEnvironment);
// XPP3 calling convention assert: Parser should be at end tag of the consumed/parsed element
ParserUtils.forwardToEndTagOfDepth(parser, initialDepth);
return e;
}
@Override
public final I parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
// Old-style IQ parsers do not need IqData.
return parse(parser, initialDepth, xmlEnvironment);
}
public abstract I parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException;
}

View File

@ -34,7 +34,7 @@ public final class IQProviderInfo extends AbstractProviderInfo {
* @param namespace Namespace that provider parses.
* @param iqProvider The provider implementation.
*/
public IQProviderInfo(String elementName, String namespace, IQProvider<IQ> iqProvider) {
public IQProviderInfo(String elementName, String namespace, IqProvider<IQ> iqProvider) {
super(elementName, namespace, iqProvider);
}
}

View File

@ -23,7 +23,6 @@ import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;

View File

@ -0,0 +1,46 @@
/**
*
* 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.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
public abstract class IqProvider<I extends IQ> extends AbstractProvider<I> {
public final I parse(XmlPullParser parser, IqData iqCommon)
throws XmlPullParserException, IOException, SmackParsingException {
return parse(parser, iqCommon, null);
}
public final I parse(XmlPullParser parser, IqData iqData, XmlEnvironment outerXmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
final int initialDepth = parser.getDepth();
final XmlEnvironment xmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
return parse(parser, initialDepth, iqData, xmlEnvironment);
}
public abstract I parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException;
}

View File

@ -18,14 +18,11 @@
package org.jivesoftware.smack.provider;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@ -40,27 +37,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException;
* @author Florian Schmaus
* @param <E> the type of the resulting element.
*/
public abstract class Provider<E extends Element> {
private final Class<E> elementClass;
@SuppressWarnings("unchecked")
protected Provider() {
Type currentType = getClass().getGenericSuperclass();
while (!(currentType instanceof ParameterizedType)) {
Class<?> currentClass = (Class<?>) currentType;
currentType = currentClass.getGenericSuperclass();
}
ParameterizedType parameterizedGenericSuperclass = (ParameterizedType) currentType;
Type[] actualTypeArguments = parameterizedGenericSuperclass.getActualTypeArguments();
Type elementType = actualTypeArguments[0];
elementClass = (Class<E>) elementType;
}
public final Class<E> getElementClass() {
return elementClass;
}
public abstract class Provider<E extends Element> extends AbstractProvider<E> {
public final E parse(XmlPullParser parser) throws IOException, XmlPullParserException, SmackParsingException {
return parse(parser, null);

View File

@ -80,8 +80,8 @@ public class ProviderFileLoader implements ProviderLoader {
// an IQ class, add the class object itself, then we'll use
// reflection later to create instances of the class.
// Add the provider to the map.
if (IQProvider.class.isAssignableFrom(provider)) {
IQProvider<IQ> iqProvider = (IQProvider<IQ>) provider.getConstructor().newInstance();
if (IqProvider.class.isAssignableFrom(provider)) {
IqProvider<IQ> iqProvider = (IqProvider<IQ>) provider.getConstructor().newInstance();
iqProviders.add(new IQProviderInfo(elementName, namespace, iqProvider));
}
else {

View File

@ -113,7 +113,7 @@ import org.jivesoftware.smack.util.XmppElementUtil;
public final class ProviderManager {
private static final Map<QName, ExtensionElementProvider<ExtensionElement>> extensionProviders = new ConcurrentHashMap<>();
private static final Map<QName, IQProvider<IQ>> iqProviders = new ConcurrentHashMap<>();
private static final Map<QName, IqProvider<IQ>> iqProviders = new ConcurrentHashMap<>();
private static final Map<QName, ExtensionElementProvider<ExtensionElement>> streamFeatureProviders = new ConcurrentHashMap<>();
private static final Map<QName, NonzaProvider<? extends Nonza>> nonzaProviders = new ConcurrentHashMap<>();
@ -167,7 +167,7 @@ public final class ProviderManager {
* @param namespace the XML namespace.
* @return the IQ provider.
*/
public static IQProvider<IQ> getIQProvider(String elementName, String namespace) {
public static IqProvider<IQ> getIQProvider(String elementName, String namespace) {
QName key = getQName(elementName, namespace);
return iqProviders.get(key);
}
@ -179,8 +179,8 @@ public final class ProviderManager {
*
* @return all IQProvider instances.
*/
public static List<IQProvider<IQ>> getIQProviders() {
List<IQProvider<IQ>> providers = new ArrayList<>(iqProviders.size());
public static List<IqProvider<IQ>> getIQProviders() {
List<IqProvider<IQ>> providers = new ArrayList<>(iqProviders.size());
providers.addAll(iqProviders.values());
return providers;
}
@ -200,10 +200,10 @@ public final class ProviderManager {
validate(elementName, namespace);
// First remove existing providers
QName key = removeIQProvider(elementName, namespace);
if (provider instanceof IQProvider) {
iqProviders.put(key, (IQProvider<IQ>) provider);
if (provider instanceof IqProvider) {
iqProviders.put(key, (IqProvider<IQ>) provider);
} else {
throw new IllegalArgumentException("Provider must be an IQProvider");
throw new IllegalArgumentException("Provider must be an instance of IqProvider");
}
}

View File

@ -18,7 +18,6 @@ package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.packet.TlsProceed;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.xml.XmlPullParser;
public final class TlsFailureProvider extends NonzaProvider<TlsProceed> {

View File

@ -18,7 +18,6 @@ package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.packet.TlsFailure;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.xml.XmlPullParser;
public final class TlsProceedProvider extends NonzaProvider<TlsFailure> {

View File

@ -222,7 +222,7 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
* empty array here.
*
* @return the initial response or null
* @throws SmackSaslException if a SASL specific error occured.
* @throws SmackSaslException if a SASL specific error occurred.
*/
protected abstract byte[] getAuthenticationText() throws SmackSaslException;

View File

@ -259,7 +259,7 @@ public abstract class ScramMechanism extends SASLMechanism {
/**
*
* @return the Channel Binding data.
* @throws SmackSaslException if a SASL specific error occured.
* @throws SmackSaslException if a SASL specific error occurred.
*/
protected byte[] getChannelBindingData() throws SmackSaslException {
return null;

View File

@ -110,7 +110,7 @@ public final class FileUtils {
*
* @param file TODO javadoc me please
* @return the content of file or null in case of an error
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
*/
@SuppressWarnings("DefaultCharset")
public static String readFileOrThrow(File file) throws IOException {

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;
@ -35,10 +36,14 @@ import org.jivesoftware.smack.packet.EmptyResultIQ;
import org.jivesoftware.smack.packet.ErrorIQ;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IqData;
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;
@ -47,7 +52,7 @@ import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.IqProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.xml.SmackXmlParser;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -101,9 +106,9 @@ public class PacketParserUtils {
* @param parser TODO javadoc me please
* @param outerXmlEnvironment the outer XML environment (optional).
* @return a stanza which is either a Message, IQ or Presence.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
*/
public static Stanza parseStanza(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, SmackParsingException, IOException {
ParserUtils.assertAtStartTag(parser);
@ -120,18 +125,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 {
@ -144,8 +156,8 @@ public class PacketParserUtils {
* @param parser the XML parser, positioned at the start of a message packet.
* @param outerXmlEnvironment the outer XML environment (optional).
* @return a Message packet.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws IOException if an I/O error occurred.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
*/
public static Message parseMessage(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
@ -154,11 +166,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 +191,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 +203,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 +223,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();
}
/**
@ -223,8 +238,8 @@ public class PacketParserUtils {
*
* @param parser TODO javadoc me please
* @return the textual content of the element as String
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws IOException if an I/O error occurred.
*/
public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
assert parser.getEventType() == XmlPullParser.Event.START_ELEMENT;
@ -262,8 +277,8 @@ public class PacketParserUtils {
*
* @param parser the XML pull parser
* @return the element as string
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws IOException if an I/O error occurred.
*/
public static CharSequence parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
return parseElement(parser, false);
@ -301,8 +316,8 @@ public class PacketParserUtils {
* @param depth TODO javadoc me please
* @param fullNamespaces TODO javadoc me please
* @return the content of the current depth
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws IOException if an I/O error occurred.
*/
public static CharSequence parseContentDepth(XmlPullParser parser, int depth, boolean fullNamespaces) throws XmlPullParserException, IOException {
if (parser.supportsRoundtrip()) {
@ -427,8 +442,8 @@ public class PacketParserUtils {
* @param parser the XML parser, positioned at the start of a presence packet.
* @param outerXmlEnvironment the outer XML environment (optional).
* @return a Presence packet.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occurred.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
*/
public static Presence parsePresence(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
@ -436,13 +451,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 +486,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 +498,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 +516,8 @@ public class PacketParserUtils {
break;
}
}
return presence;
return presence.build();
}
public static IQ parseIQ(XmlPullParser parser) throws Exception {
@ -513,9 +530,9 @@ public class PacketParserUtils {
* @param parser the XML parser, positioned at the start of an IQ packet.
* @param outerXmlEnvironment the outer XML environment (optional).
* @return an IQ object.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws XmppStringprepException if the provided string is invalid.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
*/
public static IQ parseIQ(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, XmppStringprepException, IOException, SmackParsingException {
@ -523,12 +540,19 @@ 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");
IqData iqData = StanzaBuilder.buildIqData(id);
final Jid to = ParserUtils.getJidAttribute(parser, "to");
iqData.to(to);
final Jid from = ParserUtils.getJidAttribute(parser, "from");
iqData.from(from);
final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
iqData.ofType(type);
outerloop: while (true) {
XmlPullParser.Event eventType = parser.next();
@ -544,9 +568,9 @@ public class PacketParserUtils {
// Otherwise, see if there is a registered provider for
// this element name and namespace.
default:
IQProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
IqProvider<IQ> provider = ProviderManager.getIQProvider(elementName, namespace);
if (provider != null) {
iqPacket = provider.parse(parser, outerXmlEnvironment);
iqPacket = provider.parse(parser, iqData, outerXmlEnvironment);
}
// Note that if we reach this code, it is guranteed that the result IQ contained a child element
// (RFC 6120 § 8.2.3 6) because otherwhise we would have reached the END_ELEMENT first.
@ -598,8 +622,8 @@ public class PacketParserUtils {
*
* @param parser the XML parser, positioned at the start of the mechanisms stanza.
* @return a collection of Stings with the mechanisms included in the mechanisms stanza.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occurred.
* @throws XmlPullParserException if an error in the XML parser occurred.
*/
public static Collection<String> parseMechanisms(XmlPullParser parser)
throws XmlPullParserException, IOException {
@ -628,7 +652,7 @@ public class PacketParserUtils {
*
* @param parser the XML parser, positioned at the start of the compression stanza.
* @return The CompressionFeature stream element
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
* @throws XmlPullParserException if an exception occurs while parsing the stanza.
*/
public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
@ -695,8 +719,8 @@ public class PacketParserUtils {
* @param parser the XML parser.
* @param outerXmlEnvironment the outer XML environment (optional).
* @return an stream error packet.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occurred.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
*/
public static StreamError parseStreamError(XmlPullParser parser, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
@ -747,7 +771,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);
}
@ -757,11 +781,11 @@ public class PacketParserUtils {
* @param parser the XML parser.
* @param outerXmlEnvironment the outer XML environment (optional).
* @return an error sub-packet.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occurred.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @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 +832,8 @@ public class PacketParserUtils {
}
}
builder.setExtensions(extensions).setDescriptiveTexts(descriptiveTexts);
return builder;
return builder.build();
}
/**
@ -820,8 +845,8 @@ public class PacketParserUtils {
* @param outerXmlEnvironment the outer XML environment (optional).
*
* @return an extension element.
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws IOException if an I/O error occurred.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
*/
public static ExtensionElement parseExtensionElement(String elementName, String namespace,
@ -898,6 +923,18 @@ public class PacketParserUtils {
return new Session.Feature(optional);
}
public static void addExtensionElement(StanzaBuilder<?> stanzaBuilder, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
ParserUtils.assertAtStartTag(parser);
addExtensionElement(stanzaBuilder, parser, parser.getName(), parser.getNamespace(), outerXmlEnvironment);
}
public static void addExtensionElement(StanzaBuilder<?> stanzaBuilder, XmlPullParser parser, String elementName,
String namespace, XmlEnvironment outerXmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
ExtensionElement extensionElement = parseExtensionElement(elementName, namespace, parser, outerXmlEnvironment);
stanzaBuilder.addExtension(extensionElement);
}
public static void addExtensionElement(Stanza packet, XmlPullParser parser, XmlEnvironment outerXmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
ParserUtils.assertAtStartTag(parser);

View File

@ -0,0 +1,24 @@
/**
*
* 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;
// TODO: Replace with java.util.function.Predicate once Smack's minimum Android SDK level is 24 or higher.
public interface Predicate<T> {
boolean test(T t);
}

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) {
@ -524,7 +535,7 @@ public class StringUtils {
if (cs == null) {
return null;
}
if (cs.toString().isEmpty()) {
if (isEmpty(cs)) {
throw new IllegalArgumentException(message);
}
return cs;

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

@ -628,7 +628,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
*
* @param writer TODO javadoc me please
* @param enclosingXmlEnvironment the enclosing XML environment.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
*/
public void write(Writer writer, XmlEnvironment enclosingXmlEnvironment) throws IOException {
try {

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());
}
}

View File

@ -120,18 +120,10 @@ public final class ChatMarkersManager extends Manager {
chatManager = ChatManager.getInstanceFor(connection);
connection.addStanzaInterceptor(new StanzaListener() {
@Override
public void processStanza(Stanza packet)
throws
NotConnectedException,
InterruptedException,
SmackException.NotLoggedInException {
Message message = (Message) packet;
// add a markable extension
message.addExtension(ChatMarkersElements.MarkableExtension.INSTANCE);
}
}, OUTGOING_MESSAGE_FILTER);
connection.addMessageInterceptor(mb -> mb.addExtension(ChatMarkersElements.MarkableExtension.INSTANCE),
m -> {
return OUTGOING_MESSAGE_FILTER.accept(m);
});
connection.addSyncStanzaListener(new StanzaListener() {
@Override

View File

@ -88,10 +88,11 @@ public final class DnsOverXmppManager extends Manager {
try {
response = resolver.resolve(query);
} catch (IOException exception) {
StanzaError.Builder errorBuilder = StanzaError.getBuilder()
StanzaError errorBuilder = StanzaError.getBuilder()
.setType(Type.CANCEL)
.setCondition(Condition.internal_server_error)
.setDescriptiveEnText("Exception while resolving your DNS query", exception)
.build()
;
IQ errorResponse = IQ.createErrorResponse(iqRequest, errorBuilder);

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus
* Copyright 2017-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,8 +20,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.MessageView;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
@ -33,6 +37,8 @@ public class ExplicitMessageEncryptionElement implements ExtensionElement {
public static final String NAMESPACE = "urn:xmpp:eme:0";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
public enum ExplicitMessageEncryptionProtocol {
/**
@ -154,14 +160,12 @@ public class ExplicitMessageEncryptionElement implements ExtensionElement {
* @param protocolNamespace namespace
* @return true if message has EME element for that namespace, otherwise false
*/
public static boolean hasProtocol(Message message, String protocolNamespace) {
List<ExtensionElement> extensionElements = message.getExtensions(
ExplicitMessageEncryptionElement.ELEMENT,
ExplicitMessageEncryptionElement.NAMESPACE);
public static boolean hasProtocol(MessageView message, String protocolNamespace) {
List<ExplicitMessageEncryptionElement> emeElements = message
.getExtensions(ExplicitMessageEncryptionElement.class);
for (ExtensionElement extensionElement : extensionElements) {
ExplicitMessageEncryptionElement e = (ExplicitMessageEncryptionElement) extensionElement;
if (e.getEncryptionNamespace().equals(protocolNamespace)) {
for (ExplicitMessageEncryptionElement emeElement : emeElements) {
if (emeElement.getEncryptionNamespace().equals(protocolNamespace)) {
return true;
}
}
@ -176,7 +180,7 @@ public class ExplicitMessageEncryptionElement implements ExtensionElement {
* @param protocol protocol
* @return true if message has EME element for that namespace, otherwise false
*/
public static boolean hasProtocol(Message message, ExplicitMessageEncryptionProtocol protocol) {
public static boolean hasProtocol(MessageView message, ExplicitMessageEncryptionProtocol protocol) {
return hasProtocol(message, protocol.namespace);
}
@ -184,10 +188,10 @@ public class ExplicitMessageEncryptionElement implements ExtensionElement {
* Add an EME element containing the specified {@code protocol} namespace to the message.
* In case there is already an element with that protocol, we do nothing.
*
* @param message message
* @param message a message builder.
* @param protocol encryption protocol
*/
public static void set(Message message, ExplicitMessageEncryptionProtocol protocol) {
public static void set(MessageBuilder message, ExplicitMessageEncryptionProtocol protocol) {
if (!hasProtocol(message, protocol.namespace)) {
message.addExtension(new ExplicitMessageEncryptionElement(protocol));
}

View File

@ -43,6 +43,7 @@ import java.util.WeakHashMap;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.util.SecurityUtil;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.hashes.element.HashElement;

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus
* Copyright 2017-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.
@ -16,7 +16,10 @@
*/
package org.jivesoftware.smackx.hints.element;
import org.jivesoftware.smack.packet.Message;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.MessageView;
/**
* A "store" hint. Messages with this hint should be stored in permanent stores or archives.
@ -29,6 +32,8 @@ public final class StoreHint extends MessageProcessingHint {
public static final String ELEMENT = "store";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private StoreHint() {
}
@ -47,15 +52,15 @@ public final class StoreHint extends MessageProcessingHint {
return MessageProcessingHintType.store;
}
public static StoreHint from(Message message) {
return message.getExtension(ELEMENT, NAMESPACE);
public static StoreHint from(MessageView message) {
return message.getExtension(QNAME);
}
public static boolean hasHint(Message message) {
public static boolean hasHint(MessageView message) {
return from(message) != null;
}
public static void set(Message message) {
public static void set(MessageBuilder message) {
message.overrideExtension(INSTANCE);
}
}

View File

@ -55,8 +55,8 @@ public abstract class AbstractHttpOverXmppProvider<H extends AbstractHttpOverXmp
*
* @param parser parser
* @return HeadersExtension or null if no headers
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws IOException if an I/O error occurred.
* @throws SmackParsingException if the Smack parser (provider) encountered invalid input.
*/
protected HeadersExtension parseHeaders(XmlPullParser parser) throws IOException, XmlPullParserException, SmackParsingException {
@ -76,8 +76,8 @@ public abstract class AbstractHttpOverXmppProvider<H extends AbstractHttpOverXmp
* @param parser parser
* @return Data or null if no data
*
* @throws XmlPullParserException if an error in the XML parser occured.
* @throws IOException if an I/O error occured.
* @throws XmlPullParserException if an error in the XML parser occurred.
* @throws IOException if an I/O error occurred.
*/
protected AbstractHttpOverXmpp.Data parseData(XmlPullParser parser) throws XmlPullParserException, IOException {
NamedElement child = null;

View File

@ -251,7 +251,7 @@ public final class HttpFileUploadManager extends Manager {
* @throws InterruptedException if the calling thread was interrupted.
* @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
* @throws SmackException if Smack detected an exceptional situation.
* @throws IOException if an I/O error occured.
* @throws IOException if an I/O error occurred.
*/
public URL uploadFile(File file, UploadProgressListener listener) throws InterruptedException,
XMPPException.XMPPErrorException, SmackException, IOException {

View File

@ -1,6 +1,6 @@
/**
*
* Copyright © 2016 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.
@ -16,20 +16,26 @@
*/
package org.jivesoftware.smackx.iot.control.element;
import org.jivesoftware.smack.packet.AbstractIqBuilder;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IqData;
public class IoTSetResponse extends IQ {
public static final String ELEMENT = "setResponse";
public static final String NAMESPACE = Constants.IOT_CONTROL_NAMESPACE;
public IoTSetResponse(IqData iqBuilder) {
super(iqBuilder, ELEMENT, NAMESPACE);
}
// TODO: Deprecate when stanza build is ready.
public IoTSetResponse() {
super(ELEMENT, NAMESPACE);
}
public IoTSetResponse(IoTSetRequest iotSetRequest) {
this();
initializeAsResultFor(iotSetRequest);
this(AbstractIqBuilder.createResponse(iotSetRequest));
}
@Override

View File

@ -125,10 +125,14 @@ public final class IoTDataManager extends IoTManager {
@Override
public void momentaryReadOut(List<? extends IoTDataField> results) {
IoTFieldsExtension iotFieldsExtension = IoTFieldsExtension.buildFor(dataRequest.getSequenceNr(), true, thing.getNodeInfo(), results);
Message message = new Message(dataRequest.getFrom());
message.addExtension(iotFieldsExtension);
XMPPConnection connection = connection();
Message message = connection.getStanzaFactory().buildMessageStanza()
.to(dataRequest.getFrom())
.addExtension(iotFieldsExtension)
.build();
try {
connection().sendStanza(message);
connection.sendStanza(message);
}
catch (NotConnectedException | InterruptedException e) {
LOGGER.log(Level.SEVERE, "Could not send read-out response " + message, e);

View File

@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.iot.element.NodeInfo;
public class NodeElement extends IoTDataExtensionElement {

View File

@ -140,8 +140,10 @@ public final class IoTProvisioningManager extends Manager {
+ " is already not subscribed to our presence.");
return;
}
Presence unsubscribed = new Presence(Presence.Type.unsubscribed);
unsubscribed.setTo(unfriendJid);
Presence unsubscribed = connection.getStanzaFactory().buildPresenceStanza()
.ofType(Presence.Type.unsubscribed)
.to(unfriendJid)
.build();
connection.sendStanza(unsubscribed);
}
}, UNFRIEND_MESSAGE);
@ -162,7 +164,10 @@ public final class IoTProvisioningManager extends Manager {
// friendship requests.
final XMPPConnection connection = connection();
Friend friendNotification = new Friend(connection.getUser().asBareJid());
Message notificationMessage = new Message(friendJid, friendNotification);
Message notificationMessage = connection.getStanzaFactory().buildMessageStanza()
.to(friendJid)
.addExtension(friendNotification)
.build();
connection.sendStanza(notificationMessage);
} else {
// Check is the message was send from a thing we previously
@ -359,8 +364,11 @@ public final class IoTProvisioningManager extends Manager {
}
public void sendFriendshipRequest(BareJid bareJid) throws NotConnectedException, InterruptedException {
Presence presence = new Presence(Presence.Type.subscribe);
presence.setTo(bareJid);
XMPPConnection connection = connection();
Presence presence = connection.getStanzaFactory().buildPresenceStanza()
.ofType(Presence.Type.subscribe)
.to(bareJid)
.build();
friendshipRequestedCache.put(bareJid, null);
@ -379,9 +387,12 @@ public final class IoTProvisioningManager extends Manager {
public void unfriend(Jid friend) throws NotConnectedException, InterruptedException {
if (isMyFriend(friend)) {
Presence presence = new Presence(Presence.Type.unsubscribed);
presence.setTo(friend);
connection().sendStanza(presence);
XMPPConnection connection = connection();
Presence presence = connection.getStanzaFactory().buildPresenceStanza()
.ofType(Presence.Type.unsubscribed)
.to(friend)
.build();
connection.sendStanza(presence);
}
}

View File

@ -23,6 +23,7 @@ import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.jid_prep.element.JidPrepIq;
public class JidPrepIqProvider extends IQProvider<JidPrepIq> {

View File

@ -35,6 +35,7 @@ import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.muclight.element.MUCLightAffiliationsIQ;
@ -126,9 +127,9 @@ public class MultiUserChatLight {
* @throws InterruptedException if the calling thread was interrupted.
*/
public void sendMessage(String text) throws NotConnectedException, InterruptedException {
Message message = createMessage();
MessageBuilder message = buildMessage();
message.setBody(text);
connection.sendStanza(message);
connection.sendStanza(message.build());
}
/**
@ -157,9 +158,28 @@ public class MultiUserChatLight {
* Creates a new Message to send to the chat room.
*
* @return a new Message addressed to the chat room.
* @deprecated use {@link #buildMessage()} instead.
*/
@Deprecated
// TODO: Remove when stanza builder is ready.
public Message createMessage() {
return new Message(room, Message.Type.groupchat);
return connection.getStanzaFactory().buildMessageStanza()
.ofType(Message.Type.groupchat)
.to(room)
.build();
}
/**
* Constructs a new message builder for messages send to this MUC room.
*
* @return a new message builder.
*/
public MessageBuilder buildMessage() {
return connection.getStanzaFactory()
.buildMessageStanza()
.ofType(Message.Type.groupchat)
.to(room)
;
}
/**
@ -169,10 +189,23 @@ public class MultiUserChatLight {
* the message.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @deprecated use {@link #sendMessage(MessageBuilder)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public void sendMessage(Message message) throws NotConnectedException, InterruptedException {
message.setTo(room);
message.setType(Message.Type.groupchat);
sendMessage(message.asBuilder());
}
/**
* Sends a Message to the chat room.
*
* @param messageBuilder the message.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void sendMessage(MessageBuilder messageBuilder) throws NotConnectedException, InterruptedException {
Message message = messageBuilder.to(room).ofType(Message.Type.groupchat).build();
connection.sendStanza(message);
}

View File

@ -50,7 +50,9 @@ public class RemoteDisablingProvider extends ExtensionElementProvider<RemoteDisa
String affiliation = parser.getAttributeValue("", "affiliation");
if (affiliation == null || !affiliation.equals("none")) {
return null;
// TODO: Is this correct? We previously returned null here, but was certainly wrong, as
// providers should always return an element or throw.
throw new IOException("Invalid affiliation: " + affiliation);
}
}
} else if (eventType == XmlPullParser.Event.END_ELEMENT) {

View File

@ -21,7 +21,6 @@ import java.util.WeakHashMap;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.filter.AndFilter;
@ -31,7 +30,9 @@ import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.ToTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.Predicate;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
@ -62,12 +63,12 @@ public final class StableUniqueStanzaIdManager extends Manager {
private static final StanzaFilter ORIGIN_ID_FILTER = new StanzaExtensionFilter(OriginIdElement.ELEMENT, NAMESPACE);
// Listener for outgoing stanzas that adds origin-ids to outgoing stanzas.
private static final StanzaListener ADD_ORIGIN_ID_INTERCEPTOR = new StanzaListener() {
@Override
public void processStanza(Stanza stanza) {
Message message = (Message) stanza;
OriginIdElement.addOriginId(message);
}
private static final Consumer<MessageBuilder> ADD_ORIGIN_ID_INTERCEPTOR = mb -> OriginIdElement.addOriginId(mb);
// We need a filter for outgoing messages that do not carry an origin-id already.
private static final StanzaFilter ADD_ORIGIN_ID_FILTER = new AndFilter(OUTGOING_FILTER, new NotFilter(ORIGIN_ID_FILTER));
private static final Predicate<Message> ADD_ORIGIN_ID_PREDICATE = m -> {
return ADD_ORIGIN_ID_FILTER.accept(m);
};
static {
@ -112,10 +113,8 @@ public final class StableUniqueStanzaIdManager extends Manager {
* Start appending origin-id elements to outgoing stanzas and add the feature to disco.
*/
public synchronized void enable() {
connection().addMessageInterceptor(ADD_ORIGIN_ID_INTERCEPTOR, ADD_ORIGIN_ID_PREDICATE);
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
// We need a filter for outgoing messages that do not carry an origin-id already
StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(ORIGIN_ID_FILTER));
connection().addStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR, filter);
}
/**
@ -123,7 +122,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
*/
public synchronized void disable() {
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE);
connection().removeStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR);
connection().removeMessageInterceptor(ADD_ORIGIN_ID_INTERCEPTOR);
}
/**

View File

@ -17,6 +17,7 @@
package org.jivesoftware.smackx.sid.element;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;
@ -38,7 +39,10 @@ public class OriginIdElement extends StableAndUniqueIdElement {
*
* @param message message.
* @return the added origin-id element.
* @deprecated use {@link #addOriginId(MessageBuilder)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public static OriginIdElement addOriginId(Message message) {
OriginIdElement originId = new OriginIdElement();
message.addExtension(originId);
@ -47,6 +51,20 @@ public class OriginIdElement extends StableAndUniqueIdElement {
return originId;
}
/**
* Add an origin-id element to a message and set the stanzas id to the same id as in the origin-id element.
*
* @param messageBuilder the message builder to add an origin ID to.
* @return the added origin-id element.
*/
public static OriginIdElement addOriginId(MessageBuilder messageBuilder) {
OriginIdElement originId = new OriginIdElement();
messageBuilder.addExtension(originId);
// TODO: Find solution to have both the originIds stanzaId and a nice to look at incremental stanzaID.
// message.setStanzaId(originId.getId());
return originId;
}
/**
* Return true, if the message contains a origin-id element.
*

View File

@ -21,6 +21,7 @@ import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.StandardExtensionElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.FormFieldChildElement;

View File

@ -26,6 +26,7 @@ import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.xdata.provider.FormFieldChildElementProvider;
import org.jivesoftware.smackx.xmlelement.element.DataFormsXmlElement;

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smackx.chat_markers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -28,7 +29,6 @@ import org.jivesoftware.smackx.chat_markers.element.ChatMarkersElements.Acknowle
import org.jivesoftware.smackx.chat_markers.provider.AcknowledgedProvider;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.impl.JidCreate;
public class AcknowledgedExtensionTest {
@ -39,9 +39,10 @@ public class AcknowledgedExtensionTest {
@Test
public void checkDisplayedExtension() throws Exception {
Message message = new Message(JidCreate.from("northumberland@shakespeare.lit/westminster"));
message.setStanzaId("message-2");
message.addExtension(new ChatMarkersElements.AcknowledgedExtension("message-1"));
Message message = StanzaBuilder.buildMessage("message-2")
.to("northumberland@shakespeare.lit/westminster")
.addExtension(new ChatMarkersElements.AcknowledgedExtension("message-1"))
.build();
assertEquals(acknowledgedMessageStanza, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smackx.chat_markers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -28,7 +29,6 @@ import org.jivesoftware.smackx.chat_markers.element.ChatMarkersElements.Displaye
import org.jivesoftware.smackx.chat_markers.provider.DisplayedProvider;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.impl.JidCreate;
public class DisplayedExtensionTest {
@ -39,9 +39,10 @@ public class DisplayedExtensionTest {
@Test
public void checkDisplayedExtension() throws Exception {
Message message = new Message(JidCreate.from("northumberland@shakespeare.lit/westminster"));
message.setStanzaId("message-2");
message.addExtension(new ChatMarkersElements.DisplayedExtension("message-1"));
Message message = StanzaBuilder.buildMessage("message-2")
.to("northumberland@shakespeare.lit/westminster")
.addExtension(new ChatMarkersElements.DisplayedExtension("message-1"))
.build();
assertEquals(displayedMessageStanza, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smackx.chat_markers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -39,10 +40,12 @@ public class MarkableExtensionTest {
@Test
public void checkMarkableExtension() throws Exception {
Message message = new Message(JidCreate.from("ingrichard@royalty.england.lit/throne"));
message.setStanzaId("message-1");
message.setBody("My lord, dispatch; read o'er these articles.");
message.addExtension(ChatMarkersElements.MarkableExtension.INSTANCE);
Message message = StanzaBuilder.buildMessage("message-1")
.to(JidCreate.from("ingrichard@royalty.england.lit/throne"))
.setBody("My lord, dispatch; read o'er these articles.")
.addExtension(ChatMarkersElements.MarkableExtension.INSTANCE)
.build();
assertEquals(markableMessageStanza, message.toXML().toString());
}

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smackx.chat_markers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -28,7 +29,6 @@ import org.jivesoftware.smackx.chat_markers.element.ChatMarkersElements.Received
import org.jivesoftware.smackx.chat_markers.provider.ReceivedProvider;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.impl.JidCreate;
public class ReceivedExtensionTest {
@ -39,9 +39,10 @@ public class ReceivedExtensionTest {
@Test
public void checkReceivedExtension() throws Exception {
Message message = new Message(JidCreate.from("northumberland@shakespeare.lit/westminster"));
message.setStanzaId("message-2");
message.addExtension(new ChatMarkersElements.ReceivedExtension("message-1"));
Message message = StanzaBuilder.buildMessage("message-2")
.to("northumberland@shakespeare.lit/westminster")
.addExtension(new ChatMarkersElements.ReceivedExtension("message-1"))
.build();
assertEquals(receivedMessageStanza, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
}

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smackx.colors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.colors.ConsistentColor.Deficiency;
import org.junit.jupiter.api.Test;

View File

@ -25,6 +25,8 @@ import java.util.List;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
@ -35,7 +37,7 @@ public class ExplicitMessageEncryptionElementTest extends SmackTestSuite {
@Test
public void addToMessageTest() {
Message message = new Message();
Message message = StanzaBuilder.buildMessage().build();
// Check inital state (no elements)
assertNull(ExplicitMessageEncryptionElement.from(message));
@ -45,9 +47,12 @@ public class ExplicitMessageEncryptionElementTest extends SmackTestSuite {
List<ExtensionElement> extensions = message.getExtensions();
assertEquals(0, extensions.size());
MessageBuilder messageBuilder = StanzaBuilder.buildMessage();
// Add OMEMO
ExplicitMessageEncryptionElement.set(message,
ExplicitMessageEncryptionElement.set(messageBuilder,
ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.omemoVAxolotl);
message = messageBuilder.build();
extensions = message.getExtensions();
assertEquals(1, extensions.size());
assertTrue(ExplicitMessageEncryptionElement.hasProtocol(message,
@ -59,8 +64,10 @@ public class ExplicitMessageEncryptionElementTest extends SmackTestSuite {
assertFalse(ExplicitMessageEncryptionElement.hasProtocol(message,
ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.openpgpV0.getNamespace()));
ExplicitMessageEncryptionElement.set(message,
ExplicitMessageEncryptionElement.set(messageBuilder,
ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.openpgpV0);
message = messageBuilder.build();
extensions = message.getExtensions();
assertEquals(2, extensions.size());
assertTrue(ExplicitMessageEncryptionElement.hasProtocol(message,
@ -69,9 +76,10 @@ public class ExplicitMessageEncryptionElementTest extends SmackTestSuite {
ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.omemoVAxolotl));
// Check, if adding additional OMEMO wont add another element
ExplicitMessageEncryptionElement.set(message,
ExplicitMessageEncryptionElement.set(messageBuilder,
ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.omemoVAxolotl);
message = messageBuilder.build();
extensions = message.getExtensions();
assertEquals(2, extensions.size());
}

View File

@ -25,6 +25,7 @@ import java.util.TimeZone;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Message.Type;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
@ -62,21 +63,21 @@ public class QueryArchiveTest extends MamTest {
@Test
public void checkMamQueryResults() throws Exception {
Message message = new Message();
message.setStanzaId("iasd207");
message.setFrom(JidCreate.from("coven@chat.shakespeare.lit"));
message.setTo(JidCreate.from("hag66@shakespeare.lit/pda"));
Message message = StanzaBuilder.buildMessage("iasd207")
.from("coven@chat.shakespeare.lit")
.to("hag66@shakespeare.lit/pda")
.build();
GregorianCalendar calendar = new GregorianCalendar(2002, 10 - 1, 13, 23, 58, 37);
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = calendar.getTime();
DelayInformation delay = new DelayInformation(date);
Message forwardedMessage = new Message();
forwardedMessage.setFrom(JidCreate.from("coven@chat.shakespeare.lit/firstwitch"));
forwardedMessage.setStanzaId("162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2");
forwardedMessage.setType(Type.chat);
forwardedMessage.setBody("Thrice the brinded cat hath mew.");
Message forwardedMessage = StanzaBuilder.buildMessage("162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2")
.from(JidCreate.from("coven@chat.shakespeare.lit/firstwitch"))
.ofType(Type.chat)
.setBody("Thrice the brinded cat hath mew.")
.build();
Forwarded forwarded = new Forwarded(delay, forwardedMessage);

Some files were not shown because too many files have changed in this diff Show More