1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-12-08 22:21:08 +01:00

Merge branch '4.0'

Conflicts:
	build.gradle
	documentation/connections.html
	documentation/gettingstarted.html
	smack-core/src/main/java/org/jivesoftware/smack/SmackException.java
	smack-core/src/test/java/org/jivesoftware/smack/parsing/ParsingExceptionTest.java
	smack-core/src/test/java/org/jivesoftware/smack/test/util/TestUtils.java
	smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java
	smack-extensions/src/main/java/org/jivesoftware/smackx/caps/cache/SimpleDirectoryPersistentCache.java
	smack-extensions/src/main/java/org/jivesoftware/smackx/ping/PingManager.java
	smack-tcp/src/main/java/org/jivesoftware/smack/tcp/PacketReader.java
This commit is contained in:
Florian Schmaus 2014-07-05 14:37:22 +02:00
commit 94a16ba41e
61 changed files with 806 additions and 300 deletions

View file

@ -17,8 +17,10 @@
package org.jivesoftware.smack;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
@ -34,6 +36,7 @@ import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.parsing.ExceptionThrowingCallback;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.FileUtils;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlPullParser;
@ -49,8 +52,7 @@ import org.xmlpull.v1.XmlPullParserException;
* via the API will override settings in the configuration file.
* </ul>
*
* Configuration settings are stored in org.jivesoftware.smack/smack-config.xml (typically inside the
* smack.jar file).
* Configuration settings are stored in org.jivesoftware.smack/smack-config.xml.
*
* @author Gaston Dombiak
*/
@ -95,10 +97,13 @@ public final class SmackConfiguration {
static {
String smackVersion;
try {
InputStream is = FileUtils.getStreamForUrl("classpath:org.jivesoftware.smack/version", null);
byte[] buf = new byte[1024];
is.read(buf);
smackVersion = new String(buf, "UTF-8");
BufferedReader reader = new BufferedReader(new InputStreamReader(FileUtils.getStreamForUrl("classpath:org.jivesoftware.smack/version", null)));
smackVersion = reader.readLine();
try {
reader.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "IOException closing stream", e);
}
} catch(Exception e) {
LOGGER.log(Level.SEVERE, "Could not determine Smack version", e);
smackVersion = "unkown";
@ -162,6 +167,9 @@ public final class SmackConfiguration {
catch (Exception e) {
// Ignore.
}
// Initialize the DNS resolvers
DNSUtil.init();
}
/**

View file

@ -134,6 +134,12 @@ public class SmackException extends Exception {
}
}
/**
* ConnectionException is thrown if Smack is unable to connect to all hosts of a given XMPP
* service. The failed hosts can be retrieved with
* {@link ConnectionException#getFailedAddresses()}, which will have the exception causing the
* connection failure set and retrievable with {@link HostAddress#getException()}.
*/
public static class ConnectionException extends SmackException {
/**

View file

@ -16,6 +16,8 @@
*/
package org.jivesoftware.smack.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@ -39,6 +41,31 @@ public class DNSUtil {
private static final Logger LOGGER = Logger.getLogger(DNSUtil.class.getName());
private static DNSResolver dnsResolver = null;
/**
* Initializes DNSUtil. This method is automatically called by SmackConfiguration, you don't
* have to call it manually.
*/
public static void init() {
final String[] RESOLVERS = new String[] { "javax.JavaxResolver", "minidns.MiniDnsResolver",
"dnsjava.DNSJavaResolver" };
for (String resolver :RESOLVERS) {
DNSResolver availableResolver = null;
String resolverFull = "org.jivesoftware.smack.util.dns" + resolver;
try {
Class<?> resolverClass = Class.forName(resolverFull);
Method getInstanceMethod = resolverClass.getMethod("getInstance");
availableResolver = (DNSResolver) getInstanceMethod.invoke(null);
if (availableResolver != null) {
setDNSResolver(availableResolver);
break;
}
}
catch (ClassNotFoundException|NoSuchMethodException|SecurityException|IllegalAccessException|IllegalArgumentException|InvocationTargetException e) {
LOGGER.log(Level.FINE, "Exception on init", e);
}
}
}
/**
* Set the DNS resolver that should be used to perform DNS lookups.
*

View file

@ -22,13 +22,20 @@ import java.util.List;
public class LazyStringBuilder implements Appendable, CharSequence {
private final List<CharSequence> list;
private String cache;
private void invalidateCache() {
cache = null;
}
public LazyStringBuilder() {
list = new ArrayList<CharSequence>(20);
}
public LazyStringBuilder append(LazyStringBuilder lsb) {
list.addAll(lsb.list);
invalidateCache();
return this;
}
@ -36,6 +43,7 @@ public class LazyStringBuilder implements Appendable, CharSequence {
public LazyStringBuilder append(CharSequence csq) {
assert csq != null;
list.add(csq);
invalidateCache();
return this;
}
@ -43,17 +51,22 @@ public class LazyStringBuilder implements Appendable, CharSequence {
public LazyStringBuilder append(CharSequence csq, int start, int end) {
CharSequence subsequence = csq.subSequence(start, end);
list.add(subsequence);
invalidateCache();
return this;
}
@Override
public LazyStringBuilder append(char c) {
list.add(Character.toString(c));
invalidateCache();
return this;
}
@Override
public int length() {
if (cache != null) {
return cache.length();
}
int length = 0;
for (CharSequence csq : list) {
length += csq.length();
@ -63,6 +76,9 @@ public class LazyStringBuilder implements Appendable, CharSequence {
@Override
public char charAt(int index) {
if (cache != null) {
return cache.charAt(index);
}
for (CharSequence csq : list) {
if (index < csq.length()) {
return csq.charAt(index);
@ -80,10 +96,13 @@ public class LazyStringBuilder implements Appendable, CharSequence {
@Override
public String toString() {
StringBuilder sb = new StringBuilder(length());
for (CharSequence csq : list) {
sb.append(csq);
if (cache == null) {
StringBuilder sb = new StringBuilder(length());
for (CharSequence csq : list) {
sb.append(csq);
}
cache = sb.toString();
}
return sb.toString();
return cache;
}
}

View file

@ -134,6 +134,24 @@ public class PacketParserUtils {
}
}
/**
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
* FEATURE_PROCESS_NAMESPACES is enabled.
* <p>
* Note that not all XmlPullParser implementations will return a String on
* <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
* behavior when using the parser.
* </p>
*
* @return A suitable XmlPullParser for XMPP parsing
* @throws XmlPullParserException
*/
public static XmlPullParser newXmppParser() throws XmlPullParserException {
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
}
/**
* Parses a message packet.
*
@ -179,7 +197,7 @@ public class PacketParserUtils {
xmlLang = defaultLanguage;
}
String subject = parseContent(parser);
String subject = parseElementText(parser);
if (message.getSubject(xmlLang) == null) {
message.addSubject(xmlLang, subject);
@ -191,8 +209,8 @@ public class PacketParserUtils {
xmlLang = defaultLanguage;
}
String body = parseContent(parser);
String body = parseElementText(parser);
if (message.getBody(xmlLang) == null) {
message.addBody(xmlLang, body);
}
@ -223,29 +241,156 @@ public class PacketParserUtils {
}
/**
* Returns the content of a tag as string regardless of any tags included.
* Returns the textual content of an element as String.
* <p>
* The parser must be positioned on a START_TAG of an element which MUST NOT contain Mixed
* Content (as defined in XML 3.2.2), or else an XmlPullParserException will be thrown.
* </p>
* This method is used for the parts where the XMPP specification requires elements that contain
* only text or are the empty element.
*
* @param parser
* @return the textual content of the element as String
* @throws XmlPullParserException
* @throws IOException
*/
public static String parseElementText(XmlPullParser parser) throws XmlPullParserException, IOException {
assert (parser.getEventType() == XmlPullParser.START_TAG);
String res;
if (parser.isEmptyElementTag()) {
res = "";
}
else {
// Advance to the text of the Element
int event = parser.next();
if (event != XmlPullParser.TEXT) {
throw new XmlPullParserException(
"Non-empty element tag not followed by text, while Mixed Content (XML 3.2.2) is disallowed");
}
res = parser.getText();
event = parser.next();
if (event != XmlPullParser.END_TAG) {
throw new XmlPullParserException(
"Non-empty element tag contains child-elements, while Mixed Content (XML 3.2.2) is disallowed");
}
}
return res;
}
/**
* Returns the current element as string.
* <p>
* The parser must be positioned on START_TAG.
* </p>
* Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
*
* @param parser the XML pull parser
* @return the element as string
* @throws XmlPullParserException
* @throws IOException
*/
public static String parseElement(XmlPullParser parser) throws XmlPullParserException, IOException {
assert(parser.getEventType() == XmlPullParser.START_TAG);
return parseContentDepth(parser, parser.getDepth());
}
/**
* Returns the content of a element as string.
* <p>
* The parser must be positioned on the START_TAG of the element which content is going to get
* returned. If the current element is the empty element, then the empty string is returned. If
* it is a element which contains just text, then just the text is returned. If it contains
* nested elements (and text), then everything from the current opening tag to the corresponding
* closing tag of the same depth is returned as String.
* </p>
* Note that only the outermost namespace attributes ("xmlns") will be returned, not nested ones.
*
* @param parser the XML pull parser
* @return the content of a tag as string
* @throws XmlPullParserException if parser encounters invalid XML
* @throws IOException if an IO error occurs
*/
private static String parseContent(XmlPullParser parser)
public static String parseContent(XmlPullParser parser)
throws XmlPullParserException, IOException {
int parserDepth = parser.getDepth();
return parseContentDepth(parser, parserDepth);
assert(parser.getEventType() == XmlPullParser.START_TAG);
if (parser.isEmptyElementTag()) {
return "";
}
// Advance the parser, since we want to parse the content of the current element
parser.next();
return parseContentDepth(parser, parser.getDepth());
}
/**
* Returns the content from the current position of the parser up to the closing tag of the
* given depth. Note that only the outermost namespace attributes ("xmlns") will be returned,
* not nested ones.
* <p>
* This method is able to parse the content with MX- and KXmlParser. In order to achieve
* this some trade-off has to be make, because KXmlParser does not support xml-roundtrip (ie.
* return a String on getText() on START_TAG and END_TAG). We are therefore required to work
* around this limitation, which results in only partial support for XML namespaces ("xmlns"):
* Only the outermost namespace of elements will be included in the resulting String.
* </p>
*
* @param parser
* @param depth
* @return the content of the current depth
* @throws XmlPullParserException
* @throws IOException
*/
public static String parseContentDepth(XmlPullParser parser, int depth) throws XmlPullParserException, IOException {
StringBuilder content = new StringBuilder();
while (!(parser.next() == XmlPullParser.END_TAG && parser.getDepth() == depth)) {
String text = parser.getText();
if (text == null) {
throw new IllegalStateException("Parser should never return 'null' on getText() here");
XmlStringBuilder xml = new XmlStringBuilder();
int event = parser.getEventType();
boolean isEmptyElement = false;
// XmlPullParser reports namespaces in nested elements even if *only* the outer ones defines
// it. This 'flag' ensures that when a namespace is set for an element, it won't be set again
// in a nested element. It's an ugly workaround that has the potential to break things.
String namespaceElement = null;;
while (true) {
if (event == XmlPullParser.START_TAG) {
xml.halfOpenElement(parser.getName());
if (namespaceElement == null) {
String namespace = parser.getNamespace();
if (StringUtils.isNotEmpty(namespace)) {
xml.attribute("xmlns", namespace);
namespaceElement = parser.getName();
}
}
for (int i = 0; i < parser.getAttributeCount(); i++) {
xml.attribute(parser.getAttributeName(i), parser.getAttributeValue(i));
}
if (parser.isEmptyElementTag()) {
xml.closeEmptyElement();
isEmptyElement = true;
}
else {
xml.rightAngelBracket();
}
}
content.append(text);
else if (event == XmlPullParser.END_TAG) {
if (isEmptyElement) {
// Do nothing as the element was already closed, just reset the flag
isEmptyElement = false;
}
else {
xml.closeElement(parser.getName());
}
if (namespaceElement != null && namespaceElement.equals(parser.getName())) {
// We are on the closing tag, which defined the namespace as starting tag, reset the 'flag'
namespaceElement = null;
}
if (parser.getDepth() <= depth) {
// Abort parsing, we are done
break;
}
}
else if (event == XmlPullParser.TEXT) {
xml.append(parser.getText());
}
event = parser.next();
}
return content.toString();
return xml.toString();
}
/**

View file

@ -174,6 +174,25 @@ public class XmlStringBuilder implements Appendable, CharSequence {
return this;
}
public XmlStringBuilder emptyElement(String element) {
halfOpenElement(element);
return closeEmptyElement();
}
public XmlStringBuilder condEmptyElement(boolean condition, String element) {
if (condition) {
emptyElement(element);
}
return this;
}
public XmlStringBuilder condAttribute(boolean condition, String name, String value) {
if (condition) {
attribute(name, value);
}
return this;
}
@Override
public XmlStringBuilder append(CharSequence csq) {
assert csq != null;
@ -213,4 +232,18 @@ public class XmlStringBuilder implements Appendable, CharSequence {
public String toString() {
return sb.toString();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof XmlStringBuilder)) {
return false;
}
XmlStringBuilder otherXmlStringBuilder = (XmlStringBuilder) other;
return toString().equals(otherXmlStringBuilder.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2013 Florian Schmaus
* Copyright © 2013-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.
@ -16,9 +16,11 @@
*/
package org.jivesoftware.smack.util.dns;
import org.jivesoftware.smack.SmackException.ConnectionException;
public class HostAddress {
private String fqdn;
private int port;
private final String fqdn;
private final int port;
private Exception exception;
/**
@ -28,16 +30,8 @@ public class HostAddress {
* @throws IllegalArgumentException If the fqdn is null.
*/
public HostAddress(String fqdn) {
if (fqdn == null)
throw new IllegalArgumentException("FQDN is null");
if (fqdn.charAt(fqdn.length() - 1) == '.') {
this.fqdn = fqdn.substring(0, fqdn.length() - 1);
}
else {
this.fqdn = fqdn;
}
// Set port to the default port for XMPP client communication
this.port = 5222;
this(fqdn, 5222);
}
/**
@ -48,11 +42,17 @@ public class HostAddress {
* @throws IllegalArgumentException If the fqdn is null or port is out of valid range (0 - 65535).
*/
public HostAddress(String fqdn, int port) {
this(fqdn);
if (fqdn == null)
throw new IllegalArgumentException("FQDN is null");
if (port < 0 || port > 65535)
throw new IllegalArgumentException(
"Port must be a 16-bit unsiged integer (i.e. between 0-65535. Port was: " + port);
if (fqdn.charAt(fqdn.length() - 1) == '.') {
this.fqdn = fqdn.substring(0, fqdn.length() - 1);
}
else {
this.fqdn = fqdn;
}
this.port = port;
}
@ -68,6 +68,17 @@ public class HostAddress {
this.exception = e;
}
/**
* Retrieve the Exception that caused a connection failure to this HostAddress. Every
* HostAddress found in {@link ConnectionException} will have an Exception set,
* which can be retrieved with this method.
*
* @return the Exception causing this HostAddress to fail
*/
public Exception getException() {
return this.exception;
}
@Override
public String toString() {
return fqdn + ":" + port;