mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-12-09 06:31:08 +01:00
Introduce test fixtures
This also removes the powermock dependency. Although powermock is a fine library, it currently prevents dropping Junit4. And since we only use the Whitebox API of powermock, this simply replaced powermock's Whitebox with our own.
This commit is contained in:
parent
4a99f7252c
commit
b5f9d4d7a3
51 changed files with 123 additions and 80 deletions
|
|
@ -1,240 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2010 Jive Software.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Nonza;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||
|
||||
import org.jxmpp.jid.EntityFullJid;
|
||||
import org.jxmpp.jid.JidTestUtil;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.jid.parts.Resourcepart;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
|
||||
/**
|
||||
* A dummy implementation of {@link XMPPConnection}, intended to be used during
|
||||
* unit tests.
|
||||
*
|
||||
* Instances store any packets that are delivered to be send using the
|
||||
* {@link #sendStanza(Stanza)} method in a blocking queue. The content of this queue
|
||||
* can be inspected using {@link #getSentPacket()}. Typically these queues are
|
||||
* used to retrieve a message that was generated by the client.
|
||||
*
|
||||
* Packets that should be processed by the client to simulate a received stanza
|
||||
* can be delivered using the {@linkplain #processStanza(Stanza)} method.
|
||||
* It invokes the registered stanza interceptors and listeners.
|
||||
*
|
||||
* @see XMPPConnection
|
||||
* @author Guenther Niess
|
||||
*/
|
||||
public class DummyConnection extends AbstractXMPPConnection {
|
||||
|
||||
private final BlockingQueue<TopLevelStreamElement> queue = new LinkedBlockingQueue<TopLevelStreamElement>();
|
||||
|
||||
public static DummyConnectionConfiguration.Builder getDummyConfigurationBuilder() {
|
||||
return DummyConnectionConfiguration.builder().setXmppDomain(JidTestUtil.EXAMPLE_ORG).setUsernameAndPassword("dummy",
|
||||
"dummypass");
|
||||
}
|
||||
|
||||
public DummyConnection() {
|
||||
this(getDummyConfigurationBuilder().build());
|
||||
}
|
||||
|
||||
public DummyConnection(CharSequence username, String password, String serviceName) throws XmppStringprepException {
|
||||
this(getDummyConfigurationBuilder().setUsernameAndPassword(username, password).setXmppDomain(
|
||||
JidCreate.domainBareFrom(serviceName)).build());
|
||||
}
|
||||
|
||||
private EntityFullJid getUserJid() {
|
||||
try {
|
||||
return JidCreate.entityFullFrom(config.getUsername()
|
||||
+ "@"
|
||||
+ config.getXMPPServiceDomain()
|
||||
+ "/"
|
||||
+ (config.getResource() != null ? config.getResource() : "Test"));
|
||||
}
|
||||
catch (XmppStringprepException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public DummyConnection(DummyConnectionConfiguration configuration) {
|
||||
super(configuration);
|
||||
|
||||
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
|
||||
listener.connectionCreated(this);
|
||||
}
|
||||
user = getUserJid();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectInternal() {
|
||||
connected = true;
|
||||
streamId = "dummy-" + new Random(new Date().getTime()).nextInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutdown() {
|
||||
user = null;
|
||||
authenticated = false;
|
||||
|
||||
callConnectionClosedListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void instantShutdown() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecureConnection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsingCompression() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loginInternal(String username, String password, Resourcepart resource)
|
||||
throws XMPPException {
|
||||
user = getUserJid();
|
||||
authenticated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendNonza(Nonza element) {
|
||||
queue.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendStanzaInternal(Stanza packet) {
|
||||
queue.add(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of packets that's sent through {@link #sendStanza(Stanza)} and
|
||||
* that has not been returned by {@link #getSentPacket()}.
|
||||
*
|
||||
* @return the number of packets which are in the queue.
|
||||
*/
|
||||
public int getNumberOfSentPackets() {
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first stanza that's sent through {@link #sendStanza(Stanza)}
|
||||
* and that has not been returned by earlier calls to this method.
|
||||
*
|
||||
* @return a sent packet.
|
||||
*/
|
||||
public <P extends TopLevelStreamElement> P getSentPacket() {
|
||||
return getSentPacket(5 * 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first stanza that's sent through {@link #sendStanza(Stanza)}
|
||||
* and that has not been returned by earlier calls to this method. This
|
||||
* method will block for up to the specified number of seconds if no packets
|
||||
* have been sent yet.
|
||||
*
|
||||
* @return a sent packet.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <P extends TopLevelStreamElement> P getSentPacket(int wait) {
|
||||
try {
|
||||
return (P) queue.poll(wait, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a stanza through the installed stanza collectors and listeners
|
||||
* and letting them examine the stanza to see if they are a match with the
|
||||
* filter.
|
||||
*
|
||||
* @param packet the stanza to process.
|
||||
*/
|
||||
@Override
|
||||
public void processStanza(Stanza packet) {
|
||||
invokeStanzaCollectorsAndNotifyRecvListeners(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable stream feature.
|
||||
*
|
||||
* @param streamFeature the stream feature.
|
||||
* @since 4.2
|
||||
*/
|
||||
public void enableStreamFeature(ExtensionElement streamFeature) {
|
||||
addStreamFeature(streamFeature);
|
||||
}
|
||||
|
||||
public static DummyConnection newConnectedDummyConnection() {
|
||||
DummyConnection dummyConnection = new DummyConnection();
|
||||
try {
|
||||
dummyConnection.connect();
|
||||
dummyConnection.login();
|
||||
}
|
||||
catch (InterruptedException | SmackException | IOException | XMPPException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return dummyConnection;
|
||||
}
|
||||
|
||||
public static class DummyConnectionConfiguration extends ConnectionConfiguration {
|
||||
protected DummyConnectionConfiguration(Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder
|
||||
extends
|
||||
ConnectionConfiguration.Builder<Builder, DummyConnectionConfiguration> {
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DummyConnectionConfiguration build() {
|
||||
return new DummyConnectionConfiguration(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Builder getThis() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
|
||||
/**
|
||||
* A threaded dummy connection.
|
||||
* @author Robin Collier
|
||||
*
|
||||
*/
|
||||
public class ThreadedDummyConnection extends DummyConnection {
|
||||
private static final Logger LOGGER = Logger.getLogger(ThreadedDummyConnection.class.getName());
|
||||
|
||||
private final BlockingQueue<IQ> replyQ = new ArrayBlockingQueue<>(1);
|
||||
private final BlockingQueue<Stanza> messageQ = new LinkedBlockingQueue<>(5);
|
||||
private volatile boolean timeout = false;
|
||||
|
||||
@Override
|
||||
protected void sendStanzaInternal(Stanza packet) {
|
||||
super.sendStanzaInternal(packet);
|
||||
|
||||
if (packet instanceof IQ && !timeout) {
|
||||
timeout = false;
|
||||
// Set reply packet to match one being sent. We haven't started the
|
||||
// other thread yet so this is still safe.
|
||||
IQ replyPacket = replyQ.peek();
|
||||
|
||||
// If no reply has been set via addIQReply, then we create a simple reply
|
||||
if (replyPacket == null) {
|
||||
replyPacket = IQ.createResultIQ((IQ) packet);
|
||||
replyQ.add(replyPacket);
|
||||
}
|
||||
replyPacket.setStanzaId(packet.getStanzaId());
|
||||
replyPacket.setTo(packet.getFrom());
|
||||
if (replyPacket.getType() == null) {
|
||||
replyPacket.setType(Type.result);
|
||||
}
|
||||
|
||||
new ProcessQueue(replyQ).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling this method will cause the next sendStanza call with an IQ stanza to timeout.
|
||||
* This is accomplished by simply stopping the auto creating of the reply stanza
|
||||
* or processing one that was entered via {@link #processStanza(Stanza)}.
|
||||
*/
|
||||
public void setTimeout() {
|
||||
timeout = true;
|
||||
}
|
||||
|
||||
public void addMessage(Message msgToProcess) {
|
||||
messageQ.add(msgToProcess);
|
||||
}
|
||||
|
||||
public void addIQReply(IQ reply) {
|
||||
replyQ.add(reply);
|
||||
}
|
||||
|
||||
public void processMessages() {
|
||||
if (!messageQ.isEmpty())
|
||||
new ProcessQueue(messageQ).start();
|
||||
else
|
||||
LOGGER.warning("No messages to process");
|
||||
}
|
||||
|
||||
class ProcessQueue extends Thread {
|
||||
private BlockingQueue<? extends Stanza> processQ;
|
||||
|
||||
ProcessQueue(BlockingQueue<? extends Stanza> queue) {
|
||||
processQ = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
processStanza(processQ.take());
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.log(Level.WARNING, "exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ThreadedDummyConnection newInstance() throws SmackException, IOException, XMPPException, InterruptedException {
|
||||
ThreadedDummyConnection threadedDummyConnection = new ThreadedDummyConnection();
|
||||
threadedDummyConnection.connect();
|
||||
return threadedDummyConnection;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014-2016 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.sasl;
|
||||
|
||||
import org.jivesoftware.smack.DummyConnection;
|
||||
|
||||
public class AbstractSaslTest {
|
||||
|
||||
protected final DummyConnection xmppConnection = new DummyConnection();
|
||||
protected final SASLMechanism saslMechanism;
|
||||
|
||||
protected AbstractSaslTest(SASLMechanism saslMechanism) {
|
||||
this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection, xmppConnection.getConfiguration());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.sasl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
|
||||
public class DigestMd5SaslTest extends AbstractSaslTest {
|
||||
|
||||
protected static final String challenge = "realm=\"xmpp.org\",nonce=\"jgGgnz+cQcmyVaAs2n88kQ==\",qop=\"auth\",charset=utf-8,algorithm=md5-sess";
|
||||
protected static final byte[] challengeBytes = StringUtils.toUtf8Bytes(challenge);
|
||||
|
||||
public DigestMd5SaslTest(SASLMechanism saslMechanism) {
|
||||
super(saslMechanism);
|
||||
}
|
||||
|
||||
protected void runTest(boolean useAuthzid) throws SmackException, InterruptedException, XmppStringprepException {
|
||||
EntityBareJid authzid = null;
|
||||
if (useAuthzid) {
|
||||
authzid = JidCreate.entityBareFrom("shazbat@xmpp.org");
|
||||
}
|
||||
saslMechanism.authenticate("florian", "irrelevant", JidCreate.domainBareFrom("xmpp.org"), "secret", authzid, null);
|
||||
byte[] response = saslMechanism.evaluateChallenge(challengeBytes);
|
||||
String responseString = new String(response, StandardCharsets.UTF_8);
|
||||
String[] responseParts = responseString.split(",");
|
||||
Map<String, String> responsePairs = new HashMap<String, String>();
|
||||
for (String part : responseParts) {
|
||||
String[] keyValue = part.split("=", 2);
|
||||
String key = keyValue[0];
|
||||
String value = keyValue[1].replace("\"", "");
|
||||
responsePairs.put(key, value);
|
||||
}
|
||||
if (useAuthzid) {
|
||||
assertMapValue("authzid", "shazbat@xmpp.org", responsePairs);
|
||||
} else {
|
||||
assertTrue (!responsePairs.containsKey("authzid"));
|
||||
}
|
||||
assertMapValue("username", "florian", responsePairs);
|
||||
assertMapValue("realm", "xmpp.org", responsePairs);
|
||||
assertMapValue("digest-uri", "xmpp/xmpp.org", responsePairs);
|
||||
assertMapValue("qop", "auth", responsePairs);
|
||||
}
|
||||
|
||||
private static void assertMapValue(String key, String value, Map<String, String> map) {
|
||||
assertEquals(value, map.get(key));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.test.util;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Factory;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
public class CharSequenceEquals extends TypeSafeMatcher<CharSequence> {
|
||||
|
||||
private final String charSequenceString;
|
||||
|
||||
public CharSequenceEquals(CharSequence charSequence) {
|
||||
charSequenceString = charSequence.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Does not match CharSequence ").appendValue(charSequenceString);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(CharSequence item) {
|
||||
String itemString = item.toString();
|
||||
return charSequenceString.equals(itemString);
|
||||
}
|
||||
|
||||
@Factory
|
||||
public static Matcher<CharSequence> equalsCharSequence(CharSequence charSequence) {
|
||||
return new CharSequenceEquals(charSequence);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014-2020 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.test.util;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.jivesoftware.smack.SmackConfiguration;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64.Encoder;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
/**
|
||||
* The SmackTestSuite takes care of initializing Smack for the unit tests. For example the Base64
|
||||
* encoder is configured.
|
||||
*/
|
||||
public class SmackTestSuite {
|
||||
|
||||
static {
|
||||
SmackConfiguration.getVersion();
|
||||
org.jivesoftware.smack.util.stringencoder.Base64.setEncoder(new Encoder() {
|
||||
|
||||
@Override
|
||||
public byte[] decode(String string) {
|
||||
return Base64.getDecoder().decode(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeToString(byte[] input) {
|
||||
return Base64.getEncoder().encodeToString(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeToStringWithoutPadding(byte[] input) {
|
||||
return Base64.getEncoder().withoutPadding().encodeToString(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(byte[] input) {
|
||||
return Base64.getEncoder().encode(input);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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.test.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||
import org.jivesoftware.smack.provider.Provider;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserFactory;
|
||||
import org.jivesoftware.smack.xml.stax.StaxXmlPullParserFactory;
|
||||
import org.jivesoftware.smack.xml.xpp3.Xpp3XmlPullParserFactory;
|
||||
|
||||
public class SmackTestUtil {
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker")
|
||||
public enum XmlPullParserKind {
|
||||
StAX(StaxXmlPullParserFactory.class),
|
||||
XPP3(Xpp3XmlPullParserFactory.class),
|
||||
;
|
||||
|
||||
public final XmlPullParserFactory factory;
|
||||
|
||||
XmlPullParserKind(Class<? extends XmlPullParserFactory> factoryClass) {
|
||||
try {
|
||||
factory = factoryClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends Element, P extends Provider<E>> E parse(CharSequence xml, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
P provider = providerClassToProvider(providerClass);
|
||||
return parse(xml, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element, P extends Provider<E>> E parse(InputStream inputStream, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
P provider = providerClassToProvider(providerClass);
|
||||
return parse(inputStream, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element, P extends Provider<E>> E parse(Reader reader, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
P provider = providerClassToProvider(providerClass);
|
||||
return parse(reader, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element> E parse(CharSequence xml, Provider<E> provider, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
String xmlString = xml.toString();
|
||||
Reader reader = new StringReader(xmlString);
|
||||
return parse(reader, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element> E parse(InputStream inputStream, Provider<E> provider, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
return parse(inputStreamReader, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element> E parse(Reader reader, Provider<E> provider, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
XmlPullParser parser = getParserFor(reader, parserKind);
|
||||
E element = provider.parse(parser);
|
||||
return element;
|
||||
}
|
||||
|
||||
public static XmlPullParser getParserFor(String xml, XmlPullParserKind parserKind) throws XmlPullParserException, IOException {
|
||||
Reader reader = new StringReader(xml);
|
||||
return getParserFor(reader, parserKind);
|
||||
}
|
||||
|
||||
public static XmlPullParser getParserFor(InputStream inputStream, XmlPullParserKind parserKind) throws XmlPullParserException, IOException {
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
return getParserFor(inputStreamReader, parserKind);
|
||||
}
|
||||
|
||||
public static XmlPullParser getParserFor(Reader reader, XmlPullParserKind parserKind) throws XmlPullParserException, IOException {
|
||||
XmlPullParser parser = parserKind.factory.newXmlPullParser(reader);
|
||||
forwardParserToStartElement(parser);
|
||||
return parser;
|
||||
}
|
||||
|
||||
public static XmlPullParser getParserFor(String xml, QName startTagQName, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException {
|
||||
XmlPullParser parser = getParserFor(xml, parserKind);
|
||||
forwardParserToStartElement(parser, p -> p.getQName().equals(startTagQName));
|
||||
return parser;
|
||||
}
|
||||
|
||||
public static XmlPullParser getParserFor(String xml, String startTagLocalpart, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException {
|
||||
XmlPullParser parser = getParserFor(xml, parserKind);
|
||||
forwardParserToStartElement(parser, p -> p.getName().equals(startTagLocalpart));
|
||||
return parser;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Element, P extends Provider<E>> P providerClassToProvider(Class<P> providerClass) {
|
||||
P provider;
|
||||
|
||||
try {
|
||||
provider = (P) providerClass.getDeclaredField("INSTANCE").get(null);
|
||||
return provider;
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Continue with the next approach.
|
||||
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
provider = providerClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
|
||||
| NoSuchMethodException | SecurityException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static void forwardParserToStartElement(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
forwardParserToStartElement(parser, p -> true);
|
||||
}
|
||||
|
||||
private static void forwardParserToStartElement(XmlPullParser parser,
|
||||
Predicate<XmlPullParser> doneForwarding) throws XmlPullParserException, IOException {
|
||||
outerloop: while (true) {
|
||||
XmlPullParser.Event event = parser.getEventType();
|
||||
switch (event) {
|
||||
case START_ELEMENT:
|
||||
if (doneForwarding.test(parser)) {
|
||||
break outerloop;
|
||||
}
|
||||
break;
|
||||
case END_DOCUMENT:
|
||||
throw new IllegalArgumentException("Not matching START_ELEMENT found");
|
||||
default:
|
||||
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
|
||||
break;
|
||||
}
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2013 Robin Collier
|
||||
*
|
||||
* 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.test.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.jivesoftware.smack.xml.SmackXmlParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
|
||||
// TODO: Remove this class and replace it with SmackTestUtil.
|
||||
public final class TestUtils {
|
||||
private TestUtils() {
|
||||
}
|
||||
|
||||
public static XmlPullParser getIQParser(String stanza) {
|
||||
return getParser(stanza, "iq");
|
||||
}
|
||||
|
||||
public static XmlPullParser getMessageParser(String stanza) {
|
||||
return getParser(stanza, "message");
|
||||
}
|
||||
|
||||
public static XmlPullParser getPresenceParser(String stanza) {
|
||||
return getParser(stanza, "presence");
|
||||
}
|
||||
|
||||
public static XmlPullParser getParser(String string) {
|
||||
return getParser(string, null);
|
||||
}
|
||||
|
||||
public static XmlPullParser getParser(String string, String startTag) {
|
||||
return getParser(new StringReader(string), startTag);
|
||||
}
|
||||
|
||||
private static XmlPullParser getParser(Reader reader, String startTag) {
|
||||
XmlPullParser parser;
|
||||
try {
|
||||
parser = SmackXmlParser.newXmlParser(reader);
|
||||
if (startTag == null) {
|
||||
while (parser.getEventType() != XmlPullParser.Event.START_ELEMENT) {
|
||||
parser.next();
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
if ((parser.next() == XmlPullParser.Event.START_ELEMENT) && parser.getName().equals(startTag))
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
throw new IllegalArgumentException("Can not find start tag '" + startTag + "'");
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2015 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.test.util;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.StanzaListener;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
|
||||
public class WaitForPacketListener implements StanzaListener {
|
||||
|
||||
private CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void processStanza(Stanza packet) throws NotConnectedException {
|
||||
reportInvoked();
|
||||
}
|
||||
|
||||
protected void reportInvoked() {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void waitAndReset() {
|
||||
waitUntilInvocationOrTimeout();
|
||||
reset();
|
||||
}
|
||||
|
||||
public void waitUntilInvocationOrTimeout() {
|
||||
try {
|
||||
boolean res = latch.await(300, TimeUnit.SECONDS);
|
||||
if (!res) {
|
||||
throw new IllegalStateException("Latch timed out before it reached zero");
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
latch = new CountDownLatch(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.test.util;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.xmlunit.assertj.CompareAssert;
|
||||
import org.xmlunit.assertj.XmlAssert;
|
||||
import org.xmlunit.diff.DefaultNodeMatcher;
|
||||
import org.xmlunit.diff.ElementSelectors;
|
||||
import org.xmlunit.input.NormalizedSource;
|
||||
|
||||
// TODO: Rename this class to XmlAssertUtil
|
||||
public class XmlUnitUtils {
|
||||
|
||||
public static void assertXmlNotSimilar(CharSequence xmlOne, CharSequence xmlTwo) {
|
||||
normalizedCompare(xmlOne, xmlTwo).areNotSimilar();
|
||||
}
|
||||
|
||||
public static void assertXmlSimilar(CharSequence expected, CharSequence actual) {
|
||||
normalizedCompare(expected, actual).areSimilar();
|
||||
}
|
||||
|
||||
private static CompareAssert normalizedCompare(CharSequence expectedCharSequence, CharSequence actualCharSequence) {
|
||||
String expectedString = expectedCharSequence.toString();
|
||||
String actualString = actualCharSequence.toString();
|
||||
|
||||
NormalizedSource expected = new NormalizedSource(new StreamSource(new StringReader(expectedString)));
|
||||
NormalizedSource actual = new NormalizedSource(new StreamSource(new StringReader(actualString)));
|
||||
return XmlAssert.assertThat(actual).and(expected)
|
||||
.ignoreChildNodesOrder()
|
||||
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndAllAttributes, ElementSelectors.byNameAndText))
|
||||
.normalizeWhitespace();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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 static org.junit.Assert.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.lang.ref.PhantomReference;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.DummyConnection;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
|
||||
/**
|
||||
* Utility class to test for memory leaks caused by Smack.
|
||||
* <p>
|
||||
* Note that this test is based on the assumption that it is possible to trigger a full garbage collection run, which is
|
||||
* not the case. See also this
|
||||
* <a href="https://stackoverflow.com/questions/1481178/how-to-force-garbage-collection-in-java">stackoverflow
|
||||
* question</a>. Hence the {@link #triggerGarbageCollection()} method defined in this class is not portable and depends
|
||||
* on implementation depended Java Virtual Machine behavior.
|
||||
* </p>
|
||||
*
|
||||
* @see <a href="https://issues.igniterealtime.org/browse/SMACK-383">SMACK-383 Jira Issue</a>
|
||||
*/
|
||||
public class MemoryLeakTestUtil {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(MemoryLeakTestUtil.class.getName());
|
||||
|
||||
@SuppressWarnings("UnusedVariable")
|
||||
public static <M extends Manager> void noResourceLeakTest(Function<DummyConnection, M> managerSupplier)
|
||||
throws XmppStringprepException, IllegalArgumentException, InterruptedException {
|
||||
final int numConnections = 10;
|
||||
|
||||
ReferenceQueue<DummyConnection> connectionsReferenceQueue = new ReferenceQueue<>();
|
||||
ReferenceQueue<Manager> managerReferenceQueue = new ReferenceQueue<>();
|
||||
|
||||
// Those two sets ensure that we hold a strong reference to the created PhantomReferences until the end of the
|
||||
// test.
|
||||
@SuppressWarnings("ModifiedButNotUsed")
|
||||
Set<PhantomReference<DummyConnection>> connectionsPhantomReferences = new HashSet<>();
|
||||
@SuppressWarnings("ModifiedButNotUsed")
|
||||
Set<PhantomReference<Manager>> managersPhantomReferences = new HashSet<>();
|
||||
|
||||
List<DummyConnection> connections = new ArrayList<>(numConnections);
|
||||
for (int i = 0; i < numConnections; i++) {
|
||||
DummyConnection connection = new DummyConnection("foo" + i, "bar", "baz");
|
||||
|
||||
PhantomReference<DummyConnection> connectionPhantomReference = new PhantomReference<>(connection, connectionsReferenceQueue);
|
||||
connectionsPhantomReferences.add(connectionPhantomReference);
|
||||
|
||||
Manager manager = managerSupplier.apply(connection);
|
||||
PhantomReference<Manager> managerPhantomReference = new PhantomReference<Manager>(manager, managerReferenceQueue);
|
||||
managersPhantomReferences.add(managerPhantomReference);
|
||||
|
||||
connections.add(connection);
|
||||
}
|
||||
|
||||
// Clear the only references to the created connections.
|
||||
connections = null;
|
||||
|
||||
triggerGarbageCollection();
|
||||
|
||||
// Now the connections should have been gc'ed, but not managers not yet.
|
||||
assertReferencesQueueSize(connectionsReferenceQueue, numConnections);
|
||||
assertReferencesQueueIsEmpty(managerReferenceQueue);
|
||||
|
||||
// We new create another connection and explicitly a new Manager. This will trigger the cleanup mechanism in the
|
||||
// WeakHashMaps used by the Manager's iNSTANCE field. This should clean up all references to the Managers.
|
||||
DummyConnection connection = new DummyConnection("last", "bar", "baz");
|
||||
@SuppressWarnings("unused")
|
||||
Manager manager = managerSupplier.apply(connection);
|
||||
|
||||
// The previous Managers should now be reclaimable by the garbage collector. First trigger a GC run.
|
||||
triggerGarbageCollection();
|
||||
|
||||
// Now the Managers should have been freed and this means we should see their phantom references in the
|
||||
// reference queue.
|
||||
assertReferencesQueueSize(managerReferenceQueue, numConnections);
|
||||
}
|
||||
|
||||
private static void assertReferencesQueueSize(ReferenceQueue<?> referenceQueue, int expectedSize) throws IllegalArgumentException, InterruptedException {
|
||||
final int timeout = 120000;
|
||||
final int maxAttempts = 3;
|
||||
for (int itemsRemoved = 0; itemsRemoved < expectedSize; ++itemsRemoved) {
|
||||
int attempt = 0;
|
||||
Reference<?> reference = null;
|
||||
do {
|
||||
reference = referenceQueue.remove(timeout);
|
||||
if (reference != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
attempt++;
|
||||
String message = "No reference to a gc'ed object found after " + timeout + "ms in the " + attempt
|
||||
+ ". attempt.";
|
||||
if (attempt >= maxAttempts) {
|
||||
fail(message);
|
||||
}
|
||||
|
||||
LOGGER.warning(message);
|
||||
triggerGarbageCollection();
|
||||
} while (true);
|
||||
reference.clear();
|
||||
}
|
||||
|
||||
Reference<?> reference = referenceQueue.poll();
|
||||
assertNull("Reference queue is not empty when it should be", reference);
|
||||
}
|
||||
|
||||
private static void assertReferencesQueueIsEmpty(ReferenceQueue<?> referenceQueue) {
|
||||
Reference<?> reference = referenceQueue.poll();
|
||||
assertNull(reference);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedVariable")
|
||||
private static void triggerGarbageCollection() {
|
||||
Object object = new Object();
|
||||
WeakReference<Object> weakReference = new WeakReference<>(object);
|
||||
object = null;
|
||||
|
||||
int gcCalls = 0;
|
||||
do {
|
||||
if (gcCalls > 1000) {
|
||||
throw new AssertionError("No observed gargabe collection after " + gcCalls + " calls of System.gc()");
|
||||
}
|
||||
System.gc();
|
||||
// TODO: Would a Thread.yield() here improve the chances of a full GC? It appears that on some systems we
|
||||
// observe a partial GC here.
|
||||
gcCalls++;
|
||||
} while (weakReference.get() != null);
|
||||
|
||||
// Note that this is no guarantee that a *full* garbage collection run has been made, which is what we actually
|
||||
// need here in order to prevent false negatives.
|
||||
LOGGER.finer("Observed garbage collection after " + gcCalls + " calls of System.gc()");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.io.IOException;
|
||||
import java.net.BindException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class NetworkUtil {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(NetworkUtil.class.getName());
|
||||
|
||||
public static ServerSocket getSocketOnLoopback() throws IOException {
|
||||
final InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
|
||||
final int portMin = 1024;
|
||||
final int portMax = (1 << 16) - 1;
|
||||
final int backlog = 1;
|
||||
|
||||
ServerSocket serverSocket = null;
|
||||
for (int port = portMin; port <= portMax; port++) {
|
||||
try {
|
||||
serverSocket = new ServerSocket(port, backlog, loopbackAddress);
|
||||
break;
|
||||
} catch (BindException e) {
|
||||
LOGGER.log(Level.FINEST, "Could not bind port " + port + ", trying next", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (serverSocket == null) {
|
||||
throw new IOException("Could not bind any port between " + portMin + " and " + portMax
|
||||
+ " on loopback address" + loopbackAddress);
|
||||
}
|
||||
|
||||
return serverSocket;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue